@hasna/testers 0.0.48 → 0.0.50

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/dist/cli/index.js CHANGED
@@ -59150,6 +59150,511 @@ var init_openapi_import = __esm(() => {
59150
59150
  init_api_checks();
59151
59151
  });
59152
59152
 
59153
+ // src/lib/next-route-inventory.ts
59154
+ var exports_next_route_inventory = {};
59155
+ __export(exports_next_route_inventory, {
59156
+ scenarioInputForNextRoute: () => scenarioInputForNextRoute,
59157
+ importNextRouteInventory: () => importNextRouteInventory,
59158
+ discoverNextRouteInventory: () => discoverNextRouteInventory
59159
+ });
59160
+ import { existsSync as existsSync18, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
59161
+ import { basename as basename3, join as join20, relative as relative4, resolve as resolve3 } from "path";
59162
+ function discoverNextRouteInventory(options) {
59163
+ const rootDir = resolve3(options.rootDir);
59164
+ const appDir = resolveAppDir(rootDir, options.appDir);
59165
+ const includePages = options.includePages !== false;
59166
+ const includeApi = options.includeApi !== false;
59167
+ const files = walkRouteFiles(appDir);
59168
+ const items = files.map((file) => routeItemFromFile(rootDir, appDir, file)).filter((item) => Boolean(item)).filter((item) => item.kind === "page" ? includePages : includeApi).sort((a2, b2) => `${a2.kind}:${a2.routePath}:${a2.file}`.localeCompare(`${b2.kind}:${b2.routePath}:${b2.file}`)).slice(0, options.limit);
59169
+ const categories = {};
59170
+ for (const item of items) {
59171
+ categories[item.category] = (categories[item.category] ?? 0) + 1;
59172
+ }
59173
+ return {
59174
+ rootDir,
59175
+ appDir,
59176
+ total: items.length,
59177
+ pages: items.filter((item) => item.kind === "page").length,
59178
+ apiRoutes: items.filter((item) => item.kind === "api").length,
59179
+ dynamic: items.filter((item) => item.dynamic).length,
59180
+ categories,
59181
+ items
59182
+ };
59183
+ }
59184
+ function scenarioInputForNextRoute(item, projectId) {
59185
+ const label = item.kind === "page" ? "page" : "API route";
59186
+ const methodList = item.methods.length > 0 ? item.methods.join(", ") : "discovered methods";
59187
+ const fixtureStep = item.fixtureParams.length > 0 ? `Bind dynamic fixture values for ${item.fixtureParams.map((name) => `:${name}`).join(", ")} before running route actions.` : undefined;
59188
+ const dynamicStep = item.dynamic ? "Substitute dynamic path parameters with valid fixture values from the target org before opening or calling the route." : undefined;
59189
+ const actionSteps = item.actions.slice(0, 16).map(formatActionStep);
59190
+ const pageSteps = [
59191
+ fixtureStep,
59192
+ dynamicStep,
59193
+ `Open the Next.js ${label} ${item.routePath}.`,
59194
+ "Wait for the route to finish loading and verify it does not show a blank shell, framework error page, or unexpected auth loop.",
59195
+ ...actionSteps.length > 0 ? actionSteps : ["Exercise visible primary navigation, tabs, filters, dialogs, forms, and safe buttons on this route."],
59196
+ "Verify the route stays within the expected org/workspace context and does not emit console errors."
59197
+ ].filter(Boolean);
59198
+ const apiSteps = [
59199
+ fixtureStep,
59200
+ dynamicStep,
59201
+ `Call the ${methodList} handler(s) for ${item.routePath} using safe fixture data.`,
59202
+ ...actionSteps,
59203
+ "Verify expected authentication, authorization, validation, and tenant isolation behavior.",
59204
+ "For mutating methods, use harmless test payloads and confirm the response does not create cross-org side effects.",
59205
+ "Verify response status, JSON shape, and error messages are stable and regression-safe."
59206
+ ].filter(Boolean);
59207
+ return {
59208
+ name: `Next ${label}: ${item.routePath}`,
59209
+ description: `Source-discovered ${label} from ${item.file}. Verify route behavior and regressions for ${item.category}.`,
59210
+ steps: item.kind === "page" ? pageSteps : apiSteps,
59211
+ tags: item.tags,
59212
+ priority: item.priority,
59213
+ targetPath: item.routePath,
59214
+ requiresAuth: item.requiresAuth,
59215
+ assertions: item.kind === "page" ? SAFE_PAGE_ASSERTIONS : [],
59216
+ metadata: {
59217
+ source: "next-route-inventory",
59218
+ routeFile: item.file,
59219
+ routeKind: item.kind,
59220
+ category: item.category,
59221
+ methods: item.methods,
59222
+ dynamic: item.dynamic,
59223
+ fixtureParams: item.fixtureParams,
59224
+ actions: item.actions,
59225
+ actionCount: item.actions.length,
59226
+ groups: item.groups
59227
+ },
59228
+ projectId
59229
+ };
59230
+ }
59231
+ function importNextRouteInventory(options) {
59232
+ const inventory = discoverNextRouteInventory(options);
59233
+ let created = 0;
59234
+ let updated = 0;
59235
+ let deduped = 0;
59236
+ const scenarios = [];
59237
+ const workflows = [];
59238
+ if (options.createScenarios) {
59239
+ for (const item of inventory.items) {
59240
+ const result = upsertScenario(scenarioInputForNextRoute(item, options.projectId));
59241
+ scenarios.push(result.scenario);
59242
+ if (result.action === "created")
59243
+ created++;
59244
+ else if (result.action === "updated")
59245
+ updated++;
59246
+ else
59247
+ deduped++;
59248
+ }
59249
+ }
59250
+ if (options.createWorkflows) {
59251
+ workflows.push(...upsertRouteInventoryWorkflows(inventory, options));
59252
+ }
59253
+ return { inventory, created, updated, deduped, scenarios, workflows };
59254
+ }
59255
+ function upsertRouteInventoryWorkflows(inventory, options) {
59256
+ const workflows = [];
59257
+ const categories = Object.keys(inventory.categories).sort();
59258
+ for (const category of categories) {
59259
+ const kinds = new Set(inventory.items.filter((item) => item.category === category).map((item) => item.kind));
59260
+ for (const kind of kinds) {
59261
+ const name = `Next route inventory ${category} ${kind}`;
59262
+ const scenarioTags = ["next-route", `area:${category}`, `route:${kind}`];
59263
+ const execution = {
59264
+ target: options.workflowTarget ?? "sandbox",
59265
+ provider: options.workflowProvider,
59266
+ sandboxCleanup: "delete",
59267
+ sandboxSyncStrategy: "rsync",
59268
+ ...options.workflowExecution
59269
+ };
59270
+ const existing = listTestingWorkflows({ projectId: options.projectId, enabled: undefined }).find((workflow) => workflow.name === name);
59271
+ const input = {
59272
+ name,
59273
+ description: `Source-discovered Next.js ${kind} coverage for ${category} routes.`,
59274
+ projectId: options.projectId,
59275
+ scenarioFilter: { tags: scenarioTags },
59276
+ execution
59277
+ };
59278
+ workflows.push(existing ? updateTestingWorkflow(existing.id, input) : createTestingWorkflow(input));
59279
+ }
59280
+ }
59281
+ return workflows;
59282
+ }
59283
+ function resolveAppDir(rootDir, appDir) {
59284
+ const candidates = appDir ? [resolve3(rootDir, appDir)] : [
59285
+ join20(rootDir, "packages", "web", "app"),
59286
+ join20(rootDir, "app"),
59287
+ rootDir
59288
+ ];
59289
+ for (const candidate of candidates) {
59290
+ if (existsSync18(candidate) && statSync4(candidate).isDirectory())
59291
+ return candidate;
59292
+ }
59293
+ throw new Error(`Next.js app directory not found under ${rootDir}`);
59294
+ }
59295
+ function walkRouteFiles(appDir) {
59296
+ const files = [];
59297
+ function walk(dir) {
59298
+ for (const entry of readdirSync6(dir)) {
59299
+ if (WALK_EXCLUDES.has(entry))
59300
+ continue;
59301
+ const fullPath = join20(dir, entry);
59302
+ const stat = statSync4(fullPath);
59303
+ if (stat.isDirectory()) {
59304
+ walk(fullPath);
59305
+ } else if (ROUTE_FILE_NAMES.has(entry)) {
59306
+ files.push(fullPath);
59307
+ }
59308
+ }
59309
+ }
59310
+ walk(appDir);
59311
+ return files;
59312
+ }
59313
+ function routeItemFromFile(rootDir, appDir, file) {
59314
+ const fileName = basename3(file);
59315
+ const kind = fileName.startsWith("page.") ? "page" : "api";
59316
+ const relativeFile = relative4(rootDir, file);
59317
+ const appRelative = relative4(appDir, file).split(/[\\/]/);
59318
+ const routeSegments = appRelative.slice(0, -1);
59319
+ const groups = routeSegments.filter((segment) => segment.startsWith("(") && segment.endsWith(")")).map((segment) => segment.slice(1, -1));
59320
+ const pathSegments = routeSegments.filter((segment) => !segment.startsWith("(")).filter((segment) => !segment.startsWith("@")).map(normalizeRouteSegment).filter(Boolean);
59321
+ const routePath = `/${pathSegments.join("/")}`.replace(/\/+/g, "/");
59322
+ const normalizedRoutePath = routePath === "/" ? "/" : routePath.replace(/\/$/, "");
59323
+ const sources = collectRouteSources(rootDir, file);
59324
+ const primarySource = sources[0]?.source ?? readFileSync9(file, "utf8");
59325
+ const methods = kind === "api" ? extractRouteMethods(primarySource) : [];
59326
+ const category = classifyRoute(normalizedRoutePath, groups, relativeFile);
59327
+ const dynamic = routeSegments.some((segment) => segment.includes("["));
59328
+ const fixtureParams = extractFixtureParams(normalizedRoutePath);
59329
+ const actions = kind === "api" ? methods.map((method) => ({
59330
+ kind: "api-method",
59331
+ label: method,
59332
+ target: normalizedRoutePath,
59333
+ sourceFile: relativeFile,
59334
+ destructive: isDestructiveAction(method, normalizedRoutePath),
59335
+ requiresFixture: fixtureParams.length > 0
59336
+ })) : extractPageActions(rootDir, sources, fixtureParams);
59337
+ const requiresAuth = inferRequiresAuth(normalizedRoutePath, groups, kind);
59338
+ return {
59339
+ kind,
59340
+ routePath: normalizedRoutePath,
59341
+ file: relativeFile,
59342
+ category,
59343
+ groups,
59344
+ methods,
59345
+ dynamic,
59346
+ requiresAuth,
59347
+ fixtureParams,
59348
+ actions,
59349
+ tags: tagsForRoute({ kind, routePath: normalizedRoutePath, category, groups, dynamic, requiresAuth }),
59350
+ priority: priorityForRoute(normalizedRoutePath, category, kind)
59351
+ };
59352
+ }
59353
+ function normalizeRouteSegment(segment) {
59354
+ if (segment.startsWith("[[...") && segment.endsWith("]]")) {
59355
+ return `:${segment.slice(5, -2)}*?`;
59356
+ }
59357
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
59358
+ return `:${segment.slice(4, -1)}*`;
59359
+ }
59360
+ if (segment.startsWith("[") && segment.endsWith("]")) {
59361
+ return `:${segment.slice(1, -1)}`;
59362
+ }
59363
+ return segment;
59364
+ }
59365
+ function extractRouteMethods(source) {
59366
+ const methods = new Set;
59367
+ const pattern = /\b(?:export\s+)?(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b|\bexport\s+const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;
59368
+ for (const match of source.matchAll(pattern)) {
59369
+ const method = match[1] ?? match[2];
59370
+ if (method)
59371
+ methods.add(method);
59372
+ }
59373
+ return [...methods].sort();
59374
+ }
59375
+ function collectRouteSources(rootDir, entryFile) {
59376
+ const seen = new Set;
59377
+ const sources = [];
59378
+ function visit(file, depth) {
59379
+ if (seen.has(file) || sources.length >= IMPORT_SCAN_LIMIT)
59380
+ return;
59381
+ if (!existsSync18(file) || !statSync4(file).isFile())
59382
+ return;
59383
+ seen.add(file);
59384
+ const source = readFileSync9(file, "utf8");
59385
+ sources.push({ file: relative4(rootDir, file), source });
59386
+ if (depth >= IMPORT_SCAN_DEPTH)
59387
+ return;
59388
+ for (const specifier of localImportSpecifiers(source)) {
59389
+ const resolved = resolveImportFile(file, specifier);
59390
+ if (resolved)
59391
+ visit(resolved, depth + 1);
59392
+ }
59393
+ }
59394
+ visit(entryFile, 0);
59395
+ return sources;
59396
+ }
59397
+ function localImportSpecifiers(source) {
59398
+ const specifiers = [];
59399
+ const pattern = /\bimport\b(?:[\s\S]*?\bfrom\s*)?["'](\.{1,2}\/[^"']+)["']|\bimport\(\s*["'](\.{1,2}\/[^"']+)["']\s*\)/g;
59400
+ for (const match of source.matchAll(pattern)) {
59401
+ const specifier = match[1] ?? match[2];
59402
+ if (specifier)
59403
+ specifiers.push(specifier);
59404
+ }
59405
+ return specifiers;
59406
+ }
59407
+ function resolveImportFile(fromFile, specifier) {
59408
+ const base = resolve3(join20(fromFile, ".."), specifier);
59409
+ const candidates = [
59410
+ base,
59411
+ ...SOURCE_EXTENSIONS.map((ext) => `${base}${ext}`),
59412
+ ...SOURCE_EXTENSIONS.map((ext) => join20(base, `index${ext}`))
59413
+ ];
59414
+ return candidates.find((candidate) => existsSync18(candidate) && statSync4(candidate).isFile()) ?? null;
59415
+ }
59416
+ function extractPageActions(rootDir, sources, fixtureParams) {
59417
+ const actions = [];
59418
+ for (const source of sources) {
59419
+ actions.push(...extractLinkedActions(source, fixtureParams));
59420
+ actions.push(...extractButtonActions(source, fixtureParams));
59421
+ actions.push(...extractFormActions(source, fixtureParams));
59422
+ actions.push(...extractInputActions(source, fixtureParams));
59423
+ }
59424
+ const deduped = new Map;
59425
+ for (const action of actions) {
59426
+ const key = [
59427
+ action.kind,
59428
+ normalizeLabel(action.label),
59429
+ action.target ?? "",
59430
+ action.sourceFile
59431
+ ].join("|");
59432
+ if (!deduped.has(key))
59433
+ deduped.set(key, action);
59434
+ }
59435
+ return [...deduped.values()].sort((a2, b2) => `${a2.kind}:${a2.label}:${a2.target ?? ""}`.localeCompare(`${b2.kind}:${b2.label}:${b2.target ?? ""}`)).slice(0, 40).map((action) => ({ ...action, sourceFile: relative4(rootDir, resolve3(rootDir, action.sourceFile)) }));
59436
+ }
59437
+ function extractLinkedActions(source, fixtureParams) {
59438
+ const actions = [];
59439
+ const pattern = /<(Link|a)\b([^>]*)>([\s\S]*?)<\/\1>/g;
59440
+ for (const match of source.source.matchAll(pattern)) {
59441
+ const attrs = match[2] ?? "";
59442
+ const body = match[3] ?? "";
59443
+ const href = attributeValue(attrs, "href");
59444
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "title"), textFromJsx(body), href);
59445
+ if (!label)
59446
+ continue;
59447
+ actions.push({
59448
+ kind: "link",
59449
+ label: clamp(label),
59450
+ target: href ? clamp(href, 180) : undefined,
59451
+ sourceFile: source.file,
59452
+ destructive: isDestructiveAction(label, href ?? ""),
59453
+ requiresFixture: requiresFixture(href ?? "", fixtureParams)
59454
+ });
59455
+ }
59456
+ return actions;
59457
+ }
59458
+ function extractButtonActions(source, fixtureParams) {
59459
+ const actions = [];
59460
+ const pattern = /<(button|Button|IconButton|DropdownMenuItem|CommandItem|SelectItem|TabsTrigger)\b([^>]*?)(?:>([\s\S]*?)<\/\1>|\/>)/g;
59461
+ for (const match of source.source.matchAll(pattern)) {
59462
+ const attrs = match[2] ?? "";
59463
+ const body = match[3] ?? "";
59464
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "title"), attributeValue(attrs, "data-testid"), textFromJsx(body), attributeValue(attrs, "value"));
59465
+ if (!label)
59466
+ continue;
59467
+ const target = attributeValue(attrs, "href") ?? attributeValue(attrs, "data-testid");
59468
+ actions.push({
59469
+ kind: "button",
59470
+ label: clamp(label),
59471
+ target: target ? clamp(target, 180) : undefined,
59472
+ sourceFile: source.file,
59473
+ destructive: isDestructiveAction(label, attrs),
59474
+ requiresFixture: requiresFixture(`${label} ${target ?? ""}`, fixtureParams)
59475
+ });
59476
+ }
59477
+ return actions;
59478
+ }
59479
+ function extractFormActions(source, fixtureParams) {
59480
+ const actions = [];
59481
+ const pattern = /<form\b([^>]*)>/g;
59482
+ for (const match of source.source.matchAll(pattern)) {
59483
+ const attrs = match[1] ?? "";
59484
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "name"), attributeValue(attrs, "data-testid"), attributeValue(attrs, "action"), `form in ${source.file}`);
59485
+ const target = attributeValue(attrs, "action");
59486
+ actions.push({
59487
+ kind: "form",
59488
+ label: clamp(label),
59489
+ target: target ? clamp(target, 180) : undefined,
59490
+ sourceFile: source.file,
59491
+ destructive: isDestructiveAction(label, attrs),
59492
+ requiresFixture: requiresFixture(`${label} ${target ?? ""}`, fixtureParams)
59493
+ });
59494
+ }
59495
+ return actions;
59496
+ }
59497
+ function extractInputActions(source, fixtureParams) {
59498
+ const actions = [];
59499
+ const pattern = /<(input|Input|Textarea|Select|Combobox)\b([^>]*?)(?:\/>|>)/g;
59500
+ for (const match of source.source.matchAll(pattern)) {
59501
+ const attrs = match[2] ?? "";
59502
+ const label = firstNonEmpty(attributeValue(attrs, "aria-label"), attributeValue(attrs, "placeholder"), attributeValue(attrs, "name"), attributeValue(attrs, "data-testid"));
59503
+ if (!label)
59504
+ continue;
59505
+ actions.push({
59506
+ kind: "input",
59507
+ label: clamp(label),
59508
+ target: attributeValue(attrs, "name") ?? attributeValue(attrs, "data-testid") ?? undefined,
59509
+ sourceFile: source.file,
59510
+ destructive: false,
59511
+ requiresFixture: requiresFixture(label, fixtureParams)
59512
+ });
59513
+ }
59514
+ return actions;
59515
+ }
59516
+ function attributeValue(attrs, name) {
59517
+ const pattern = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|\\{\\s*["']([^"']+)["']\\s*\\})`, "i");
59518
+ const match = attrs.match(pattern);
59519
+ const value = match?.[1] ?? match?.[2] ?? match?.[3];
59520
+ return value?.trim() || undefined;
59521
+ }
59522
+ function textFromJsx(value) {
59523
+ const text = value.replace(/<[^>]+>/g, " ").replace(/\{[^}]*\}/g, " ").replace(/\s+/g, " ").trim();
59524
+ return text || undefined;
59525
+ }
59526
+ function firstNonEmpty(...values) {
59527
+ return values.find((value) => value && value.trim())?.trim() ?? "";
59528
+ }
59529
+ function clamp(value, max = 120) {
59530
+ const compact = value.replace(/\s+/g, " ").trim();
59531
+ return compact.length > max ? `${compact.slice(0, max - 1)}\u2026` : compact;
59532
+ }
59533
+ function normalizeLabel(value) {
59534
+ return value.toLowerCase().replace(/\s+/g, " ").trim();
59535
+ }
59536
+ function extractFixtureParams(routePath) {
59537
+ const params = new Set;
59538
+ for (const match of routePath.matchAll(/:([A-Za-z0-9_]+)(?:\*\??)?/g)) {
59539
+ if (match[1])
59540
+ params.add(match[1]);
59541
+ }
59542
+ return [...params];
59543
+ }
59544
+ function requiresFixture(value, fixtureParams) {
59545
+ if (fixtureParams.length === 0)
59546
+ return false;
59547
+ const normalized = value.toLowerCase();
59548
+ return fixtureParams.some((param) => normalized.includes(param.toLowerCase()) || normalized.includes(`[${param}]`));
59549
+ }
59550
+ function isDestructiveAction(label, context = "") {
59551
+ return /\b(delete|destroy|remove|revoke|refund|void|archive|suspend|pause|disable|cancel|reset|purge|terminate)\b/i.test(`${label} ${context}`);
59552
+ }
59553
+ function formatActionStep(action) {
59554
+ const target = action.target ? ` (${action.target})` : "";
59555
+ const fixture = action.requiresFixture ? " after binding fixture values" : "";
59556
+ const guard = action.destructive ? " without confirming the destructive final action" : "";
59557
+ if (action.kind === "api-method") {
59558
+ return `Exercise API method ${action.label}${target}${fixture}${guard}.`;
59559
+ }
59560
+ return `Exercise ${action.kind} "${action.label}"${target}${fixture}${guard}.`;
59561
+ }
59562
+ function classifyRoute(routePath, groups, file) {
59563
+ const haystack = `${routePath} ${groups.join(" ")} ${file}`.toLowerCase();
59564
+ if (haystack.includes("admin"))
59565
+ return "admin";
59566
+ if (haystack.includes("auth") || routePath.startsWith("/cli/device"))
59567
+ return "auth";
59568
+ if (haystack.includes("ai-runtime") || /\/(chat|sessions|memories|knowledge|learning|copilot|guardrails)\b/.test(routePath))
59569
+ return "ai-runtime";
59570
+ if (haystack.includes("commerce") || /\/(billing|shop|agent-wallet|domains|whois-profiles)\b/.test(routePath))
59571
+ return "commerce";
59572
+ if (haystack.includes("communications") || /\/(telephony|emails)\b/.test(routePath))
59573
+ return "communications";
59574
+ if (haystack.includes("crm") || routePath.includes("/contacts"))
59575
+ return "crm";
59576
+ if (haystack.includes("integrations") || routePath.includes("/connectors"))
59577
+ return "integrations";
59578
+ if (haystack.includes("dashboard") || routePath.includes(":orgSlug"))
59579
+ return "dashboard";
59580
+ if (haystack.includes("public") || haystack.includes("pages"))
59581
+ return "public";
59582
+ if (routePath.startsWith("/api/"))
59583
+ return "api";
59584
+ return "app";
59585
+ }
59586
+ function inferRequiresAuth(routePath, groups, kind) {
59587
+ const haystack = `${routePath} ${groups.join(" ")}`.toLowerCase();
59588
+ if (haystack.includes("auth") || haystack.includes("public") || haystack.includes("webhook"))
59589
+ return false;
59590
+ if (routePath.startsWith("/api/v1/auth/"))
59591
+ return false;
59592
+ if (routePath.startsWith("/api/"))
59593
+ return true;
59594
+ return kind === "page" && (haystack.includes("admin") || haystack.includes("dashboard") || routePath.includes(":orgSlug") || routePath.startsWith("/settings"));
59595
+ }
59596
+ function tagsForRoute(input) {
59597
+ const tags = new Set([
59598
+ "next-route",
59599
+ `route:${input.kind}`,
59600
+ `area:${input.category}`,
59601
+ input.category
59602
+ ]);
59603
+ for (const group of input.groups)
59604
+ tags.add(`group:${group}`);
59605
+ if (input.dynamic)
59606
+ tags.add("dynamic-route");
59607
+ if (input.requiresAuth)
59608
+ tags.add("auth-required");
59609
+ if (input.routePath.startsWith("/api/"))
59610
+ tags.add("api");
59611
+ return [...tags];
59612
+ }
59613
+ function priorityForRoute(routePath, category, kind) {
59614
+ if (category === "auth")
59615
+ return "critical";
59616
+ if (category === "commerce" || category === "ai-runtime")
59617
+ return "critical";
59618
+ if (category === "admin" || category === "dashboard")
59619
+ return "high";
59620
+ if (kind === "api")
59621
+ return "high";
59622
+ if (routePath === "/" || category === "public")
59623
+ return "medium";
59624
+ return "medium";
59625
+ }
59626
+ var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS, IMPORT_SCAN_LIMIT = 40, IMPORT_SCAN_DEPTH = 3, SOURCE_EXTENSIONS;
59627
+ var init_next_route_inventory = __esm(() => {
59628
+ init_scenarios();
59629
+ init_workflows();
59630
+ ROUTE_FILE_NAMES = new Set([
59631
+ "page.tsx",
59632
+ "page.ts",
59633
+ "page.jsx",
59634
+ "page.js",
59635
+ "page.mdx",
59636
+ "route.ts",
59637
+ "route.js"
59638
+ ]);
59639
+ WALK_EXCLUDES = new Set([
59640
+ ".git",
59641
+ ".next",
59642
+ ".turbo",
59643
+ "node_modules",
59644
+ "dist",
59645
+ "build",
59646
+ "coverage"
59647
+ ]);
59648
+ SAFE_PAGE_ASSERTIONS = [{ type: "no_console_errors" }];
59649
+ SOURCE_EXTENSIONS = [
59650
+ ".tsx",
59651
+ ".ts",
59652
+ ".jsx",
59653
+ ".js",
59654
+ ".mdx"
59655
+ ];
59656
+ });
59657
+
59153
59658
  // src/lib/generator.ts
59154
59659
  var exports_generator = {};
59155
59660
  __export(exports_generator, {
@@ -59448,7 +59953,7 @@ async function recordSession(url, options) {
59448
59953
  await Promise.race([
59449
59954
  page.waitForEvent("close").catch(() => {}),
59450
59955
  context.waitForEvent("close").catch(() => {}),
59451
- new Promise((resolve3) => setTimeout(resolve3, timeout))
59956
+ new Promise((resolve4) => setTimeout(resolve4, timeout))
59452
59957
  ]);
59453
59958
  clearInterval(pollInterval);
59454
59959
  try {
@@ -77016,7 +77521,7 @@ function createProviderToolFactoryWithOutputSchema({
77016
77521
  supportsDeferredResults
77017
77522
  });
77018
77523
  }
77019
- async function resolve3(value) {
77524
+ async function resolve4(value) {
77020
77525
  if (typeof value === "function") {
77021
77526
  value = value();
77022
77527
  }
@@ -78703,7 +79208,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78703
79208
  try {
78704
79209
  const { value } = await getFromApi({
78705
79210
  url: `${this.config.baseURL}/config`,
78706
- headers: await resolve3(this.config.headers()),
79211
+ headers: await resolve4(this.config.headers()),
78707
79212
  successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
78708
79213
  failedResponseHandler: createJsonErrorResponseHandler({
78709
79214
  errorSchema: exports_external2.any(),
@@ -78721,7 +79226,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78721
79226
  const baseUrl = new URL(this.config.baseURL);
78722
79227
  const { value } = await getFromApi({
78723
79228
  url: `${baseUrl.origin}/v1/credits`,
78724
- headers: await resolve3(this.config.headers()),
79229
+ headers: await resolve4(this.config.headers()),
78725
79230
  successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
78726
79231
  failedResponseHandler: createJsonErrorResponseHandler({
78727
79232
  errorSchema: exports_external2.any(),
@@ -78767,7 +79272,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78767
79272
  }
78768
79273
  const { value } = await getFromApi({
78769
79274
  url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
78770
- headers: await resolve3(this.config.headers()),
79275
+ headers: await resolve4(this.config.headers()),
78771
79276
  successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
78772
79277
  failedResponseHandler: createJsonErrorResponseHandler({
78773
79278
  errorSchema: exports_external2.any(),
@@ -78789,7 +79294,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78789
79294
  const baseUrl = new URL(this.config.baseURL);
78790
79295
  const { value } = await getFromApi({
78791
79296
  url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
78792
- headers: await resolve3(this.config.headers()),
79297
+ headers: await resolve4(this.config.headers()),
78793
79298
  successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
78794
79299
  failedResponseHandler: createJsonErrorResponseHandler({
78795
79300
  errorSchema: exports_external2.any(),
@@ -78822,7 +79327,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78822
79327
  async doGenerate(options) {
78823
79328
  const { args, warnings } = await this.getArgs(options);
78824
79329
  const { abortSignal } = options;
78825
- const resolvedHeaders = await resolve3(this.config.headers());
79330
+ const resolvedHeaders = await resolve4(this.config.headers());
78826
79331
  try {
78827
79332
  const {
78828
79333
  responseHeaders,
@@ -78830,7 +79335,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78830
79335
  rawValue: rawResponse
78831
79336
  } = await postJsonToApi({
78832
79337
  url: this.getUrl(),
78833
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve3(this.config.o11yHeaders)),
79338
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve4(this.config.o11yHeaders)),
78834
79339
  body: args,
78835
79340
  successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
78836
79341
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -78853,11 +79358,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78853
79358
  async doStream(options) {
78854
79359
  const { args, warnings } = await this.getArgs(options);
78855
79360
  const { abortSignal } = options;
78856
- const resolvedHeaders = await resolve3(this.config.headers());
79361
+ const resolvedHeaders = await resolve4(this.config.headers());
78857
79362
  try {
78858
79363
  const { value: response, responseHeaders } = await postJsonToApi({
78859
79364
  url: this.getUrl(),
78860
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve3(this.config.o11yHeaders)),
79365
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve4(this.config.o11yHeaders)),
78861
79366
  body: args,
78862
79367
  successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
78863
79368
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -78942,7 +79447,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78942
79447
  providerOptions
78943
79448
  }) {
78944
79449
  var _a92;
78945
- const resolvedHeaders = await resolve3(this.config.headers());
79450
+ const resolvedHeaders = await resolve4(this.config.headers());
78946
79451
  try {
78947
79452
  const {
78948
79453
  responseHeaders,
@@ -78950,7 +79455,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
78950
79455
  rawValue
78951
79456
  } = await postJsonToApi({
78952
79457
  url: this.getUrl(),
78953
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79458
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
78954
79459
  body: {
78955
79460
  values,
78956
79461
  ...providerOptions ? { providerOptions } : {}
@@ -79006,7 +79511,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79006
79511
  abortSignal
79007
79512
  }) {
79008
79513
  var _a92, _b92, _c2, _d2;
79009
- const resolvedHeaders = await resolve3(this.config.headers());
79514
+ const resolvedHeaders = await resolve4(this.config.headers());
79010
79515
  try {
79011
79516
  const {
79012
79517
  responseHeaders,
@@ -79014,7 +79519,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79014
79519
  rawValue
79015
79520
  } = await postJsonToApi({
79016
79521
  url: this.getUrl(),
79017
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79522
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
79018
79523
  body: {
79019
79524
  prompt,
79020
79525
  n: n2,
@@ -79089,11 +79594,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79089
79594
  abortSignal
79090
79595
  }) {
79091
79596
  var _a92;
79092
- const resolvedHeaders = await resolve3(this.config.headers());
79597
+ const resolvedHeaders = await resolve4(this.config.headers());
79093
79598
  try {
79094
79599
  const { responseHeaders, value: responseBody } = await postJsonToApi({
79095
79600
  url: this.getUrl(),
79096
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders), { accept: "text/event-stream" }),
79601
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders), { accept: "text/event-stream" }),
79097
79602
  body: {
79098
79603
  prompt,
79099
79604
  n: n2,
@@ -79216,7 +79721,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79216
79721
  abortSignal,
79217
79722
  providerOptions
79218
79723
  }) {
79219
- const resolvedHeaders = await resolve3(this.config.headers());
79724
+ const resolvedHeaders = await resolve4(this.config.headers());
79220
79725
  try {
79221
79726
  const {
79222
79727
  responseHeaders,
@@ -79224,7 +79729,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
79224
79729
  rawValue
79225
79730
  } = await postJsonToApi({
79226
79731
  url: this.getUrl(),
79227
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
79732
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
79228
79733
  body: {
79229
79734
  documents,
79230
79735
  query,
@@ -88642,7 +89147,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
88642
89147
  const schema = asSchema(inputSchema);
88643
89148
  return {
88644
89149
  name: "object",
88645
- responseFormat: resolve3(schema.jsonSchema).then((jsonSchema2) => ({
89150
+ responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
88646
89151
  type: "json",
88647
89152
  schema: jsonSchema2,
88648
89153
  ...name21 != null && { name: name21 },
@@ -88703,7 +89208,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
88703
89208
  const elementSchema = asSchema(inputElementSchema);
88704
89209
  return {
88705
89210
  name: "array",
88706
- responseFormat: resolve3(elementSchema.jsonSchema).then((jsonSchema2) => {
89211
+ responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
88707
89212
  const { $schema, ...itemSchema } = jsonSchema2;
88708
89213
  return {
88709
89214
  type: "json",
@@ -91600,9 +92105,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
91600
92105
  ...options
91601
92106
  }) {
91602
92107
  var _a21, _b16, _c2, _d2, _e2;
91603
- const resolvedBody = await resolve3(this.body);
91604
- const resolvedHeaders = await resolve3(this.headers);
91605
- const resolvedCredentials = await resolve3(this.credentials);
92108
+ const resolvedBody = await resolve4(this.body);
92109
+ const resolvedHeaders = await resolve4(this.headers);
92110
+ const resolvedCredentials = await resolve4(this.credentials);
91606
92111
  const baseHeaders = {
91607
92112
  ...normalizeHeaders(resolvedHeaders),
91608
92113
  ...normalizeHeaders(options.headers)
@@ -91650,9 +92155,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
91650
92155
  }
91651
92156
  async reconnectToStream(options) {
91652
92157
  var _a21, _b16, _c2, _d2, _e2;
91653
- const resolvedBody = await resolve3(this.body);
91654
- const resolvedHeaders = await resolve3(this.headers);
91655
- const resolvedCredentials = await resolve3(this.credentials);
92158
+ const resolvedBody = await resolve4(this.body);
92159
+ const resolvedHeaders = await resolve4(this.headers);
92160
+ const resolvedCredentials = await resolve4(this.credentials);
91656
92161
  const baseHeaders = {
91657
92162
  ...normalizeHeaders(resolvedHeaders),
91658
92163
  ...normalizeHeaders(options.headers)
@@ -93873,7 +94378,7 @@ __export(exports_session_converter, {
93873
94378
  convertSessionToScenario: () => convertSessionToScenario,
93874
94379
  convertSessionFile: () => convertSessionFile
93875
94380
  });
93876
- import { readFileSync as readFileSync9 } from "fs";
94381
+ import { readFileSync as readFileSync10 } from "fs";
93877
94382
  import { extname } from "path";
93878
94383
  function parseRrwebSession(events) {
93879
94384
  const result = [];
@@ -94058,7 +94563,7 @@ ${condensed}`;
94058
94563
  };
