@patchstack/connect 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -138,6 +138,22 @@ npm test
138
138
  npm run build
139
139
  ```
140
140
 
141
+ ### Manifest endpoint testing
142
+
143
+ To post the current lockfile manifest to a local Patchstack API endpoint and provision a new site:
144
+
145
+ ```bash
146
+ bun run test:manifest -- --endpoint http://localhost:8000/monitor/pulse/manifest
147
+ ```
148
+
149
+ The response should include the new site UUID. To re-test an existing site, pass that UUID explicitly:
150
+
151
+ ```bash
152
+ bun run test:manifest -- --endpoint http://localhost:8000/monitor/pulse/manifest --site-uuid YOUR_REAL_UUID
153
+ ```
154
+
155
+ Use `--dry-run` to preview the payload without posting.
156
+
141
157
  ## Release process
142
158
 
143
159
  Pull requests run typecheck, tests, build, package verification, and a production dependency audit in GitHub Actions.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync, writeFileSync } from "fs";
4
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5
5
 
6
6
  // src/parsers/index.ts
7
7
  import { access } from "fs/promises";
@@ -1159,6 +1159,151 @@ function injectMarker(html, snippet) {
1159
1159
  return stripped + snippet;
1160
1160
  }
1161
1161
 
1162
+ // src/protect/install.ts
1163
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync, copyFileSync } from "fs";
1164
+ import { join } from "path";
1165
+ import { fileURLToPath } from "url";
1166
+ var TEMPLATES = fileURLToPath(new URL("./protect/templates/", import.meta.url));
1167
+ var CLIENT_TUNNEL = [
1168
+ "",
1169
+ " // PATCHSTACK auto-guard: in the browser, tunnel Supabase traffic through the app's own",
1170
+ " // server guard (same-origin) so payloads are inspected before they reach Supabase.",
1171
+ " if (typeof window !== 'undefined') {",
1172
+ " const target = typeof input === 'string' ? input : input instanceof Request ? input.url : String(input);",
1173
+ " const method = init?.method ?? (input instanceof Request ? input.method : 'GET');",
1174
+ " headers.set('x-ps-target', target);",
1175
+ " const guardUrl = new URL('/_patchstack/guard', window.location.origin).toString();",
1176
+ " return fetch(guardUrl, { ...init, method, headers });",
1177
+ " }",
1178
+ ""
1179
+ ].join("\n");
1180
+ var START_IMPORTS = [
1181
+ 'import { getRequest } from "@tanstack/react-start/server";',
1182
+ 'import { GUARD_PATH, handleGuardRequest } from "@/integrations/patchstack/guard";'
1183
+ ].join("\n");
1184
+ var START_MIDDLEWARE = [
1185
+ "",
1186
+ "// Patchstack auto-guard: intercept the tunneled data traffic before anything else runs.",
1187
+ "const patchstackGuard = createMiddleware().server(async ({ next }) => {",
1188
+ " const request = getRequest();",
1189
+ " if (request) {",
1190
+ " const { pathname } = new URL(request.url);",
1191
+ " if (pathname === GUARD_PATH) return handleGuardRequest(request);",
1192
+ " }",
1193
+ " return next();",
1194
+ "});",
1195
+ ""
1196
+ ].join("\n");
1197
+ var PS_DIR_REL = "src/integrations/patchstack";
1198
+ function log(msg) {
1199
+ console.log(`patchstack protect: ${msg}`);
1200
+ }
1201
+ function detectSupportedStack(cwd) {
1202
+ const pkgPath = join(cwd, "package.json");
1203
+ if (!existsSync2(pkgPath)) return false;
1204
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
1205
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1206
+ return Boolean(deps["@tanstack/react-start"]) && existsSync2(join(cwd, "src/start.ts")) && existsSync2(join(cwd, "src/integrations/supabase/client.ts"));
1207
+ }
1208
+ function generateManifest(cwd) {
1209
+ const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
1210
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1211
+ const packages = {};
1212
+ for (const name of Object.keys(deps)) {
1213
+ let version = String(deps[name]).replace(/^[^\d]*/, "");
1214
+ const installed = join(cwd, "node_modules", name, "package.json");
1215
+ if (existsSync2(installed)) {
1216
+ try {
1217
+ version = JSON.parse(readFileSync(installed, "utf8")).version;
1218
+ } catch {
1219
+ }
1220
+ }
1221
+ packages[name] = version;
1222
+ }
1223
+ mkdirSync(join(cwd, PS_DIR_REL), { recursive: true });
1224
+ const body = "// Patchstack auto-guard manifest \u2014 generated from the lockfile.\n// Do not edit; regenerated by `patchstack-connect protect --manifest` (wired into prebuild).\nexport const manifest = " + JSON.stringify({ packages }, null, 2) + ";\n";
1225
+ writeFileSync(join(cwd, PS_DIR_REL, "manifest.js"), body);
1226
+ log(`wrote manifest.js (${Object.keys(packages).length} packages)`);
1227
+ }
1228
+ function scaffold(cwd) {
1229
+ const dst = join(cwd, PS_DIR_REL);
1230
+ mkdirSync(dst, { recursive: true });
1231
+ copyFileSync(join(TEMPLATES, "engine.js"), join(dst, "engine.js"));
1232
+ copyFileSync(join(TEMPLATES, "engine.d.ts"), join(dst, "engine.d.ts"));
1233
+ copyFileSync(join(TEMPLATES, "manifest.d.ts"), join(dst, "manifest.d.ts"));
1234
+ copyFileSync(join(TEMPLATES, "rules.json"), join(dst, "rules.json"));
1235
+ copyFileSync(join(TEMPLATES, "guard.ts"), join(dst, "guard.ts"));
1236
+ log("scaffolded engine.js (+types), rules.json, guard.ts");
1237
+ }
1238
+ function patchClient(cwd) {
1239
+ const p = join(cwd, "src/integrations/supabase/client.ts");
1240
+ let s = readFileSync(p, "utf8");
1241
+ if (s.includes("x-ps-target")) {
1242
+ log("client.ts already wired");
1243
+ return;
1244
+ }
1245
+ const anchor = "headers.set('apikey', supabaseKey);";
1246
+ if (!s.includes(anchor)) {
1247
+ log("client.ts anchor not found (template changed?) \u2014 skipping client patch");
1248
+ return;
1249
+ }
1250
+ s = s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL);
1251
+ writeFileSync(p, s);
1252
+ log("patched client.ts (tunnel Supabase through the guard)");
1253
+ }
1254
+ function patchStart(cwd) {
1255
+ const p = join(cwd, "src/start.ts");
1256
+ let s = readFileSync(p, "utf8");
1257
+ if (s.includes("patchstackGuard")) {
1258
+ log("start.ts already wired");
1259
+ return;
1260
+ }
1261
+ const importAnchor = 'import { createStart, createMiddleware } from "@tanstack/react-start";';
1262
+ const exportAnchor = "export const startInstance";
1263
+ const rmAnchor = "requestMiddleware: [";
1264
+ if (!s.includes(importAnchor) || !s.includes(exportAnchor) || !s.includes(rmAnchor)) {
1265
+ log("start.ts anchors not found (template changed?) \u2014 skipping start patch");
1266
+ return;
1267
+ }
1268
+ s = s.replace(importAnchor, importAnchor + "\n" + START_IMPORTS);
1269
+ s = s.replace(exportAnchor, START_MIDDLEWARE + "\n" + exportAnchor);
1270
+ s = s.replace(rmAnchor, rmAnchor + "patchstackGuard, ");
1271
+ writeFileSync(p, s);
1272
+ log("patched start.ts (registered the guard as request middleware)");
1273
+ }
1274
+ function wirePrebuild(cwd) {
1275
+ const p = join(cwd, "package.json");
1276
+ const pkg = JSON.parse(readFileSync(p, "utf8"));
1277
+ pkg.scripts = pkg.scripts || {};
1278
+ const step = "patchstack-connect protect --manifest";
1279
+ const prebuild = pkg.scripts.prebuild || "";
1280
+ if (prebuild.includes(step)) {
1281
+ log("prebuild already refreshes the manifest");
1282
+ return;
1283
+ }
1284
+ pkg.scripts.prebuild = prebuild ? prebuild + " && " + step : step;
1285
+ writeFileSync(p, JSON.stringify(pkg, null, 2) + "\n");
1286
+ log("wired manifest refresh into prebuild");
1287
+ }
1288
+ function runProtect(cwd, opts = {}) {
1289
+ if (opts.manifestOnly) {
1290
+ generateManifest(cwd);
1291
+ return;
1292
+ }
1293
+ if (!detectSupportedStack(cwd)) {
1294
+ log(
1295
+ "runtime protection currently supports TanStack Start + Supabase apps; stack not detected \u2014 skipping."
1296
+ );
1297
+ return;
1298
+ }
1299
+ scaffold(cwd);
1300
+ patchClient(cwd);
1301
+ patchStart(cwd);
1302
+ generateManifest(cwd);
1303
+ wirePrebuild(cwd);
1304
+ log("done \u2014 guard wired and always-on (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only.");
1305
+ }
1306
+
1162
1307
  // src/cli.ts
1163
1308
  var HELP = `@patchstack/connect \u2014 scan your lockfile and report packages to Patchstack.
1164
1309
 
@@ -1171,6 +1316,9 @@ Usage:
1171
1316
  patchstack-connect status [options] Show current configuration
1172
1317
  patchstack-connect mark-build [options] Stamp built HTML with a production flag +
1173
1318
  build fingerprint (run as a postbuild step)
1319
+ patchstack-connect protect [--manifest] Install runtime protection (the guard) into
1320
+ a server-side JS app. --manifest only
1321
+ regenerates the package manifest (prebuild).
1174
1322
  patchstack-connect help Print this message
1175
1323
 
1176
1324
  Options (for scan and status):
@@ -1308,6 +1456,14 @@ async function runScan(args) {
1308
1456
  }
1309
1457
  return 0;
1310
1458
  }
