/** * Live smoke + fixture capture for the g7 North-American auction connectors (works before the registry is * rebuilt: the connector's own meta.json is used). * Usage: pnpm tsx connectors/api/_g7-auctions-na-lib/smoke.ts / [--limit N] [--seed URL]... [--capture name] [--trim N] [--mode probe|incremental|backfill] */ import { readFileSync } from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { ConnectorMetaSchema, createCrawlContext, createRouter, DomainsFileSchema, setDomains, type ConnectorFactory, type ConnectorMeta } from '@rareindex/connectors'; import { saveFixture } from '@rareindex/connectors/testing'; import { childLogger } from '@rareindex/shared'; // Parallel connector agents may leave an invalid fragment in connectors/domains.d/ which would make // loadDomains() throw for everybody; pre-seed the policy cache from domains.json + this group's fragment. { const base = JSON.parse(readFileSync(path.resolve(process.cwd(), 'connectors/domains.json'), 'utf8')) as { version?: string; defaults?: unknown; domains?: Record }; const frag = JSON.parse(readFileSync(path.resolve(process.cwd(), 'connectors/domains.d/g7-auctions-na.json'), 'utf8')) as { domains?: Record }; setDomains(DomainsFileSchema.parse({ ...base, domains: { ...(base.domains ?? {}), ...(frag.domains ?? {}) } })); } const [modulePath, ...rest] = process.argv.slice(2); if (!modulePath) throw new Error('usage: smoke.ts / [--limit N] [--seed URL] [--capture name] [--trim N] [--mode m]'); const opt = { limit: 3, seeds: [] as string[], capture: null as string | null, trim: 12, mode: 'probe' as 'probe' | 'incremental' | 'backfill', maxRaw: 4 }; for (let i = 0; i < rest.length; i++) { const a = rest[i]!; if (a === '--limit') opt.limit = Number(rest[++i]); else if (a === '--seed') opt.seeds.push(rest[++i]!); else if (a === '--capture') opt.capture = rest[++i]!; else if (a === '--trim') opt.trim = Number(rest[++i]); else if (a === '--mode') opt.mode = rest[++i] as typeof opt.mode; else if (a === '--max-raw') opt.maxRaw = Number(rest[++i]); } const dir = path.resolve(process.cwd(), 'connectors', modulePath); const meta = ConnectorMetaSchema.parse(JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8'))); const factory = (await import(pathToFileURL(path.join(dir, 'index.ts')).href)).default as (meta: ConnectorMeta) => ReturnType; const connector = await factory(meta); const router = createRouter({ firecrawlApiKey: process.env.FIRECRAWL_API_KEY, scrapflyApiKey: process.env.SCRAPFLY_API_KEY }); const ctx = createCrawlContext({ router, meta, options: { mode: opt.mode, limit: opt.limit, ...(opt.seeds.length ? { seeds: opt.seeds } : {}) }, log: childLogger({ connector: meta.id, smoke: true }), onCursor: async (c) => console.log('cursor →', JSON.stringify(c).slice(0, 300)) }); /** Trim list-shaped payloads so fixtures stay small (keeps every scalar field, first N items). */ function trim(payload: unknown): unknown { if (!payload || typeof payload !== 'object') return payload; const p = { ...(payload as Record) }; for (const k of Object.keys(p)) if (Array.isArray(p[k]) && (p[k] as unknown[]).length > opt.trim) p[k] = (p[k] as unknown[]).slice(0, opt.trim); return p; } let rawCount = 0; let total = 0; let captured = 0; const kinds: Record = {}; for await (const raw of connector.crawl(ctx)) { rawCount++; const rawLike = { ...raw, externalId: raw.externalId ?? null, fetchedAt: raw.fetchedAt ?? new Date() }; const records = await connector.normalize(rawLike); total += records.length; for (const r of records) kinds[r.kind] = (kinds[r.kind] ?? 0) + 1; console.log(`raw#${rawCount} ${raw.kind} ${raw.url} → ${records.length} records`); for (const r of records.slice(0, 2)) console.log(JSON.stringify(r).slice(0, 900)); if (opt.capture && captured < 3 && records.length) { const trimmed = trim(JSON.parse(JSON.stringify(rawLike.payload))); const recs = await connector.normalize({ ...rawLike, payload: trimmed }); const first = recs[0]!; const name = `${opt.capture}-${captured + 1}`; saveFixture(meta.id, name, { raw: { ...rawLike, payload: trimmed }, expect: { minCount: 1, kinds: [...new Set(recs.map((r) => r.kind))], first: { kind: first.kind, ...('auctionHouse' in first && first.auctionHouse ? { auctionHouse: first.auctionHouse } : {}), ...('currency' in first && first.currency ? { currency: first.currency } : {}) } }, note: `Captured live by connectors/api/_g7-auctions-na-lib/smoke.ts on ${new Date().toISOString().slice(0, 10)} from ${raw.url} (payload lists trimmed to ${opt.trim} items; ${recs.length} records).`, }); captured++; console.log(` saved fixture data/fixtures/${meta.id}/${name}.json`); } if (rawCount >= opt.maxRaw) break; } console.log(JSON.stringify({ rawCount, totalNormalized: total, kinds, engineStats: ctx.engineStats, anomalies: ctx.anomalies }, null, 1));