94059
94564
  }
94060
94565
  async function convertSessionFile(filePath, format, options) {
94061
- const raw = readFileSync9(filePath, "utf-8");
94566
+ const raw = readFileSync10(filePath, "utf-8");
94062
94567
  let parsed;
94063
94568
  try {
94064
94569
  parsed = JSON.parse(raw);
@@ -94092,7 +94597,7 @@ function detectSessionFormat(filePath) {
94092
94597
  if (ext === ".har")
94093
94598
  return "har";
94094
94599
  try {
94095
- const content = readFileSync9(filePath, "utf-8").trim();
94600
+ const content = readFileSync10(filePath, "utf-8").trim();
94096
94601
  const parsed = JSON.parse(content);
94097
94602
  if (Array.isArray(parsed) && parsed[0]?.type !== undefined && typeof parsed[0]?.timestamp === "number") {
94098
94603
  return "rrweb";
@@ -94536,7 +95041,7 @@ import chalk6 from "chalk";
94536
95041
  // package.json
94537
95042
  var package_default = {
94538
95043
  name: "@hasna/testers",
94539
- version: "0.0.48",
95044
+ version: "0.0.50",
94540
95045
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
94541
95046
  type: "module",
94542
95047
  main: "dist/index.js",
@@ -94635,9 +95140,9 @@ init_todos_connector();
94635
95140
  init_browser();
94636
95141
  import { render, Box, Text, useInput, useApp } from "ink";
94637
95142
  import React, { useState } from "react";
94638
- import { readFileSync as readFileSync10, readdirSync as readdirSync6, writeFileSync as writeFileSync7 } from "fs";
95143
+ import { readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "fs";
94639
95144
  import { createInterface } from "readline";
94640
- import { join as join20, resolve as resolve4 } from "path";
95145
+ import { join as join21, resolve as resolve5 } from "path";
94641
95146
 
94642
95147
  // src/lib/init.ts
94643
95148
  init_paths();
@@ -96723,7 +97228,7 @@ init_ci();
96723
97228
  init_assertions();
96724
97229
  init_paths();
96725
97230
  init_sessions();
96726
- import { existsSync as existsSync18, mkdirSync as mkdirSync15 } from "fs";
97231
+ import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
96727
97232
 
96728
97233
  // src/lib/repo-discovery.ts
96729
97234
  init_paths();
@@ -97742,18 +98247,18 @@ program2.command("prod-debug <target>").description("Create a safe production de
97742
98247
  }, config2.prodDebug);
97743
98248
  const output = opts.json ? JSON.stringify(plan, null, 2) : formatProdDebugPlan(plan);
97744
98249
  if (opts.output) {
97745
- writeFileSync7(resolve4(opts.output), output + `
98250
+ writeFileSync7(resolve5(opts.output), output + `
97746
98251
  `);
97747
98252
  } else {
97748
98253
  log(output);
97749
98254
  }
97750
98255
  });
97751
98256
  var CONFIG_DIR5 = getTestersDir();
97752
- var CONFIG_PATH4 = join20(CONFIG_DIR5, "config.json");
98257
+ var CONFIG_PATH4 = join21(CONFIG_DIR5, "config.json");
97753
98258
  function getActiveProject() {
97754
98259
  try {
97755
- if (existsSync18(CONFIG_PATH4)) {
97756
- const raw = JSON.parse(readFileSync10(CONFIG_PATH4, "utf-8"));
98260
+ if (existsSync19(CONFIG_PATH4)) {
98261
+ const raw = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
97757
98262
  return raw.activeProject ?? undefined;
97758
98263
  }
97759
98264
  } catch {}
@@ -97960,7 +98465,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
97960
98465
  }
97961
98466
  if (!opts.yes) {
97962
98467
  process.stdout.write(chalk6.yellow(`Delete scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
97963
- const answer = await new Promise((resolve5) => {
98468
+ const answer = await new Promise((resolve6) => {
97964
98469
  let buf = "";
97965
98470
  process.stdin.setRawMode?.(true);
97966
98471
  process.stdin.resume();
@@ -97970,7 +98475,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
97970
98475
  process.stdin.pause();
97971
98476
  process.stdout.write(`
97972
98477
  `);
97973
- resolve5(buf);
98478
+ resolve6(buf);
97974
98479
  });
97975
98480
  });
97976
98481
  if (answer !== "y" && answer !== "yes") {
@@ -97999,7 +98504,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
97999
98504
  }
98000
98505
  if (!opts.yes) {
98001
98506
  process.stdout.write(chalk6.yellow(`Remove scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
98002
- const answer = await new Promise((resolve5) => {
98507
+ const answer = await new Promise((resolve6) => {
98003
98508
  let buf = "";
98004
98509
  process.stdin.setRawMode?.(true);
98005
98510
  process.stdin.resume();
@@ -98009,7 +98514,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
98009
98514
  process.stdin.pause();
98010
98515
  process.stdout.write(`
98011
98516
  `);
98012
- resolve5(buf);
98517
+ resolve6(buf);
98013
98518
  });
98014
98519
  });
98015
98520
  if (answer !== "y" && answer !== "yes") {
@@ -98212,7 +98717,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
98212
98717
  `);
98213
98718
  }
98214
98719
  };
98215
- await new Promise((resolve5) => {
98720
+ await new Promise((resolve6) => {
98216
98721
  const poll = setInterval(() => {
98217
98722
  const run3 = getRun(runId);
98218
98723
  if (!run3)
@@ -98220,7 +98725,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
98220
98725
  renderTable();
98221
98726
  if (DONE_STATUSES.has(run3.status)) {
98222
98727
  clearInterval(poll);
98223
- resolve5();
98728
+ resolve6();
98224
98729
  }
98225
98730
  }, POLL_INTERVAL);
98226
98731
  });
@@ -98615,15 +99120,15 @@ program2.command("screenshots <id>").description("List screenshots for a run or
98615
99120
  });
98616
99121
  program2.command("import <dir>").description("Import markdown test files as scenarios").action((dir) => {
98617
99122
  try {
98618
- const absDir = resolve4(dir);
98619
- const files = readdirSync6(absDir).filter((f2) => f2.endsWith(".md"));
99123
+ const absDir = resolve5(dir);
99124
+ const files = readdirSync7(absDir).filter((f2) => f2.endsWith(".md"));
98620
99125
  if (files.length === 0) {
98621
99126
  log(chalk6.dim("No .md files found in directory."));
98622
99127
  return;
98623
99128
  }
98624
99129
  let imported = 0;
98625
99130
  for (const file2 of files) {
98626
- const content = readFileSync10(join20(absDir, file2), "utf-8");
99131
+ const content = readFileSync11(join21(absDir, file2), "utf-8");
98627
99132
  const lines = content.split(`
98628
99133
  `);
98629
99134
  let name21 = file2.replace(/\.md$/, "");
@@ -98679,11 +99184,11 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
98679
99184
  const outputPath = opts.output ?? "testers-export.json";
98680
99185
  const data = JSON.stringify(scenarios, null, 2);
98681
99186
  writeFileSync7(outputPath, data, "utf-8");
98682
- log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${resolve4(outputPath)}`));
99187
+ log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${resolve5(outputPath)}`));
98683
99188
  return;
98684
99189
  }
98685
99190
  const outputDir = opts.output ?? ".";
98686
- if (!existsSync18(outputDir)) {
99191
+ if (!existsSync19(outputDir)) {
98687
99192
  mkdirSync15(outputDir, { recursive: true });
98688
99193
  }
98689
99194
  for (const s2 of scenarios) {
@@ -98711,13 +99216,13 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
98711
99216
  lines.push("");
98712
99217
  }
98713
99218
  const safeFilename = s2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
98714
- const filePath = join20(outputDir, `${s2.shortId}-${safeFilename}.md`);
99219
+ const filePath = join21(outputDir, `${s2.shortId}-${safeFilename}.md`);
98715
99220
  writeFileSync7(filePath, lines.join(`
98716
99221
  `), "utf-8");
98717
99222
  log(chalk6.dim(` ${s2.shortId}: ${s2.name} \u2192 ${filePath}`));
98718
99223
  }
98719
99224
  log(chalk6.green(`
98720
- Exported ${scenarios.length} scenario(s) as markdown to ${resolve4(outputDir)}`));
99225
+ Exported ${scenarios.length} scenario(s) as markdown to ${resolve5(outputDir)}`));
98721
99226
  } catch (error40) {
98722
99227
  logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
98723
99228
  process.exit(1);
@@ -98736,7 +99241,7 @@ program2.command("status").description("Show database and auth status").action((
98736
99241
  try {
98737
99242
  const config2 = loadConfig();
98738
99243
  const hasApiKey = !!config2.anthropicApiKey || !!process.env["ANTHROPIC_API_KEY"];
98739
- const dbPath = join20(getTestersDir(), "testers.db");
99244
+ const dbPath = join21(getTestersDir(), "testers.db");
98740
99245
  log("");
98741
99246
  log(chalk6.bold(" Open Testers Status"));
98742
99247
  log("");
@@ -98886,13 +99391,13 @@ projectCmd.command("export-open <id>").description("Register a testers project i
98886
99391
  projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name21, opts) => {
98887
99392
  try {
98888
99393
  const project = ensureProject(name21, process.cwd());
98889
- if (!existsSync18(CONFIG_DIR5)) {
99394
+ if (!existsSync19(CONFIG_DIR5)) {
98890
99395
  mkdirSync15(CONFIG_DIR5, { recursive: true });
98891
99396
  }
98892
99397
  let config2 = {};
98893
- if (existsSync18(CONFIG_PATH4)) {
99398
+ if (existsSync19(CONFIG_PATH4)) {
98894
99399
  try {
98895
- config2 = JSON.parse(readFileSync10(CONFIG_PATH4, "utf-8"));
99400
+ config2 = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
98896
99401
  } catch {}
98897
99402
  }
98898
99403
  config2.activeProject = project.id;
@@ -98910,7 +99415,7 @@ projectCmd.command("use <name>").description("Set active project (find or create
98910
99415
  var repoCmd = program2.command("repo").description("Discover and run repo-native Playwright tests");
98911
99416
  repoCmd.command("discover [path]").alias("scan").description("Discover Playwright tests in a repo").option("--refresh", "Force a fresh scan, ignoring cache", false).option("--json", "Output as JSON", false).option("--base-url <url>", "Override the suggested base URL").action((path, opts) => {
98912
99417
  try {
98913
- const repoPath = resolve4(path ?? process.cwd());
99418
+ const repoPath = resolve5(path ?? process.cwd());
98914
99419
  const snapshot = discoverRepo({
98915
99420
  repoPath,
98916
99421
  refresh: opts.refresh,
@@ -98976,7 +99481,7 @@ repoCmd.command("discover [path]").alias("scan").description("Discover Playwrigh
98976
99481
  });
98977
99482
  repoCmd.command("prepare [path]").alias("prep").description("Install dependencies and browsers for repo tests").option("--all", "Run all prep steps (install, browsers, build, seed)", false).option("--install", "Install dependencies", false).option("--browsers", "Install Playwright browsers", false).option("--build", "Build the app", false).option("--seed", "Seed the database", false).option("--refresh", "Force fresh discovery scan", false).option("--json", "Output as JSON", false).action((path, opts) => {
98978
99483
  try {
98979
- const repoPath = resolve4(path ?? process.cwd());
99484
+ const repoPath = resolve5(path ?? process.cwd());
98980
99485
  const snapshot = discoverRepo({ repoPath, refresh: opts.refresh });
98981
99486
  const steps = [];
98982
99487
  if (opts.all) {
@@ -99054,7 +99559,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
99054
99559
  return acc;
99055
99560
  }, []).option("--timeout <ms>", "Timeout per spec file", "300000").option("--url <url>", "Dev server URL").option("--project <id>", "Project ID for result storage").option("--label <text>", "Run label").option("--json", "Output as JSON", false).action(async (path, opts) => {
99056
99561
  try {
99057
- const repoPath = resolve4(path ?? process.cwd());
99562
+ const repoPath = resolve5(path ?? process.cwd());
99058
99563
  const snapshot = discoverRepo({
99059
99564
  repoPath,
99060
99565
  refresh: opts.refresh,
@@ -99120,7 +99625,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
99120
99625
  repoCmd.command("cache [path]").description("Manage discovery cache").option("--clear", "Clear discovery cache", false).option("--status", "Show cache status", false).action((path, opts) => {
99121
99626
  try {
99122
99627
  if (opts.clear) {
99123
- const repoPath2 = path ? resolve4(path) : undefined;
99628
+ const repoPath2 = path ? resolve5(path) : undefined;
99124
99629
  clearDiscoveryCache(repoPath2);
99125
99630
  if (repoPath2) {
99126
99631
  log(chalk6.green("Discovery cache cleared for this repo."));
@@ -99130,7 +99635,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
99130
99635
  return;
99131
99636
  }
99132
99637
  if (opts.status) {
99133
- const repoPath2 = resolve4(path ?? process.cwd());
99638
+ const repoPath2 = resolve5(path ?? process.cwd());
99134
99639
  const info2 = getDiscoveryCacheInfo(repoPath2);
99135
99640
  if (!info2) {
99136
99641
  log(chalk6.dim("No discovery cache for this repo."));
@@ -99144,7 +99649,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
99144
99649
  log("");
99145
99650
  return;
99146
99651
  }
99147
- const repoPath = resolve4(path ?? process.cwd());
99652
+ const repoPath = resolve5(path ?? process.cwd());
99148
99653
  const info = getDiscoveryCacheInfo(repoPath);
99149
99654
  if (!info) {
99150
99655
  log(chalk6.dim("No discovery cache. Run 'testers repo discover' to create one."));
@@ -99233,8 +99738,8 @@ sessionCmd.command("show <id>").description("Show details of a recorded session"
99233
99738
  });
99234
99739
  sessionCmd.command("import <file>").description("Import a session JSON file exported from the Chrome extension").action(async (file2) => {
99235
99740
  try {
99236
- const { readFileSync: readFileSync11 } = await import("fs");
99237
- const raw = readFileSync11(file2, "utf-8");
99741
+ const { readFileSync: readFileSync12 } = await import("fs");
99742
+ const raw = readFileSync12(file2, "utf-8");
99238
99743
  const data = JSON.parse(raw);
99239
99744
  const items = Array.isArray(data) ? data : [data];
99240
99745
  const { createSession: createSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
@@ -99474,7 +99979,7 @@ program2.command("daemon").description("Start the scheduler daemon").option("--i
99474
99979
  } catch (err) {
99475
99980
  logError(chalk6.red(`Daemon error: ${err instanceof Error ? err.message : String(err)}`));
99476
99981
  }
99477
- await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
99982
+ await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
99478
99983
  }
99479
99984
  };
99480
99985
  process.on("SIGINT", () => {
@@ -99503,9 +100008,9 @@ program2.command("ci [provider]").description("Print or write a CI workflow (def
99503
100008
  }
99504
100009
  const workflow = generateGitHubActionsWorkflow();
99505
100010
  if (opts.output) {
99506
- const outPath = resolve4(opts.output);
100011
+ const outPath = resolve5(opts.output);
99507
100012
  const outDir = outPath.replace(/\/[^/]*$/, "");
99508
- if (outDir && !existsSync18(outDir)) {
100013
+ if (outDir && !existsSync19(outDir)) {
99509
100014
  mkdirSync15(outDir, { recursive: true });
99510
100015
  }
99511
100016
  writeFileSync7(outPath, workflow, "utf-8");
@@ -99539,11 +100044,11 @@ program2.command("init").description("Initialize a new testing project").option(
99539
100044
  log(` ${chalk6.dim(s2.shortId)} ${s2.name} ${chalk6.dim(`[${s2.tags.join(", ")}]`)}`);
99540
100045
  }
99541
100046
  if (opts.ci === "github") {
99542
- const workflowDir = join20(process.cwd(), ".github", "workflows");
99543
- if (!existsSync18(workflowDir)) {
100047
+ const workflowDir = join21(process.cwd(), ".github", "workflows");
100048
+ if (!existsSync19(workflowDir)) {
99544
100049
  mkdirSync15(workflowDir, { recursive: true });
99545
100050
  }
99546
- const workflowPath = join20(workflowDir, "testers.yml");
100051
+ const workflowPath = join21(workflowDir, "testers.yml");
99547
100052
  writeFileSync7(workflowPath, generateGitHubActionsWorkflow(), "utf-8");
99548
100053
  log(` CI: ${chalk6.green("GitHub Actions workflow written to .github/workflows/testers.yml")}`);
99549
100054
  } else if (opts.ci) {
@@ -99553,7 +100058,7 @@ program2.command("init").description("Initialize a new testing project").option(
99553
100058
  if (opts.yes)
99554
100059
  return;
99555
100060
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
99556
- const ask = (q2) => new Promise((resolve5) => rl2.question(q2, resolve5));
100061
+ const ask = (q2) => new Promise((resolve6) => rl2.question(q2, resolve6));
99557
100062
  try {
99558
100063
  const envAnswer = await ask(" Would you like to configure environments? [y/N] ");
99559
100064
  if (envAnswer.trim().toLowerCase() === "y") {
@@ -99737,14 +100242,14 @@ program2.command("quick-qa <url>").alias("quick-check").description("Run a fast
99737
100242
  wcagLevel
99738
100243
  });
99739
100244
  if (opts.output) {
99740
- writeFileSync7(resolve4(opts.output), JSON.stringify(result, null, 2));
100245
+ writeFileSync7(resolve5(opts.output), JSON.stringify(result, null, 2));
99741
100246
  }
99742
100247
  if (opts.json) {
99743
100248
  log(JSON.stringify(result, null, 2));
99744
100249
  } else {
99745
100250
  log(formatQuickQaReport(result));
99746
100251
  if (opts.output)
99747
- log(chalk6.dim(`Wrote JSON results to ${resolve4(opts.output)}`));
100252
+ log(chalk6.dim(`Wrote JSON results to ${resolve5(opts.output)}`));
99748
100253
  }
99749
100254
  process.exit(getQuickQaExitCode(result));
99750
100255
  } catch (error40) {
@@ -99789,7 +100294,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
99789
100294
  });
99790
100295
  if (opts.output && opts.output !== "report.html") {
99791
100296
  writeFileSync7(opts.output, content, "utf-8");
99792
- const absPath2 = resolve4(opts.output);
100297
+ const absPath2 = resolve5(opts.output);
99793
100298
  log(chalk6.green(`Compliance report written to ${absPath2}`));
99794
100299
  } else {
99795
100300
  log(content);
@@ -99803,7 +100308,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
99803
100308
  html = generateHtmlReport(runId);
99804
100309
  }
99805
100310
  writeFileSync7(opts.output, html, "utf-8");
99806
- const absPath = resolve4(opts.output);
100311
+ const absPath = resolve5(opts.output);
99807
100312
  log(chalk6.green(`Report generated: ${absPath}`));
99808
100313
  if (opts.open) {
99809
100314
  const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
@@ -100160,6 +100665,62 @@ Imported ${imported} scenarios from API spec:`));
100160
100665
  process.exit(1);
100161
100666
  }
100162
100667
  });
100668
+ var inventoryCmd = program2.command("inventory").description("Discover source-derived app route/action inventories");
100669
+ inventoryCmd.command("next [root]").description("Discover Next.js app routes and optionally import route coverage scenarios").option("--app-dir <path>", "Next.js app directory relative to root (default: packages/web/app or app)").option("--project <id>", "Project ID").option("--no-pages", "Do not include page.tsx/page.ts routes").option("--no-api", "Do not include route.ts/route.js API routes").option("--limit <n>", "Limit discovered routes").option("--create-scenarios", "Upsert source-derived route coverage scenarios", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--workflow-target <target>", "Workflow execution target: local or sandbox", "sandbox").option("--sandbox-provider <provider>", "Sandbox provider for created workflows", "e2b").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
100670
+ acc.push(val);
100671
+ return acc;
100672
+ }, []).option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
100673
+ try {
100674
+ const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
100675
+ const projectId = resolveProject2(opts.project) ?? undefined;
100676
+ const env = parseSandboxEnv(undefined, opts.sandboxEnvOptional);
100677
+ const result = importNextRouteInventory2({
100678
+ rootDir: root ?? process.cwd(),
100679
+ appDir: opts.appDir,
100680
+ projectId,
100681
+ includePages: opts.pages !== false,
100682
+ includeApi: opts.api !== false,
100683
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
100684
+ createScenarios: opts.createScenarios,
100685
+ createWorkflows: opts.createWorkflows,
100686
+ workflowTarget: opts.workflowTarget,
100687
+ workflowProvider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
100688
+ workflowExecution: {
100689
+ target: opts.workflowTarget,
100690
+ provider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
100691
+ sandboxCleanup: opts.sandboxCleanup,
100692
+ sandboxSyncStrategy: opts.sandboxSync,
100693
+ timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
100694
+ env
100695
+ }
100696
+ });
100697
+ if (opts.json) {
100698
+ log(JSON.stringify(result, null, 2));
100699
+ return;
100700
+ }
100701
+ log("");
100702
+ log(chalk6.bold(" Next.js Route Inventory"));
100703
+ log(chalk6.dim(` Root: ${result.inventory.rootDir}`));
100704
+ log(chalk6.dim(` App: ${result.inventory.appDir}`));
100705
+ log("");
100706
+ log(` Routes: ${chalk6.cyan(String(result.inventory.total))} (${result.inventory.pages} pages, ${result.inventory.apiRoutes} API, ${result.inventory.dynamic} dynamic)`);
100707
+ log(` Scenarios: ${chalk6.green(String(result.created))} created, ${chalk6.yellow(String(result.updated))} updated, ${chalk6.dim(String(result.deduped))} deduped`);
100708
+ log(` Workflows: ${result.workflows.length}`);
100709
+ log("");
100710
+ for (const [category, count] of Object.entries(result.inventory.categories).sort()) {
100711
+ log(` ${category.padEnd(18)} ${count}`);
100712
+ }
100713
+ log("");
100714
+ if (!opts.createScenarios)
100715
+ log(chalk6.dim(" Add --create-scenarios to upsert route scenarios."));
100716
+ if (!opts.createWorkflows)
100717
+ log(chalk6.dim(" Add --create-workflows to upsert grouped workflows."));
100718
+ log("");
100719
+ } catch (error40) {
100720
+ logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
100721
+ process.exit(1);
100722
+ }
100723
+ });
100163
100724
  program2.command("generate <url>").description("Crawl app and synthesize test scenarios using AI (any provider)").option("--max <n>", "Max scenarios to generate", "10").option("--max-pages <n>", "Max pages to crawl", "10").option("--focus <topic>", "Focus on specific area e.g. 'auth flows', 'checkout'").option("--persona <desc>", "Persona perspective e.g. 'first-time user'").option("--model <model>", "AI model (claude-haiku, gpt-4o-mini, gemini-2.0-flash, etc.)").option("--save", "Persist generated scenarios to DB", false).option("--project <id>", "Project ID").option("--headed", "Run browser in headed mode", false).option("--json", "Output as JSON", false).action(async (url2, opts) => {
100164
100725
  try {
100165
100726
  const { generateScenarios: generateScenarios2 } = await Promise.resolve().then(() => (init_generator(), exports_generator));
@@ -100622,7 +101183,7 @@ program2.command("doctor").description("Check system setup and configuration").a
100622
101183
  log(chalk6.red("\u2717") + " ANTHROPIC_API_KEY is not set (required for AI-powered tests)");
100623
101184
  allPassed = false;
100624
101185
  }
100625
- const dbPath = join20(getTestersDir(), "testers.db");
101186
+ const dbPath = join21(getTestersDir(), "testers.db");
100626
101187
  try {
100627
101188
  const { Database: Database5 } = await import("bun:sqlite");
100628
101189
  const db3 = new Database5(dbPath, { create: true });
@@ -100676,7 +101237,7 @@ program2.command("serve").description("Start the Open Testers web dashboard").op
100676
101237
  try {
100677
101238
  const port = parseInt(opts.port, 10);
100678
101239
  const url2 = `http://localhost:${port}`;
100679
- const serverBin = join20(resolve4(process.execPath, ".."), "..", "dist", "server", "index.js");
101240
+ const serverBin = join21(resolve5(process.execPath, ".."), "..", "dist", "server", "index.js");
100680
101241
  const { join: pathJoin, resolve: pathResolve, dirname: dirname7 } = await import("path");
100681
101242
  const { fileURLToPath: fileURLToPath2 } = await import("url");
100682
101243
  const serverPath = pathJoin(dirname7(fileURLToPath2(import.meta.url)), "..", "server", "index.js");
@@ -101442,7 +102003,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
101442
102003
  }
101443
102004
  if (!opts.yes) {
101444
102005
  process.stdout.write(chalk6.yellow(`Delete persona ${persona.shortId} "${persona.name}"? [y/N] `));
101445
- const answer = await new Promise((resolve5) => {
102006
+ const answer = await new Promise((resolve6) => {
101446
102007
  let buf = "";
101447
102008
  process.stdin.setRawMode?.(true);
101448
102009
  process.stdin.resume();
@@ -101452,7 +102013,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
101452
102013
  process.stdin.pause();
101453
102014
  process.stdout.write(`
101454
102015
  `);
101455
- resolve5(buf);
102016
+ resolve6(buf);
101456
102017
  });
101457
102018
  });
101458
102019
  if (answer !== "y" && answer !== "yes") {
@@ -101613,7 +102174,7 @@ evalCmd.command("rag <url>").description("Run RAG quality evaluation \u2014 fait
101613
102174
  let ragTestCases = [];
101614
102175
  if (opts.docs) {
101615
102176
  try {
101616
- const raw = readFileSync10(opts.docs, "utf-8");
102177
+ const raw = readFileSync11(opts.docs, "utf-8");
101617
102178
  ragTestCases = JSON.parse(raw);
101618
102179
  } catch {
101619
102180
  logError(chalk6.red(`Failed to read docs file: ${opts.docs}`));
@@ -101703,9 +102264,9 @@ Created golden answer check ${chalk6.bold(golden2.shortId)}`));
101703
102264
  }
101704
102265
  const ask = (prompt) => {
101705
102266
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
101706
- return new Promise((resolve5) => rl2.question(prompt, (ans) => {
102267
+ return new Promise((resolve6) => rl2.question(prompt, (ans) => {
101707
102268
  rl2.close();
101708
- resolve5(ans.trim());
102269
+ resolve6(ans.trim());
101709
102270
  }));
101710
102271
  };
101711
102272
  const question = await ask("Question (what this endpoint should answer): ");
@@ -101866,9 +102427,9 @@ program2.command("run-many <url>").description("Run scenarios \xD7 personas matr
101866
102427
  });
101867
102428
  program2.command("run-script <file>").description("Run a hybrid test script (.ts) that exports an array of HybridScenario objects").option("--url <url>", "Base URL to run against").option("--json", "Output as JSON", false).action(async (file2, opts) => {
101868
102429
  try {
101869
- const { resolve: resolve5 } = await import("path");
102430
+ const { resolve: resolve6 } = await import("path");
101870
102431
  const { runHybridScenario: runHybridScenario2 } = await Promise.resolve().then(() => (init_hybrid_runner(), exports_hybrid_runner));
101871
- const scriptPath = resolve5(process.cwd(), file2);
102432
+ const scriptPath = resolve6(process.cwd(), file2);
101872
102433
  const mod = await import(scriptPath);
101873
102434
  const scenarios = mod.scenarios ?? mod.default ?? [];
101874
102435
  if (!Array.isArray(scenarios) || scenarios.length === 0) {
@@ -0,0 +1,66 @@
1
+ import type { CreateScenarioInput, Scenario, ScenarioPriority, TestingWorkflow, WorkflowExecutionInput } from "../types/index.js";
2
+ export type NextRouteKind = "page" | "api";
3
+ export type NextRouteActionKind = "link" | "button" | "form" | "input" | "api-method";
4
+ export interface NextRouteAction {
5
+ kind: NextRouteActionKind;
6
+ label: string;
7
+ target?: string;
8
+ sourceFile: string;
9
+ destructive: boolean;
10
+ requiresFixture: boolean;
11
+ }
12
+ export interface NextRouteInventoryItem {
13
+ kind: NextRouteKind;
14
+ routePath: string;
15
+ file: string;
16
+ category: string;
17
+ groups: string[];
18
+ methods: string[];
19
+ dynamic: boolean;
20
+ requiresAuth: boolean;
21
+ fixtureParams: string[];
22
+ actions: NextRouteAction[];
23
+ tags: string[];
24
+ priority: ScenarioPriority;
25
+ }
26
+ export interface NextRouteInventory {
27
+ rootDir: string;
28
+ appDir: string;
29
+ total: number;
30
+ pages: number;
31
+ apiRoutes: number;
32
+ dynamic: number;
33
+ categories: Record<string, number>;
34
+ items: NextRouteInventoryItem[];
35
+ }
36
+ export interface ImportNextRouteInventoryOptions {
37
+ rootDir: string;
38
+ appDir?: string;
39
+ projectId?: string;
40
+ includePages?: boolean;
41
+ includeApi?: boolean;
42
+ limit?: number;
43
+ createScenarios?: boolean;
44
+ createWorkflows?: boolean;
45
+ workflowTarget?: "local" | "sandbox";
46
+ workflowProvider?: string;
47
+ workflowExecution?: Partial<WorkflowExecutionInput>;
48
+ }
49
+ export interface ImportNextRouteInventoryResult {
50
+ inventory: NextRouteInventory;
51
+ created: number;
52
+ updated: number;
53
+ deduped: number;
54
+ scenarios: Scenario[];
55
+ workflows: TestingWorkflow[];
56
+ }
57
+ export declare function discoverNextRouteInventory(options: {
58
+ rootDir: string;
59
+ appDir?: string;
60
+ includePages?: boolean;
61
+ includeApi?: boolean;
62
+ limit?: number;
63
+ }): NextRouteInventory;
64
+ export declare function scenarioInputForNextRoute(item: NextRouteInventoryItem, projectId?: string): CreateScenarioInput;
65
+ export declare function importNextRouteInventory(options: ImportNextRouteInventoryOptions): ImportNextRouteInventoryResult;
66
+ //# sourceMappingURL=next-route-inventory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,mBAAmB,EACnB,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAC3C,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,sBAAsB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAkCD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,kBAAkB,CA4BrB;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,CAAC,EAAE,MAAM,GACjB,mBAAmB,CAwDrB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,8BAA8B,CAuBhC"}
package/dist/mcp/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@hasna/testers",
55
- version: "0.0.48",
55
+ version: "0.0.50",
56
56
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
57
57
  type: "module",
58
58
  main: "dist/index.js",
@@ -46937,7 +46937,7 @@ import { join as join14 } from "path";
46937
46937
  // package.json
46938
46938
  var package_default = {
46939
46939
  name: "@hasna/testers",
46940
- version: "0.0.48",
46940
+ version: "0.0.50",
46941
46941
  description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
46942
46942
  type: "module",
46943
46943
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",