1459
+ async function runProtectCommand(args) {
1460
+ try {
1461
+ runProtect(process.cwd(), { manifestOnly: args.flags.get("manifest") === true });
1462
+ } catch (err) {
1463
+ console.warn(`patchstack protect: skipped (${err.message}).`);
1464
+ }
1465
+ return 0;
1466
+ }
1311
1467
  async function runStatus(args) {
1312
1468
  const config = await resolveConfig({
1313
1469
  cwd: process.cwd(),
@@ -1353,10 +1509,10 @@ async function runMarkBuild(args) {
1353
1509
  const snippet = buildInjectionSnippet(checksum);
1354
1510
  let marked = 0;
1355
1511
  for (const file of files) {
1356
- const before = readFileSync(file, "utf8");
1512
+ const before = readFileSync2(file, "utf8");
1357
1513
  const after = injectMarker(before, snippet);
1358
1514
  if (after !== before) {
1359
- writeFileSync(file, after);
1515
+ writeFileSync2(file, after);
1360
1516
  marked += 1;
1361
1517
  }
1362
1518
  }
@@ -1380,6 +1536,8 @@ async function main() {
1380
1536
  return runStatus(args);
1381
1537
  case "mark-build":
1382
1538
  return runMarkBuild(args);
1539
+ case "protect":
1540
+ return runProtectCommand(args);
1383
1541
  default:
1384
1542
  console.error(`Unknown command: ${args.command}
1385
1543
  `);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/consistency.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/checksum.ts","../src/client.ts","../src/config.ts","../src/mark-build.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\n\nimport { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { computeManifestChecksum } from './checksum.js';\nimport { buildClaimUrl, postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nimport {\n buildInjectionSnippet,\n findHtmlFiles,\n injectMarker,\n resolveBuildDir,\n} from './mark-build.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 <site-uuid> Optional: pre-seed .patchstackrc.json\n with an existing site UUID\n patchstack-connect status [options] Show current configuration\n patchstack-connect mark-build [options] Stamp built HTML with a production flag +\n build fingerprint (run as a postbuild step)\n patchstack-connect help Print this message\n\nOptions (for scan and status):\n --site-uuid <uuid> Override the configured site UUID\n --endpoint <url> Override the API endpoint\n --dry-run (scan only) Show the payload without posting\n\nOptions (for mark-build):\n --dir <path> Build output directory (default: auto-detect\n dist/ build/ out/ .output/public)\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 PATCHSTACK_ENVIRONMENT Manifest environment: production | sandbox (default: production)\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', 'dir']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map<string, string | true>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map<string, string | true>();\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<string, string | true>, 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<number> {\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 <site-uuid>');\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<number> {\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 for (const warning of manifest.warnings ?? []) {\n console.warn(`patchstack: ${warning}`);\n }\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 console.log(`Reporting under the ${config.environment} environment.`);\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<number> {\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 console.log(`Environment: ${config.environment}`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function runMarkBuild(args: ParsedArgs): Promise<number> {\n const cwd = process.cwd();\n\n // Compute the build fingerprint from the lockfile. Best-effort: mark-build is a\n // postbuild step and must never fail the build, so a missing/unreadable lockfile\n // just means we stamp the production flag without a fingerprint.\n let checksum: string | null = null;\n try {\n const manifest = await scanLockfile(cwd);\n for (const warning of manifest.warnings ?? []) {\n console.warn(`mark-build: ${warning}`);\n }\n const { payload } = buildWirePayload(manifest);\n checksum = computeManifestChecksum(payload.packages);\n } catch (err) {\n console.warn(\n `mark-build: could not compute the build fingerprint (${(err as Error).message}). Stamping the production flag only.`,\n );\n }\n\n const dir = resolveBuildDir(cwd, getStringFlag(args.flags, 'dir'));\n if (dir === null) {\n console.warn(\n 'mark-build: no build output directory found (looked for dist/, build/, out/, .output/public). Pass --dir <path> if it is elsewhere. Nothing to mark.',\n );\n return 0;\n }\n\n const files = findHtmlFiles(dir);\n if (files.length === 0) {\n console.warn(`mark-build: no HTML files found under ${dir}. Nothing to mark.`);\n return 0;\n }\n\n const snippet = buildInjectionSnippet(checksum);\n let marked = 0;\n for (const file of files) {\n const before = readFileSync(file, 'utf8');\n const after = injectMarker(before, snippet);\n if (after !== before) {\n writeFileSync(file, after);\n marked += 1;\n }\n }\n\n console.log(\n `mark-build: marked ${marked} HTML file(s) in ${dir}${checksum !== null ? ` (build ${checksum})` : ''}.`,\n );\n return 0;\n}\n\nasync function main(): Promise<number> {\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 case 'mark-build':\n return runMarkBuild(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","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { missingDependencies, readDeclaredDependencyNames } from './consistency.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\nimport { parsePnpmLockfile } from './pnpm.js';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\ninterface LockfileCandidate {\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\n// Checked in priority order: the first candidate present in `cwd` wins.\nconst LOCKFILE_CANDIDATES: readonly LockfileCandidate[] = [\n { filename: 'package-lock.json', strategy: 'npm-lockfile' },\n { filename: 'bun.lock', strategy: 'node-modules-walk' },\n { filename: 'bun.lockb', strategy: 'node-modules-walk' },\n { filename: 'pnpm-lock.yaml', strategy: 'pnpm-lockfile' },\n { filename: 'yarn.lock', strategy: 'yarn-lockfile' },\n];\n\nexport async function detectLockfile(cwd: string): Promise<DetectedLockfile> {\n // Probe every candidate concurrently, then resolve by declaration order so\n // the result is identical to a sequential first-match scan — but bounded by a\n // single I/O round-trip instead of up to one `stat` per candidate.\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n const match = probed.find((candidate) => candidate.present);\n if (match) {\n return {\n ecosystem: 'npm',\n filePath: match.filePath,\n filename: match.filename,\n strategy: match.strategy,\n };\n }\n\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise<Manifest> {\n const candidates = await presentLockfiles(cwd);\n if (candidates.length === 0) {\n // Preserve the exact historical error for the no-lockfile case.\n await detectLockfile(cwd);\n }\n\n // Validate each source against package.json before trusting it. A lockfile\n // missing declared dependencies is a fossil — e.g. `npm install` planted a\n // package-lock.json once on a bun-managed platform (Lovable), and the native\n // dependency flow never updates it again. Trusting it would freeze the\n // manifest and the build fingerprint while the real dependency set drifts.\n const declared = await readDeclaredDependencyNames(cwd);\n const warnings: string[] = [];\n let firstParsed: { packages: PackageEntry[]; filename: string } | null = null;\n let walkTried = false;\n\n for (const candidate of candidates) {\n let packages: PackageEntry[];\n try {\n packages = await runStrategy(candidate, cwd);\n } catch {\n continue;\n }\n walkTried ||= candidate.strategy === 'node-modules-walk';\n firstParsed ??= { packages, filename: candidate.filename };\n\n const missing = missingDependencies(declared, packages);\n if (missing.length === 0) {\n return manifestWith(packages, warnings);\n }\n warnings.push(staleWarning(candidate.filename, missing));\n }\n\n // Last resort: the installed truth. node_modules reflects what the build\n // actually compiles against, whichever package manager wrote it.\n if (!walkTried) {\n try {\n const packages = await walkNodeModules(cwd);\n const missing = missingDependencies(declared, packages);\n if (missing.length > 0) {\n warnings.push(staleWarning('node_modules/', missing));\n }\n warnings.push('Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.');\n return manifestWith(packages, warnings);\n } catch {\n // fall through to the best lockfile we managed to parse\n }\n }\n\n if (firstParsed === null) {\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No readable lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n\n warnings.push(\n `No fully-consistent source found; reporting ${firstParsed.filename}. The manifest may understate the real dependency set.`,\n );\n\n return manifestWith(firstParsed.packages, warnings);\n}\n\nfunction manifestWith(packages: PackageEntry[], warnings: string[]): Manifest {\n return warnings.length > 0\n ? { ecosystem: 'npm', packages, warnings }\n : { ecosystem: 'npm', packages };\n}\n\nfunction staleWarning(source: string, missing: string[]): string {\n const sample = missing.slice(0, 3).join(', ');\n const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : '';\n return `${source} looks stale: package.json declares ${missing.length} dependenc${missing.length === 1 ? 'y' : 'ies'} it does not contain (${sample}${suffix}).`;\n}\n\nasync function presentLockfiles(cwd: string): Promise<DetectedLockfile[]> {\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n return probed\n .filter((candidate) => candidate.present)\n .map(({ filename, strategy, filePath }) => ({ ecosystem: 'npm' as const, filename, strategy, filePath }));\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise<PackageEntry[]> {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\n/**\n * Which environment a manifest was captured in. The connector reports from real\n * builds (prebuild scan / build hooks), so it defaults to 'production'. Override\n * with PATCHSTACK_ENVIRONMENT=sandbox (or \"environment\" in .patchstackrc.json)\n * for test manifests.\n */\nexport type Environment = 'production' | 'sandbox';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n /**\n * Human-readable scan diagnostics — set when the preferred lockfile looked\n * stale (missing dependencies declared in package.json) and another source\n * was used, or when no fully-consistent source existed. Never fatal.\n */\n warnings?: string[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n /** Environment to report the manifest under. Defaults to 'production'. */\n environment: Environment;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { PackageEntry } from '../types.js';\n\n/**\n * Registry-installable dependency names declared in package.json\n * (dependencies + devDependencies). Non-registry specifiers (file:, link:,\n * workspace:, portal:) are excluded — lockfile parsers skip those too, so\n * their absence from a parsed set says nothing about staleness. Returns []\n * when package.json is missing or unreadable (nothing to validate against).\n */\nexport async function readDeclaredDependencyNames(cwd: string): Promise<string[]> {\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n const names = new Set<string>();\n for (const group of ['dependencies', 'devDependencies'] as const) {\n const deps = (parsed as Record<string, unknown>)[group];\n if (typeof deps !== 'object' || deps === null) {\n continue;\n }\n for (const [name, spec] of Object.entries(deps as Record<string, unknown>)) {\n if (typeof spec === 'string' && /^(file|link|workspace|portal):/.test(spec)) {\n continue;\n }\n names.add(name);\n }\n }\n\n return [...names];\n}\n\n/**\n * Declared names absent from a parsed package set. A non-empty result means\n * the source predates package.json — the fossil-lockfile failure mode where\n * e.g. `npm install` planted a package-lock.json once and the platform's\n * native package manager (bun on Lovable) never updates it again.\n */\nexport function missingDependencies(declared: string[], packages: PackageEntry[]): string[] {\n const present = new Set(packages.map((entry) => entry.name));\n return declared.filter((name) => !present.has(name));\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record<string, LockfileV2Package>;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record<string, LockfileV2Package>): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record<string, LockfileV1Dependency>,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise<PackageEntry[]> {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise<void> {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise<void> {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise<void> {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise<boolean> {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses pnpm-lock.yaml without pulling in a YAML library. We don't need full\n * YAML semantics — only the `packages:` block (the canonical list of every\n * installed package) and, for direct-dep marking, the dependency sections under\n * `importers:` (v9+) or at the top level (v6-v8 single-project).\n *\n * Supported pnpm-lock key formats:\n * v5: /pkg/1.0.0 /@scope/pkg/1.0.0\n * v6-v8: /pkg@1.0.0 /@scope/pkg@1.0.0 (optional `(peer@x)` suffix)\n * v9: pkg@1.0.0 @scope/pkg@1.0.0 (optional `(peer@x)` suffix, may be quoted)\n */\nexport async function parsePnpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const lines = raw.split(/\\r?\\n/);\n const directNames = collectDirectDepNames(lines);\n const packageKeys = collectPackagesBlockKeys(lines);\n\n if (packageKeys.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const entries: PackageEntry[] = [];\n for (const key of packageKeys) {\n const parsed = parsePackageKey(key);\n if (parsed === null) {\n continue;\n }\n entries.push({\n name: parsed.name,\n version: parsed.version,\n direct: directNames.has(parsed.name),\n });\n }\n\n return entries;\n}\n\ninterface ParsedKey {\n name: string;\n version: string;\n}\n\nexport function parsePackageKey(rawKey: string): ParsedKey | null {\n let k = rawKey.trim();\n if (k.length === 0) {\n return null;\n }\n\n if (\n (k.startsWith(\"'\") && k.endsWith(\"'\")) ||\n (k.startsWith('\"') && k.endsWith('\"'))\n ) {\n k = k.slice(1, -1);\n }\n\n if (k.startsWith('/')) {\n k = k.slice(1);\n }\n\n // Strip peer-dependency suffix used by v6+: `pkg@1.0.0(peer@1.0.0)(other@2)`.\n const parenIdx = k.indexOf('(');\n if (parenIdx >= 0) {\n k = k.slice(0, parenIdx);\n }\n\n // Split off the optional `@scope/` prefix so the remaining \"body\" contains\n // exactly one separator between the bare name and the version. npm forbids\n // both `@` and `/` inside the bare-name portion, so the first occurrence of\n // either character in the body is unambiguously the name/version separator.\n let scopePrefix = '';\n let body = k;\n if (k.startsWith('@')) {\n const firstSlash = k.indexOf('/');\n if (firstSlash <= 0) {\n return null;\n }\n scopePrefix = k.slice(0, firstSlash + 1);\n body = k.slice(firstSlash + 1);\n }\n\n const slashIdx = body.indexOf('/');\n const atIdx = body.indexOf('@');\n\n let sepIdx: number;\n if (slashIdx < 0 && atIdx < 0) {\n return null;\n } else if (slashIdx < 0) {\n sepIdx = atIdx;\n } else if (atIdx < 0) {\n sepIdx = slashIdx;\n } else {\n sepIdx = Math.min(slashIdx, atIdx);\n }\n\n const name = scopePrefix + body.slice(0, sepIdx);\n let version = body.slice(sepIdx + 1);\n\n // v5 peer suffix uses `_`: `1.0.0_react@18.2.0`.\n const underscoreIdx = version.indexOf('_');\n if (underscoreIdx >= 0) {\n version = version.slice(0, underscoreIdx);\n }\n\n if (name.length === 0 || version.length === 0) {\n return null;\n }\n\n return { name, version };\n}\n\nfunction indentOf(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\nfunction isBlankOrComment(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.length === 0 || trimmed.startsWith('#');\n}\n\n/**\n * Collects keys directly nested under the top-level `packages:` block. Stops\n * when another top-level key (e.g. `snapshots:`) appears.\n */\nfunction collectPackagesBlockKeys(lines: string[]): string[] {\n const keys: string[] = [];\n let inBlock = false;\n let childIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n\n const indent = indentOf(line);\n\n if (!inBlock) {\n if (indent === 0 && line.trim() === 'packages:') {\n inBlock = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (childIndent === null) {\n childIndent = indent;\n }\n\n if (indent !== childIndent) {\n continue;\n }\n\n const content = line.slice(indent);\n if (!content.endsWith(':')) {\n continue;\n }\n\n keys.push(content.slice(0, -1));\n }\n\n return keys;\n}\n\n/**\n * Collects direct dependency names. Looks in two places, since the format\n * differs across lockfile versions and workspaces:\n *\n * 1. `importers:` > `<importer>:` > `{dependencies,devDependencies,optionalDependencies}:`\n * (v9 always, v6-v8 in workspaces)\n * 2. Top-level `{dependencies,devDependencies,optionalDependencies}:`\n * (v6-v8 single-project lockfiles)\n *\n * Direct-dep marking is best-effort: a name absent from both is simply left as\n * not-direct, matching the npm parser's behaviour for transitive packages.\n */\nfunction collectDirectDepNames(lines: string[]): Set<string> {\n const names = new Set<string>();\n collectFromImporters(lines, names);\n for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n collectFromTopLevelSection(lines, section, names);\n }\n return names;\n}\n\nconst DEP_SECTIONS = new Set([\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n]);\n\nfunction collectFromImporters(lines: string[], out: Set<string>): void {\n let inImporters = false;\n let importerIndent: number | null = null;\n let inDepSection = false;\n let depSectionIndent: number | null = null;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inImporters) {\n if (indent === 0 && trimmed === 'importers:') {\n inImporters = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n // Track the indent at which importer entries (e.g. `.:`, `packages/foo:`) sit.\n if (importerIndent === null) {\n importerIndent = indent;\n }\n\n if (indent === importerIndent) {\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n continue;\n }\n\n if (!inDepSection) {\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (depSectionIndent !== null && indent <= depSectionIndent) {\n // Reset — we've exited the dep section. Reconsider this line.\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction collectFromTopLevelSection(\n lines: string[],\n section: string,\n out: Set<string>,\n): void {\n let inSection = false;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inSection) {\n if (indent === 0 && trimmed === `${section}:`) {\n inSection = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction stripTrailingColon(s: string): string | null {\n if (!s.endsWith(':')) {\n return null;\n }\n return s.slice(0, -1).trim();\n}\n\n/**\n * Extracts the package name from a dependency-section leaf, handling both the\n * short form (`pkg: 1.0.0`) and the v9 expanded form (`pkg:` followed by\n * `specifier:` / `version:` children). Quoted scoped names like `'@scope/pkg':`\n * are unquoted.\n */\nfunction extractLeafName(trimmed: string): string | null {\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 0) {\n return null;\n }\n let name = trimmed.slice(0, colonIdx).trim();\n if (name.length === 0) {\n return null;\n }\n if (\n (name.startsWith(\"'\") && name.endsWith(\"'\")) ||\n (name.startsWith('\"') && name.endsWith('\"'))\n ) {\n name = name.slice(1, -1);\n }\n return name.length > 0 ? name : null;\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set<string>();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set<string>;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set<string>();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise<Set<string>> {\n const names = new Set<string>();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record<string, unknown>;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map<string, Set<string>>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { createHash } from 'node:crypto';\n\nimport type { WirePackage } from './normalize.js';\n\n/**\n * Fingerprint of a manifest's package set, byte-for-byte identical to the\n * checksum Patchstack stores server-side (PulseController::storeManifest): the\n * first 12 hex chars of sha256 over the JSON of the packages sorted by\n * `[name, version]` with plain lexicographic (byte) ordering.\n *\n * This mirrors PHP's `usort(..., fn($a,$b) => [$a['name'],$a['version']] <=> ...)`\n * followed by `json_encode(..., JSON_UNESCAPED_SLASHES)`. Two deliberate details:\n *\n * - The sort is lexicographic, NOT the semver-aware ordering `buildWirePayload`\n * uses for display. The server re-sorts lexicographically before hashing, so\n * the fingerprint must too (e.g. `1.10.0` sorts before `1.9.0`).\n * - npm/composer package names and versions are ASCII, so JS `JSON.stringify`\n * and PHP `json_encode` produce identical bytes (no unicode escaping, and\n * neither escapes `/`, so scoped names like `@babel/core` match).\n *\n * Injected into built HTML by `mark-build` and reported by the disclosure widget\n * so Patchstack can compare the live build against the last reported manifest.\n */\nexport function computeManifestChecksum(packages: WirePackage[]): string {\n const canonical = packages\n .map((pkg) => ({ name: pkg.name, version: pkg.version }))\n .sort((a, b) => {\n if (a.name !== b.name) {\n return a.name < b.name ? -1 : 1;\n }\n if (a.version !== b.version) {\n return a.version < b.version ? -1 : 1;\n }\n return 0;\n });\n\n return createHash('sha256').update(JSON.stringify(canonical)).digest('hex').slice(0, 12);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=<uuid>`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise<StoreManifestResponse> {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify({ ...payload, environment: config.environment }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config, type Environment } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\nexport const DEFAULT_ENVIRONMENT: Environment = 'production';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n environment?: string;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise<Config> {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const environmentRaw = fromEnv.environment ?? fromFile.environment;\n if (environmentRaw !== undefined && !isEnvironment(environmentRaw)) {\n throw new PatchstackError(\n `Environment must be \"production\" or \"sandbox\"; got \"${environmentRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n const environment: Environment = environmentRaw ?? DEFAULT_ENVIRONMENT;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n environment,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise<string> {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise<string> {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise<ConfigFile> {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n environment:\n environmentRaw !== undefined && environmentRaw.length > 0 ? environmentRaw : undefined,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction isEnvironment(value: string): value is Environment {\n return value === 'production' || value === 'sandbox';\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport path from 'node:path';\n\n/** Attribute that tags our injected <script> so re-runs replace it instead of stacking. */\nexport const MARKER_ATTR = 'data-patchstack-build';\n\n/** Build output directories we look for, in priority order (Vite, CRA, Next export, Nuxt). */\nexport const BUILD_DIR_CANDIDATES = ['dist', 'build', 'out', '.output/public'];\n\n/**\n * Resolve the directory holding the built HTML. Honours an explicit `--dir`\n * override, otherwise picks the first known build directory that exists.\n * Returns null when nothing is found (mark-build then no-ops without failing).\n */\nexport function resolveBuildDir(cwd: string, override?: string): string | null {\n if (override !== undefined && override !== '') {\n const abs = path.resolve(cwd, override);\n return existsSync(abs) && statSync(abs).isDirectory() ? abs : null;\n }\n\n for (const candidate of BUILD_DIR_CANDIDATES) {\n const abs = path.resolve(cwd, candidate);\n if (existsSync(abs) && statSync(abs).isDirectory()) {\n return abs;\n }\n }\n\n return null;\n}\n\n/** Recursively collect every `.html` file under `dir`. */\nexport function findHtmlFiles(dir: string): string[] {\n const out: string[] = [];\n\n const walk = (current: string): void => {\n for (const entry of readdirSync(current, { withFileTypes: true })) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {\n out.push(full);\n }\n }\n };\n\n walk(dir);\n return out;\n}\n\n/**\n * The <script> we inject into built HTML. Always marks the build as production\n * (so the widget hides the connect/claim prompt on the published site) and, when\n * a fingerprint is available, exposes it for the widget's parity heartbeat.\n */\nexport function buildInjectionSnippet(checksum: string | null): string {\n const statements = ['window.__PATCHSTACK_PROD__=true;'];\n if (checksum !== null && checksum !== '') {\n statements.push(`window.__PATCHSTACK_BUILD__=${JSON.stringify(checksum)};`);\n }\n return `<script ${MARKER_ATTR}>${statements.join('')}</script>`;\n}\n\n/**\n * Insert (or replace) the marker script in a single HTML document. Idempotent:\n * a prior marker is stripped first so repeated builds don't stack tags. Prefers\n * `</head>`, falls back to `</body>`, then appends.\n */\nexport function injectMarker(html: string, snippet: string): string {\n const stripped = html.replace(\n new RegExp(`\\\\s*<script ${MARKER_ATTR}[^>]*>[\\\\s\\\\S]*?</script>`, 'gi'),\n '',\n );\n\n if (/<\\/head>/i.test(stripped)) {\n return stripped.replace(/<\\/head>/i, `${snippet}</head>`);\n }\n if (/<\\/body>/i.test(stripped)) {\n return stripped.replace(/<\\/body>/i, `${snippet}</body>`);\n }\n return stripped + snippet;\n}\n"],"mappings":"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","names":["path","readFile","path","readFile","path","readFile","path","path","readFile","readFile","readFile","readFile","path","readFile","path","path","readFile","path","path","readFile","path","walk"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/consistency.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/checksum.ts","../src/client.ts","../src/config.ts","../src/mark-build.ts","../src/protect/install.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\n\nimport { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { computeManifestChecksum } from './checksum.js';\nimport { buildClaimUrl, postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nimport {\n buildInjectionSnippet,\n findHtmlFiles,\n injectMarker,\n resolveBuildDir,\n} from './mark-build.js';\nimport { runProtect } from './protect/install.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 <site-uuid> Optional: pre-seed .patchstackrc.json\n with an existing site UUID\n patchstack-connect status [options] Show current configuration\n patchstack-connect mark-build [options] Stamp built HTML with a production flag +\n build fingerprint (run as a postbuild step)\n patchstack-connect protect [--manifest] Install runtime protection (the guard) into\n a server-side JS app. --manifest only\n regenerates the package manifest (prebuild).\n patchstack-connect help Print this message\n\nOptions (for scan and status):\n --site-uuid <uuid> Override the configured site UUID\n --endpoint <url> Override the API endpoint\n --dry-run (scan only) Show the payload without posting\n\nOptions (for mark-build):\n --dir <path> Build output directory (default: auto-detect\n dist/ build/ out/ .output/public)\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 PATCHSTACK_ENVIRONMENT Manifest environment: production | sandbox (default: production)\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', 'dir']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map<string, string | true>;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map<string, string | true>();\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<string, string | true>, 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<number> {\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 <site-uuid>');\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<number> {\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 for (const warning of manifest.warnings ?? []) {\n console.warn(`patchstack: ${warning}`);\n }\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 console.log(`Reporting under the ${config.environment} environment.`);\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 runProtectCommand(args: ParsedArgs): Promise<number> {\n // Best-effort: like mark-build, this runs during builds and must never fail one.\n try {\n runProtect(process.cwd(), { manifestOnly: args.flags.get('manifest') === true });\n } catch (err) {\n console.warn(`patchstack protect: skipped (${(err as Error).message}).`);\n }\n return 0;\n}\n\nasync function runStatus(args: ParsedArgs): Promise<number> {\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 console.log(`Environment: ${config.environment}`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function runMarkBuild(args: ParsedArgs): Promise<number> {\n const cwd = process.cwd();\n\n // Compute the build fingerprint from the lockfile. Best-effort: mark-build is a\n // postbuild step and must never fail the build, so a missing/unreadable lockfile\n // just means we stamp the production flag without a fingerprint.\n let checksum: string | null = null;\n try {\n const manifest = await scanLockfile(cwd);\n for (const warning of manifest.warnings ?? []) {\n console.warn(`mark-build: ${warning}`);\n }\n const { payload } = buildWirePayload(manifest);\n checksum = computeManifestChecksum(payload.packages);\n } catch (err) {\n console.warn(\n `mark-build: could not compute the build fingerprint (${(err as Error).message}). Stamping the production flag only.`,\n );\n }\n\n const dir = resolveBuildDir(cwd, getStringFlag(args.flags, 'dir'));\n if (dir === null) {\n console.warn(\n 'mark-build: no build output directory found (looked for dist/, build/, out/, .output/public). Pass --dir <path> if it is elsewhere. Nothing to mark.',\n );\n return 0;\n }\n\n const files = findHtmlFiles(dir);\n if (files.length === 0) {\n console.warn(`mark-build: no HTML files found under ${dir}. Nothing to mark.`);\n return 0;\n }\n\n const snippet = buildInjectionSnippet(checksum);\n let marked = 0;\n for (const file of files) {\n const before = readFileSync(file, 'utf8');\n const after = injectMarker(before, snippet);\n if (after !== before) {\n writeFileSync(file, after);\n marked += 1;\n }\n }\n\n console.log(\n `mark-build: marked ${marked} HTML file(s) in ${dir}${checksum !== null ? ` (build ${checksum})` : ''}.`,\n );\n return 0;\n}\n\nasync function main(): Promise<number> {\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 case 'mark-build':\n return runMarkBuild(args);\n case 'protect':\n return runProtectCommand(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","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { missingDependencies, readDeclaredDependencyNames } from './consistency.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\nimport { parsePnpmLockfile } from './pnpm.js';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\ninterface LockfileCandidate {\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\n// Checked in priority order: the first candidate present in `cwd` wins.\nconst LOCKFILE_CANDIDATES: readonly LockfileCandidate[] = [\n { filename: 'package-lock.json', strategy: 'npm-lockfile' },\n { filename: 'bun.lock', strategy: 'node-modules-walk' },\n { filename: 'bun.lockb', strategy: 'node-modules-walk' },\n { filename: 'pnpm-lock.yaml', strategy: 'pnpm-lockfile' },\n { filename: 'yarn.lock', strategy: 'yarn-lockfile' },\n];\n\nexport async function detectLockfile(cwd: string): Promise<DetectedLockfile> {\n // Probe every candidate concurrently, then resolve by declaration order so\n // the result is identical to a sequential first-match scan — but bounded by a\n // single I/O round-trip instead of up to one `stat` per candidate.\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n const match = probed.find((candidate) => candidate.present);\n if (match) {\n return {\n ecosystem: 'npm',\n filePath: match.filePath,\n filename: match.filename,\n strategy: match.strategy,\n };\n }\n\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise<Manifest> {\n const candidates = await presentLockfiles(cwd);\n if (candidates.length === 0) {\n // Preserve the exact historical error for the no-lockfile case.\n await detectLockfile(cwd);\n }\n\n // Validate each source against package.json before trusting it. A lockfile\n // missing declared dependencies is a fossil — e.g. `npm install` planted a\n // package-lock.json once on a bun-managed platform (Lovable), and the native\n // dependency flow never updates it again. Trusting it would freeze the\n // manifest and the build fingerprint while the real dependency set drifts.\n const declared = await readDeclaredDependencyNames(cwd);\n const warnings: string[] = [];\n let firstParsed: { packages: PackageEntry[]; filename: string } | null = null;\n let walkTried = false;\n\n for (const candidate of candidates) {\n let packages: PackageEntry[];\n try {\n packages = await runStrategy(candidate, cwd);\n } catch {\n continue;\n }\n walkTried ||= candidate.strategy === 'node-modules-walk';\n firstParsed ??= { packages, filename: candidate.filename };\n\n const missing = missingDependencies(declared, packages);\n if (missing.length === 0) {\n return manifestWith(packages, warnings);\n }\n warnings.push(staleWarning(candidate.filename, missing));\n }\n\n // Last resort: the installed truth. node_modules reflects what the build\n // actually compiles against, whichever package manager wrote it.\n if (!walkTried) {\n try {\n const packages = await walkNodeModules(cwd);\n const missing = missingDependencies(declared, packages);\n if (missing.length > 0) {\n warnings.push(staleWarning('node_modules/', missing));\n }\n warnings.push('Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.');\n return manifestWith(packages, warnings);\n } catch {\n // fall through to the best lockfile we managed to parse\n }\n }\n\n if (firstParsed === null) {\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No readable lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n\n warnings.push(\n `No fully-consistent source found; reporting ${firstParsed.filename}. The manifest may understate the real dependency set.`,\n );\n\n return manifestWith(firstParsed.packages, warnings);\n}\n\nfunction manifestWith(packages: PackageEntry[], warnings: string[]): Manifest {\n return warnings.length > 0\n ? { ecosystem: 'npm', packages, warnings }\n : { ecosystem: 'npm', packages };\n}\n\nfunction staleWarning(source: string, missing: string[]): string {\n const sample = missing.slice(0, 3).join(', ');\n const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : '';\n return `${source} looks stale: package.json declares ${missing.length} dependenc${missing.length === 1 ? 'y' : 'ies'} it does not contain (${sample}${suffix}).`;\n}\n\nasync function presentLockfiles(cwd: string): Promise<DetectedLockfile[]> {\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n return probed\n .filter((candidate) => candidate.present)\n .map(({ filename, strategy, filePath }) => ({ ecosystem: 'npm' as const, filename, strategy, filePath }));\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise<PackageEntry[]> {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\n/**\n * Which environment a manifest was captured in. The connector reports from real\n * builds (prebuild scan / build hooks), so it defaults to 'production'. Override\n * with PATCHSTACK_ENVIRONMENT=sandbox (or \"environment\" in .patchstackrc.json)\n * for test manifests.\n */\nexport type Environment = 'production' | 'sandbox';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n /**\n * Human-readable scan diagnostics — set when the preferred lockfile looked\n * stale (missing dependencies declared in package.json) and another source\n * was used, or when no fully-consistent source existed. Never fatal.\n */\n warnings?: string[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n /** Environment to report the manifest under. Defaults to 'production'. */\n environment: Environment;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { PackageEntry } from '../types.js';\n\n/**\n * Registry-installable dependency names declared in package.json\n * (dependencies + devDependencies). Non-registry specifiers (file:, link:,\n * workspace:, portal:) are excluded — lockfile parsers skip those too, so\n * their absence from a parsed set says nothing about staleness. Returns []\n * when package.json is missing or unreadable (nothing to validate against).\n */\nexport async function readDeclaredDependencyNames(cwd: string): Promise<string[]> {\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n const names = new Set<string>();\n for (const group of ['dependencies', 'devDependencies'] as const) {\n const deps = (parsed as Record<string, unknown>)[group];\n if (typeof deps !== 'object' || deps === null) {\n continue;\n }\n for (const [name, spec] of Object.entries(deps as Record<string, unknown>)) {\n if (typeof spec === 'string' && /^(file|link|workspace|portal):/.test(spec)) {\n continue;\n }\n names.add(name);\n }\n }\n\n return [...names];\n}\n\n/**\n * Declared names absent from a parsed package set. A non-empty result means\n * the source predates package.json — the fossil-lockfile failure mode where\n * e.g. `npm install` planted a package-lock.json once and the platform's\n * native package manager (bun on Lovable) never updates it again.\n */\nexport function missingDependencies(declared: string[], packages: PackageEntry[]): string[] {\n const present = new Set(packages.map((entry) => entry.name));\n return declared.filter((name) => !present.has(name));\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record<string, LockfileV2Package>;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record<string, LockfileV2Package>): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record<string, LockfileV1Dependency>,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise<PackageEntry[]> {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise<void> {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise<void> {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise<void> {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise<boolean> {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses pnpm-lock.yaml without pulling in a YAML library. We don't need full\n * YAML semantics — only the `packages:` block (the canonical list of every\n * installed package) and, for direct-dep marking, the dependency sections under\n * `importers:` (v9+) or at the top level (v6-v8 single-project).\n *\n * Supported pnpm-lock key formats:\n * v5: /pkg/1.0.0 /@scope/pkg/1.0.0\n * v6-v8: /pkg@1.0.0 /@scope/pkg@1.0.0 (optional `(peer@x)` suffix)\n * v9: pkg@1.0.0 @scope/pkg@1.0.0 (optional `(peer@x)` suffix, may be quoted)\n */\nexport async function parsePnpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const lines = raw.split(/\\r?\\n/);\n const directNames = collectDirectDepNames(lines);\n const packageKeys = collectPackagesBlockKeys(lines);\n\n if (packageKeys.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const entries: PackageEntry[] = [];\n for (const key of packageKeys) {\n const parsed = parsePackageKey(key);\n if (parsed === null) {\n continue;\n }\n entries.push({\n name: parsed.name,\n version: parsed.version,\n direct: directNames.has(parsed.name),\n });\n }\n\n return entries;\n}\n\ninterface ParsedKey {\n name: string;\n version: string;\n}\n\nexport function parsePackageKey(rawKey: string): ParsedKey | null {\n let k = rawKey.trim();\n if (k.length === 0) {\n return null;\n }\n\n if (\n (k.startsWith(\"'\") && k.endsWith(\"'\")) ||\n (k.startsWith('\"') && k.endsWith('\"'))\n ) {\n k = k.slice(1, -1);\n }\n\n if (k.startsWith('/')) {\n k = k.slice(1);\n }\n\n // Strip peer-dependency suffix used by v6+: `pkg@1.0.0(peer@1.0.0)(other@2)`.\n const parenIdx = k.indexOf('(');\n if (parenIdx >= 0) {\n k = k.slice(0, parenIdx);\n }\n\n // Split off the optional `@scope/` prefix so the remaining \"body\" contains\n // exactly one separator between the bare name and the version. npm forbids\n // both `@` and `/` inside the bare-name portion, so the first occurrence of\n // either character in the body is unambiguously the name/version separator.\n let scopePrefix = '';\n let body = k;\n if (k.startsWith('@')) {\n const firstSlash = k.indexOf('/');\n if (firstSlash <= 0) {\n return null;\n }\n scopePrefix = k.slice(0, firstSlash + 1);\n body = k.slice(firstSlash + 1);\n }\n\n const slashIdx = body.indexOf('/');\n const atIdx = body.indexOf('@');\n\n let sepIdx: number;\n if (slashIdx < 0 && atIdx < 0) {\n return null;\n } else if (slashIdx < 0) {\n sepIdx = atIdx;\n } else if (atIdx < 0) {\n sepIdx = slashIdx;\n } else {\n sepIdx = Math.min(slashIdx, atIdx);\n }\n\n const name = scopePrefix + body.slice(0, sepIdx);\n let version = body.slice(sepIdx + 1);\n\n // v5 peer suffix uses `_`: `1.0.0_react@18.2.0`.\n const underscoreIdx = version.indexOf('_');\n if (underscoreIdx >= 0) {\n version = version.slice(0, underscoreIdx);\n }\n\n if (name.length === 0 || version.length === 0) {\n return null;\n }\n\n return { name, version };\n}\n\nfunction indentOf(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\nfunction isBlankOrComment(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.length === 0 || trimmed.startsWith('#');\n}\n\n/**\n * Collects keys directly nested under the top-level `packages:` block. Stops\n * when another top-level key (e.g. `snapshots:`) appears.\n */\nfunction collectPackagesBlockKeys(lines: string[]): string[] {\n const keys: string[] = [];\n let inBlock = false;\n let childIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n\n const indent = indentOf(line);\n\n if (!inBlock) {\n if (indent === 0 && line.trim() === 'packages:') {\n inBlock = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (childIndent === null) {\n childIndent = indent;\n }\n\n if (indent !== childIndent) {\n continue;\n }\n\n const content = line.slice(indent);\n if (!content.endsWith(':')) {\n continue;\n }\n\n keys.push(content.slice(0, -1));\n }\n\n return keys;\n}\n\n/**\n * Collects direct dependency names. Looks in two places, since the format\n * differs across lockfile versions and workspaces:\n *\n * 1. `importers:` > `<importer>:` > `{dependencies,devDependencies,optionalDependencies}:`\n * (v9 always, v6-v8 in workspaces)\n * 2. Top-level `{dependencies,devDependencies,optionalDependencies}:`\n * (v6-v8 single-project lockfiles)\n *\n * Direct-dep marking is best-effort: a name absent from both is simply left as\n * not-direct, matching the npm parser's behaviour for transitive packages.\n */\nfunction collectDirectDepNames(lines: string[]): Set<string> {\n const names = new Set<string>();\n collectFromImporters(lines, names);\n for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n collectFromTopLevelSection(lines, section, names);\n }\n return names;\n}\n\nconst DEP_SECTIONS = new Set([\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n]);\n\nfunction collectFromImporters(lines: string[], out: Set<string>): void {\n let inImporters = false;\n let importerIndent: number | null = null;\n let inDepSection = false;\n let depSectionIndent: number | null = null;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inImporters) {\n if (indent === 0 && trimmed === 'importers:') {\n inImporters = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n // Track the indent at which importer entries (e.g. `.:`, `packages/foo:`) sit.\n if (importerIndent === null) {\n importerIndent = indent;\n }\n\n if (indent === importerIndent) {\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n continue;\n }\n\n if (!inDepSection) {\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (depSectionIndent !== null && indent <= depSectionIndent) {\n // Reset — we've exited the dep section. Reconsider this line.\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction collectFromTopLevelSection(\n lines: string[],\n section: string,\n out: Set<string>,\n): void {\n let inSection = false;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inSection) {\n if (indent === 0 && trimmed === `${section}:`) {\n inSection = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction stripTrailingColon(s: string): string | null {\n if (!s.endsWith(':')) {\n return null;\n }\n return s.slice(0, -1).trim();\n}\n\n/**\n * Extracts the package name from a dependency-section leaf, handling both the\n * short form (`pkg: 1.0.0`) and the v9 expanded form (`pkg:` followed by\n * `specifier:` / `version:` children). Quoted scoped names like `'@scope/pkg':`\n * are unquoted.\n */\nfunction extractLeafName(trimmed: string): string | null {\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 0) {\n return null;\n }\n let name = trimmed.slice(0, colonIdx).trim();\n if (name.length === 0) {\n return null;\n }\n if (\n (name.startsWith(\"'\") && name.endsWith(\"'\")) ||\n (name.startsWith('\"') && name.endsWith('\"'))\n ) {\n name = name.slice(1, -1);\n }\n return name.length > 0 ? name : null;\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set<string>();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set<string>;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set<string>();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise<Set<string>> {\n const names = new Set<string>();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record<string, unknown>;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map<string, Set<string>>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { createHash } from 'node:crypto';\n\nimport type { WirePackage } from './normalize.js';\n\n/**\n * Fingerprint of a manifest's package set, byte-for-byte identical to the\n * checksum Patchstack stores server-side (PulseController::storeManifest): the\n * first 12 hex chars of sha256 over the JSON of the packages sorted by\n * `[name, version]` with plain lexicographic (byte) ordering.\n *\n * This mirrors PHP's `usort(..., fn($a,$b) => [$a['name'],$a['version']] <=> ...)`\n * followed by `json_encode(..., JSON_UNESCAPED_SLASHES)`. Two deliberate details:\n *\n * - The sort is lexicographic, NOT the semver-aware ordering `buildWirePayload`\n * uses for display. The server re-sorts lexicographically before hashing, so\n * the fingerprint must too (e.g. `1.10.0` sorts before `1.9.0`).\n * - npm/composer package names and versions are ASCII, so JS `JSON.stringify`\n * and PHP `json_encode` produce identical bytes (no unicode escaping, and\n * neither escapes `/`, so scoped names like `@babel/core` match).\n *\n * Injected into built HTML by `mark-build` and reported by the disclosure widget\n * so Patchstack can compare the live build against the last reported manifest.\n */\nexport function computeManifestChecksum(packages: WirePackage[]): string {\n const canonical = packages\n .map((pkg) => ({ name: pkg.name, version: pkg.version }))\n .sort((a, b) => {\n if (a.name !== b.name) {\n return a.name < b.name ? -1 : 1;\n }\n if (a.version !== b.version) {\n return a.version < b.version ? -1 : 1;\n }\n return 0;\n });\n\n return createHash('sha256').update(JSON.stringify(canonical)).digest('hex').slice(0, 12);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=<uuid>`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise<StoreManifestResponse> {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify({ ...payload, environment: config.environment }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config, type Environment } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\nexport const DEFAULT_ENVIRONMENT: Environment = 'production';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n environment?: string;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise<Config> {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const environmentRaw = fromEnv.environment ?? fromFile.environment;\n if (environmentRaw !== undefined && !isEnvironment(environmentRaw)) {\n throw new PatchstackError(\n `Environment must be \"production\" or \"sandbox\"; got \"${environmentRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n const environment: Environment = environmentRaw ?? DEFAULT_ENVIRONMENT;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n environment,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise<string> {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise<string> {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise<ConfigFile> {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n environment:\n environmentRaw !== undefined && environmentRaw.length > 0 ? environmentRaw : undefined,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction isEnvironment(value: string): value is Environment {\n return value === 'production' || value === 'sandbox';\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport path from 'node:path';\n\n/** Attribute that tags our injected <script> so re-runs replace it instead of stacking. */\nexport const MARKER_ATTR = 'data-patchstack-build';\n\n/** Build output directories we look for, in priority order (Vite, CRA, Next export, Nuxt). */\nexport const BUILD_DIR_CANDIDATES = ['dist', 'build', 'out', '.output/public'];\n\n/**\n * Resolve the directory holding the built HTML. Honours an explicit `--dir`\n * override, otherwise picks the first known build directory that exists.\n * Returns null when nothing is found (mark-build then no-ops without failing).\n */\nexport function resolveBuildDir(cwd: string, override?: string): string | null {\n if (override !== undefined && override !== '') {\n const abs = path.resolve(cwd, override);\n return existsSync(abs) && statSync(abs).isDirectory() ? abs : null;\n }\n\n for (const candidate of BUILD_DIR_CANDIDATES) {\n const abs = path.resolve(cwd, candidate);\n if (existsSync(abs) && statSync(abs).isDirectory()) {\n return abs;\n }\n }\n\n return null;\n}\n\n/** Recursively collect every `.html` file under `dir`. */\nexport function findHtmlFiles(dir: string): string[] {\n const out: string[] = [];\n\n const walk = (current: string): void => {\n for (const entry of readdirSync(current, { withFileTypes: true })) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {\n out.push(full);\n }\n }\n };\n\n walk(dir);\n return out;\n}\n\n/**\n * The <script> we inject into built HTML. Always marks the build as production\n * (so the widget hides the connect/claim prompt on the published site) and, when\n * a fingerprint is available, exposes it for the widget's parity heartbeat.\n */\nexport function buildInjectionSnippet(checksum: string | null): string {\n const statements = ['window.__PATCHSTACK_PROD__=true;'];\n if (checksum !== null && checksum !== '') {\n statements.push(`window.__PATCHSTACK_BUILD__=${JSON.stringify(checksum)};`);\n }\n return `<script ${MARKER_ATTR}>${statements.join('')}</script>`;\n}\n\n/**\n * Insert (or replace) the marker script in a single HTML document. Idempotent:\n * a prior marker is stripped first so repeated builds don't stack tags. Prefers\n * `</head>`, falls back to `</body>`, then appends.\n */\nexport function injectMarker(html: string, snippet: string): string {\n const stripped = html.replace(\n new RegExp(`\\\\s*<script ${MARKER_ATTR}[^>]*>[\\\\s\\\\S]*?</script>`, 'gi'),\n '',\n );\n\n if (/<\\/head>/i.test(stripped)) {\n return stripped.replace(/<\\/head>/i, `${snippet}</head>`);\n }\n if (/<\\/body>/i.test(stripped)) {\n return stripped.replace(/<\\/body>/i, `${snippet}</body>`);\n }\n return stripped + snippet;\n}\n","// `patchstack-connect protect` — installs the runtime guard into a server-side JS app.\n//\n// This is the paid \"virtual patching\" layer. \"Add Patchstack\" already installs the connector;\n// this wires the guard so exploit requests against known-vulnerable packages are blocked, with\n// zero changes to the user's own code. Idempotent: safe to run on every build.\n//\n// It only edits Patchstack-owned / auto-generated infra: the guard folder\n// (src/integrations/patchstack/), the generated Supabase client, and the framework server\n// entry. It never touches the user's routes or components. Best-effort — it must never fail a\n// build, so callers treat a thrown error as \"skip\".\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Guard templates ship next to the built CLI (dist/protect/templates).\nconst TEMPLATES = fileURLToPath(new URL('./protect/templates/', import.meta.url));\n\nconst CLIENT_TUNNEL = [\n '',\n \" // PATCHSTACK auto-guard: in the browser, tunnel Supabase traffic through the app's own\",\n ' // server guard (same-origin) so payloads are inspected before they reach Supabase.',\n \" if (typeof window !== 'undefined') {\",\n \" const target = typeof input === 'string' ? input : input instanceof Request ? input.url : String(input);\",\n \" const method = init?.method ?? (input instanceof Request ? input.method : 'GET');\",\n \" headers.set('x-ps-target', target);\",\n \" const guardUrl = new URL('/_patchstack/guard', window.location.origin).toString();\",\n ' return fetch(guardUrl, { ...init, method, headers });',\n ' }',\n '',\n].join('\\n');\n\nconst START_IMPORTS = [\n 'import { getRequest } from \"@tanstack/react-start/server\";',\n 'import { GUARD_PATH, handleGuardRequest } from \"@/integrations/patchstack/guard\";',\n].join('\\n');\n\nconst START_MIDDLEWARE = [\n '',\n '// Patchstack auto-guard: intercept the tunneled data traffic before anything else runs.',\n 'const patchstackGuard = createMiddleware().server(async ({ next }) => {',\n ' const request = getRequest();',\n ' if (request) {',\n ' const { pathname } = new URL(request.url);',\n ' if (pathname === GUARD_PATH) return handleGuardRequest(request);',\n ' }',\n ' return next();',\n '});',\n '',\n].join('\\n');\n\nconst PS_DIR_REL = 'src/integrations/patchstack';\n\nfunction log(msg: string): void {\n console.log(`patchstack protect: ${msg}`);\n}\n\n/** Returns true if this looks like a supported server-side app (TanStack Start + Supabase). */\nexport function detectSupportedStack(cwd: string): boolean {\n const pkgPath = join(cwd, 'package.json');\n if (!existsSync(pkgPath)) return false;\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n return (\n Boolean(deps['@tanstack/react-start']) &&\n existsSync(join(cwd, 'src/start.ts')) &&\n existsSync(join(cwd, 'src/integrations/supabase/client.ts'))\n );\n}\n\nexport function generateManifest(cwd: string): void {\n const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));\n const deps: Record<string, string> = { ...pkg.dependencies, ...pkg.devDependencies };\n const packages: Record<string, string> = {};\n for (const name of Object.keys(deps)) {\n let version = String(deps[name]).replace(/^[^\\d]*/, '');\n const installed = join(cwd, 'node_modules', name, 'package.json');\n if (existsSync(installed)) {\n try {\n version = JSON.parse(readFileSync(installed, 'utf8')).version;\n } catch {\n /* keep the range-derived version */\n }\n }\n packages[name] = version;\n }\n mkdirSync(join(cwd, PS_DIR_REL), { recursive: true });\n const body =\n '// Patchstack auto-guard manifest — generated from the lockfile.\\n' +\n '// Do not edit; regenerated by `patchstack-connect protect --manifest` (wired into prebuild).\\n' +\n 'export const manifest = ' +\n JSON.stringify({ packages }, null, 2) +\n ';\\n';\n writeFileSync(join(cwd, PS_DIR_REL, 'manifest.js'), body);\n log(`wrote manifest.js (${Object.keys(packages).length} packages)`);\n}\n\nfunction scaffold(cwd: string): void {\n const dst = join(cwd, PS_DIR_REL);\n mkdirSync(dst, { recursive: true });\n copyFileSync(join(TEMPLATES, 'engine.js'), join(dst, 'engine.js'));\n copyFileSync(join(TEMPLATES, 'engine.d.ts'), join(dst, 'engine.d.ts'));\n copyFileSync(join(TEMPLATES, 'manifest.d.ts'), join(dst, 'manifest.d.ts'));\n copyFileSync(join(TEMPLATES, 'rules.json'), join(dst, 'rules.json'));\n copyFileSync(join(TEMPLATES, 'guard.ts'), join(dst, 'guard.ts'));\n log('scaffolded engine.js (+types), rules.json, guard.ts');\n}\n\nfunction patchClient(cwd: string): void {\n const p = join(cwd, 'src/integrations/supabase/client.ts');\n let s = readFileSync(p, 'utf8');\n if (s.includes('x-ps-target')) {\n log('client.ts already wired');\n return;\n }\n const anchor = \"headers.set('apikey', supabaseKey);\";\n if (!s.includes(anchor)) {\n log('client.ts anchor not found (template changed?) — skipping client patch');\n return;\n }\n s = s.replace(anchor, anchor + '\\n' + CLIENT_TUNNEL);\n writeFileSync(p, s);\n log('patched client.ts (tunnel Supabase through the guard)');\n}\n\nfunction patchStart(cwd: string): void {\n const p = join(cwd, 'src/start.ts');\n let s = readFileSync(p, 'utf8');\n if (s.includes('patchstackGuard')) {\n log('start.ts already wired');\n return;\n }\n const importAnchor = 'import { createStart, createMiddleware } from \"@tanstack/react-start\";';\n const exportAnchor = 'export const startInstance';\n const rmAnchor = 'requestMiddleware: [';\n if (!s.includes(importAnchor) || !s.includes(exportAnchor) || !s.includes(rmAnchor)) {\n log('start.ts anchors not found (template changed?) — skipping start patch');\n return;\n }\n s = s.replace(importAnchor, importAnchor + '\\n' + START_IMPORTS);\n s = s.replace(exportAnchor, START_MIDDLEWARE + '\\n' + exportAnchor);\n s = s.replace(rmAnchor, rmAnchor + 'patchstackGuard, ');\n writeFileSync(p, s);\n log('patched start.ts (registered the guard as request middleware)');\n}\n\nfunction wirePrebuild(cwd: string): void {\n const p = join(cwd, 'package.json');\n const pkg = JSON.parse(readFileSync(p, 'utf8'));\n pkg.scripts = pkg.scripts || {};\n const step = 'patchstack-connect protect --manifest';\n const prebuild: string = pkg.scripts.prebuild || '';\n if (prebuild.includes(step)) {\n log('prebuild already refreshes the manifest');\n return;\n }\n pkg.scripts.prebuild = prebuild ? prebuild + ' && ' + step : step;\n writeFileSync(p, JSON.stringify(pkg, null, 2) + '\\n');\n log('wired manifest refresh into prebuild');\n}\n\n/** Full install (or manifest-only refresh). */\nexport function runProtect(cwd: string, opts: { manifestOnly?: boolean } = {}): void {\n if (opts.manifestOnly) {\n generateManifest(cwd);\n return;\n }\n if (!detectSupportedStack(cwd)) {\n log(\n 'runtime protection currently supports TanStack Start + Supabase apps; stack not detected — skipping.',\n );\n return;\n }\n scaffold(cwd);\n patchClient(cwd);\n patchStart(cwd);\n generateManifest(cwd);\n wirePrebuild(cwd);\n log('done — guard wired and always-on (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only.');\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","path","readFile","path","readFile","path","readFile","path","path","readFile","readFile","readFile","readFile","path","readFile","path","path","readFile","path","path","readFile","path","walk","existsSync","readFileSync","writeFileSync"]}
@@ -0,0 +1,57 @@
1
+ export type Action = "ALLOW" | "LOG" | "BLOCK" | "REDIRECT";
2
+
3
+ export declare const ACTIONS: {
4
+ ALLOW: "ALLOW";
5
+ LOG: "LOG";
6
+ BLOCK: "BLOCK";
7
+ REDIRECT: "REDIRECT";
8
+ };
9
+
10
+ /** A normalized snapshot of one request. The framework guard builds this. */
11
+ export interface RequestContext {
12
+ method: string;
13
+ url: string;
14
+ headers: Record<string, string>;
15
+ /** Parsed request payload; rule parameters read from here (e.g. "insert.title"). */
16
+ body: Record<string, unknown>;
17
+ ip?: string;
18
+ }
19
+
20
+ export interface RuleCondition {
21
+ parameter: string | string[];
22
+ match: { type: "inline_xss" | "contains" | "equals" | string; value?: unknown };
23
+ mutations?: string[];
24
+ }
25
+
26
+ /** Only apply the rule if the app has this package at a vulnerable version. */
27
+ export interface PackageCond {
28
+ package: string;
29
+ vulnerable_versions?: string[];
30
+ }
31
+
32
+ export interface Rule {
33
+ id: string;
34
+ title?: string;
35
+ vulnerability_id?: string;
36
+ package_cond?: PackageCond;
37
+ rule_v2: RuleCondition[];
38
+ }
39
+
40
+ /** The app's installed package list, so package_cond can gate. */
41
+ export interface Manifest {
42
+ packages: Record<string, string>;
43
+ }
44
+
45
+ export interface Verdict {
46
+ matched: boolean;
47
+ action: Action;
48
+ rule_id: string | null;
49
+ vulnerability_id: string | null;
50
+ package: string | null;
51
+ version: string | null;
52
+ explain: string[];
53
+ trace: unknown[];
54
+ }
55
+
56
+ export declare function evaluate(ctx: RequestContext, rules: Rule[], manifest?: Manifest): Verdict;
57
+ export declare function urldecode(value: string): string;
@@ -0,0 +1,137 @@
1
+ // @patchstack/protect — the runtime protection engine.
2
+ //
3
+ // One pure function: evaluate(ctx, rules, manifest) -> Verdict.
4
+ // No framework coupling — the framework adapter (the guard) builds `ctx` and acts on the
5
+ // Verdict. This is the offsite-frozen contract between Group 1 (engine) and Group 2 (surface).
6
+ //
7
+ // SCOPE OF THIS BUILD: implements the two demo-critical pieces from the prototype —
8
+ // - `inline_xss` (ps-node never implemented it; npm XSS attacks need it)
9
+ // - `package_cond` (the line between virtual patching and a dumb wall)
10
+ // plus `equals` / `contains` and the `urldecode` mutation. The full rule_v2 zoo
11
+ // (AND/OR, nested rules, whitelists, the ~15 other match types) lands when the
12
+ // ps-node RuleEngine is ported in — this file keeps the same signature so that swap
13
+ // is drop-in.
14
+
15
+ /** @type {{ALLOW:'ALLOW',LOG:'LOG',BLOCK:'BLOCK',REDIRECT:'REDIRECT'}} */
16
+ export const ACTIONS = { ALLOW: "ALLOW", LOG: "LOG", BLOCK: "BLOCK", REDIRECT: "REDIRECT" };
17
+
18
+ /** Iteratively URL-decode (catches %2527 -> %27 -> '). Safe on malformed input. */
19
+ export function urldecode(value) {
20
+ let out = String(value);
21
+ for (let i = 0; i < 5; i++) {
22
+ let next;
23
+ try {
24
+ next = decodeURIComponent(out.replace(/\+/g, " "));
25
+ } catch {
26
+ break;
27
+ }
28
+ if (next === out) break;
29
+ out = next;
30
+ }
31
+ return out;
32
+ }
33
+
34
+ const MUTATIONS = { urldecode };
35
+
36
+ function applyMutations(value, mutations) {
37
+ let v = String(value ?? "");
38
+ for (const m of mutations ?? []) {
39
+ if (MUTATIONS[m]) v = MUTATIONS[m](v);
40
+ }
41
+ return v;
42
+ }
43
+
44
+ // Heuristic script-injection detector. Deliberately simple and readable for the demo;
45
+ // the ported engine replaces this with the audited ps-node inline_xss implementation.
46
+ const XSS_PATTERNS = [
47
+ /<\s*script\b/i,
48
+ /<\s*\/\s*script\s*>/i,
49
+ /\bon\w+\s*=/i, // onerror=, onload=, ...
50
+ /javascript\s*:/i,
51
+ /<\s*img\b[^>]*\bon\w+\s*=/i,
52
+ /<\s*svg\b[^>]*\bon\w+\s*=/i,
53
+ /\bdata\s*:\s*text\/html/i,
54
+ ];
55
+
56
+ function looksLikeInlineXss(value) {
57
+ const v = String(value ?? "");
58
+ return XSS_PATTERNS.some((re) => re.test(v));
59
+ }
60
+
61
+ // Resolve a rule parameter like "insert.title" or "body.content" against ctx.
62
+ // The guard normalizes framework specifics into ctx.body; parameters read from there.
63
+ function resolveParameter(ctx, parameter) {
64
+ const [, ...rest] = String(parameter).split(".");
65
+ const key = rest.length ? rest.join(".") : parameter;
66
+ const body = ctx?.body ?? {};
67
+ if (key in body) return body[key];
68
+ // shallow scan for nested payloads (e.g. { record: { title } })
69
+ for (const v of Object.values(body)) {
70
+ if (v && typeof v === "object" && key in v) return v[key];
71
+ }
72
+ return undefined;
73
+ }
74
+
75
+ function matchCondition(ctx, cond) {
76
+ const params = Array.isArray(cond.parameter) ? cond.parameter : [cond.parameter];
77
+ for (const param of params) {
78
+ const raw = resolveParameter(ctx, param);
79
+ if (raw == null) continue;
80
+ const value = applyMutations(raw, cond.mutations);
81
+ const type = cond.match?.type;
82
+ let hit = false;
83
+ if (type === "inline_xss") hit = looksLikeInlineXss(value);
84
+ else if (type === "contains") hit = value.includes(cond.match.value);
85
+ else if (type === "equals") hit = value === cond.match.value;
86
+ if (hit) {
87
+ return { param, type, value };
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ // package_cond: only fire if the app actually has the vulnerable package@version.
94
+ // If no manifest is supplied, fail-open on the gate (assume present) — detection already
95
+ // flagged it; the guard can tighten this once it passes the real manifest.
96
+ function packageCondSatisfied(pkgCond, manifest) {
97
+ if (!pkgCond) return true;
98
+ if (!manifest || !manifest.packages) return true;
99
+ const installed = manifest.packages[pkgCond.package];
100
+ if (installed == null) return false;
101
+ if (!pkgCond.vulnerable_versions) return true;
102
+ return pkgCond.vulnerable_versions.includes(installed);
103
+ }
104
+
105
+ /**
106
+ * Evaluate one request against the rule set.
107
+ * @param {import('./index.js').RequestContext} ctx
108
+ * @param {import('./index.js').Rule[]} rules
109
+ * @param {import('./index.js').Manifest} [manifest]
110
+ * @returns {import('./index.js').Verdict}
111
+ */
112
+ export function evaluate(ctx, rules, manifest) {
113
+ const trace = [];
114
+ for (const rule of rules ?? []) {
115
+ if (!packageCondSatisfied(rule.package_cond, manifest)) {
116
+ trace.push({ rule_id: rule.id, skipped: "package_cond not satisfied" });
117
+ continue;
118
+ }
119
+ for (const cond of rule.rule_v2 ?? []) {
120
+ const m = matchCondition(ctx, cond);
121
+ if (m) {
122
+ return {
123
+ matched: true,
124
+ action: ACTIONS.BLOCK,
125
+ rule_id: rule.id,
126
+ vulnerability_id: rule.vulnerability_id ?? null,
127
+ package: rule.package_cond?.package ?? null,
128
+ version: manifest?.packages?.[rule.package_cond?.package] ?? null,
129
+ explain: [`${m.param} matched ${m.type}${cond.mutations?.length ? ` after ${cond.mutations.join("+")}` : ""}`],
130
+ trace,
131
+ };
132
+ }
133
+ }
134
+ trace.push({ rule_id: rule.id, matched: false });
135
+ }
136
+ return { matched: false, action: ACTIONS.ALLOW, rule_id: null, vulnerability_id: null, package: null, version: null, explain: [], trace };
137
+ }
@@ -0,0 +1,102 @@
1
+ // Patchstack auto-guard — installed automatically by "add Patchstack"; the vibe coder never
2
+ // touches this. It runs in the app's own server (the Cloudflare Worker). The patched Supabase
3
+ // client tunnels every browser data call here; we inspect the payload against the rules, then
4
+ // either block it (virtual patch) or forward it to real Supabase.
5
+ import { evaluate, type Rule } from "./engine.js";
6
+ import rulesData from "./rules.json";
7
+ import { manifest } from "./manifest.js";
8
+
9
+ export const GUARD_PATH = "/_patchstack/guard";
10
+
11
+ // Headers that must not be copied verbatim when we re-emit the upstream response.
12
+ const HOP_BY_HOP = new Set(["content-encoding", "content-length", "transfer-encoding", "connection"]);
13
+
14
+ function mode(): "dry-run" | "block" {
15
+ // Always-on: protection blocks by default. Only an explicit PATCHSTACK_MODE=dry-run
16
+ // downgrades to log-only (for testing) — there is no silent dry-run.
17
+ return process.env.PATCHSTACK_MODE === "dry-run" ? "dry-run" : "block";
18
+ }
19
+
20
+ export async function handleGuardRequest(request: Request): Promise<Response> {
21
+ const target = request.headers.get("x-ps-target");
22
+ if (!target) return new Response("patchstack: missing x-ps-target", { status: 400 });
23
+
24
+ // SSRF guard: the client names the target, but we only ever forward to the app's own
25
+ // Supabase project (origin known server-side). Anything else — internal hosts, cloud
26
+ // metadata, a different scheme — is rejected before we make any outbound request.
27
+ const supabaseUrl = process.env.SUPABASE_URL;
28
+ let targetUrl: URL;
29
+ try {
30
+ targetUrl = new URL(target);
31
+ } catch {
32
+ return new Response("patchstack: invalid target", { status: 400 });
33
+ }
34
+ if (!supabaseUrl || targetUrl.protocol !== "https:" || targetUrl.origin !== new URL(supabaseUrl).origin) {
35
+ return new Response("patchstack: target not allowed", { status: 403 });
36
+ }
37
+
38
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
39
+ const bodyText = hasBody ? await request.text() : "";
40
+
41
+ let record: Record<string, unknown> = {};
42
+ if (bodyText) {
43
+ try {
44
+ const parsed = JSON.parse(bodyText);
45
+ record = Array.isArray(parsed) ? (parsed[0] ?? {}) : parsed;
46
+ } catch {
47
+ // not JSON — leave record empty; the rule simply won't match
48
+ }
49
+ }
50
+
51
+ const ctx = {
52
+ method: request.method,
53
+ url: target,
54
+ headers: Object.fromEntries(request.headers),
55
+ // expose the record under a few shapes so rule parameters like "insert.title" resolve
56
+ body: { ...record, insert: record, update: record },
57
+ ip: request.headers.get("x-forwarded-for") ?? "",
58
+ };
59
+
60
+ const t0 = performance.now();
61
+ const verdict = evaluate(ctx, (rulesData as { firewall: Rule[] }).firewall, manifest);
62
+ const latencyMs = Math.round((performance.now() - t0) * 100) / 100;
63
+
64
+ // Always log the flag (this is the "dry-run sees it too" behavior).
65
+ const decision = verdict.matched ? `MATCH ${verdict.rule_id}` : "clean";
66
+ console.log(`[patchstack] ${request.method} ${new URL(target).pathname} -> ${decision} · mode=${mode()}`);
67
+
68
+ if (verdict.matched && mode() === "block") {
69
+ const receipt = {
70
+ blocked: true,
71
+ rule_id: verdict.rule_id,
72
+ vulnerability_id: verdict.vulnerability_id,
73
+ package: verdict.package,
74
+ version: verdict.version,
75
+ matched_conditions: verdict.explain,
76
+ latency_ms: latencyMs,
77
+ };
78
+ console.log("[patchstack] BLOCKED", JSON.stringify(receipt));
79
+ return new Response(JSON.stringify(receipt), {
80
+ status: 403,
81
+ headers: { "content-type": "application/json", "x-patchstack": "blocked" },
82
+ });
83
+ }
84
+
85
+ // Forward to real Supabase, server-side.
86
+ const forwardHeaders = new Headers(request.headers);
87
+ forwardHeaders.delete("x-ps-target");
88
+ forwardHeaders.delete("host");
89
+ const upstream = await fetch(target, {
90
+ method: request.method,
91
+ headers: forwardHeaders,
92
+ body: hasBody ? bodyText : undefined,
93
+ redirect: "manual",
94
+ });
95
+
96
+ const outHeaders = new Headers();
97
+ upstream.headers.forEach((value, key) => {
98
+ if (!HOP_BY_HOP.has(key.toLowerCase())) outHeaders.set(key, value);
99
+ });
100
+ const buf = await upstream.arrayBuffer();
101
+ return new Response(buf, { status: upstream.status, statusText: upstream.statusText, headers: outHeaders });
102
+ }
@@ -0,0 +1 @@
1
+ export declare const manifest: { packages: Record<string, string> };
@@ -0,0 +1,22 @@
1
+ {
2
+ "_comment": "DEMO SCAFFOLD — static stub; the real per-site rules endpoint does not exist in saas yet. CVE-2022-21681 is a real marked advisory affecting <4.0.10 (demo installs 4.0.0). The demo uses an XSS-shaped payload for visual clarity; exact payload<->CVE alignment is pinned with Group 1's corpus.",
3
+ "firewall": [
4
+ {
5
+ "id": "rm-npm-0001",
6
+ "title": "Block stored XSS via vulnerable markdown renderer (marked)",
7
+ "vulnerability_id": "CVE-2022-21681",
8
+ "package_cond": {
9
+ "package": "marked",
10
+ "vulnerable_versions": ["4.0.0"]
11
+ },
12
+ "rule_v2": [
13
+ {
14
+ "parameter": ["insert.title", "body.title", "update.title"],
15
+ "mutations": ["urldecode"],
16
+ "match": { "type": "inline_xss" }
17
+ }
18
+ ]
19
+ }
20
+ ],
21
+ "whitelists": []
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patchstack/connect",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Patchstack connector for JavaScript applications. Scans your lockfile and reports installed packages to Patchstack for vulnerability monitoring.",
5
5
  "keywords": [
6
6
  "patchstack",
@@ -32,12 +32,13 @@
32
32
  "LICENSE"
33
33
  ],
34
34
  "scripts": {
35
- "build": "tsup",
35
+ "build": "tsup && node scripts/copy-protect-templates.mjs",
36
36
  "dev": "tsup --watch",
37
37
  "test": "vitest run",
38
+ "test:manifest": "bun scripts/test-manifest.ts",
38
39
  "test:watch": "vitest",
39
40
  "typecheck": "tsc --noEmit",
40
- "prepare": "tsup",
41
+ "prepare": "npm run build",
41
42
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
42
43
  },
43
44
  "engines": {