@hasna/testers 0.0.51 → 0.0.53
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 +22 -1
- package/dist/cli/index.js +364 -13
- package/dist/lib/next-route-inventory.d.ts +6 -0
- package/dist/lib/next-route-inventory.d.ts.map +1 -1
- package/dist/lib/workflow-fanout.d.ts +26 -0
- package/dist/lib/workflow-fanout.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,28 @@ testers workflow fanout --project alumia --workers 6 --url https://preview.examp
|
|
|
41
41
|
testers workflow fanout wf_abc,wf_def wf_xyz --workers 12 --url https://preview.example.com --json
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`--workers` is bounded to 1-12 concurrent sandboxes. Use `--dry-run` to inspect the remote commands
|
|
44
|
+
`--workers` is bounded to 1-12 concurrent sandboxes. Fanout preflights provider credentials, required sandbox environment references, `rsync`, and app source directories before launching workers. Use `--dry-run` to inspect the remote commands, upload plans, and preflight checks without spawning sandboxes.
|
|
45
|
+
|
|
46
|
+
### Next.js Route and Action Inventory
|
|
47
|
+
|
|
48
|
+
For large apps, generate source-derived route coverage from the Next.js app directory. The importer can create route-level scenarios and one scenario per discovered link, button, form, input, or API method, then group those action scenarios into sandbox workflows for fanout.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
testers inventory next /path/to/app \
|
|
52
|
+
--project alumia \
|
|
53
|
+
--create-scenarios \
|
|
54
|
+
--create-action-scenarios \
|
|
55
|
+
--create-workflows \
|
|
56
|
+
--create-action-workflows \
|
|
57
|
+
--action-workflow-grouping route \
|
|
58
|
+
--sandbox-provider e2b \
|
|
59
|
+
--sandbox-sync rsync \
|
|
60
|
+
--sandbox-env-optional OPENAI_API_KEY
|
|
61
|
+
|
|
62
|
+
testers workflow fanout --project alumia --tag next-action --workers 6 --url https://preview.example.com --dry-run
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `--action-workflow-grouping route` for route-specific workflows or `--action-workflow-grouping area-kind` for broader workflows such as commerce buttons or admin API methods.
|
|
45
66
|
|
|
46
67
|
### Common Flags
|
|
47
68
|
|
package/dist/cli/index.js
CHANGED
|
@@ -59331,6 +59331,7 @@ var init_openapi_import = __esm(() => {
|
|
|
59331
59331
|
// src/lib/next-route-inventory.ts
|
|
59332
59332
|
var exports_next_route_inventory = {};
|
|
59333
59333
|
__export(exports_next_route_inventory, {
|
|
59334
|
+
scenarioInputsForNextRouteActions: () => scenarioInputsForNextRouteActions,
|
|
59334
59335
|
scenarioInputForNextRoute: () => scenarioInputForNextRoute,
|
|
59335
59336
|
importNextRouteInventory: () => importNextRouteInventory,
|
|
59336
59337
|
discoverNextRouteInventory: () => discoverNextRouteInventory
|
|
@@ -59355,6 +59356,7 @@ function discoverNextRouteInventory(options) {
|
|
|
59355
59356
|
pages: items.filter((item) => item.kind === "page").length,
|
|
59356
59357
|
apiRoutes: items.filter((item) => item.kind === "api").length,
|
|
59357
59358
|
dynamic: items.filter((item) => item.dynamic).length,
|
|
59359
|
+
actions: items.reduce((sum, item) => sum + item.actions.length, 0),
|
|
59358
59360
|
categories,
|
|
59359
59361
|
items
|
|
59360
59362
|
};
|
|
@@ -59410,12 +59412,69 @@ function scenarioInputForNextRoute(item, projectId) {
|
|
|
59410
59412
|
projectId
|
|
59411
59413
|
};
|
|
59412
59414
|
}
|
|
59415
|
+
function scenarioInputsForNextRouteActions(item, projectId) {
|
|
59416
|
+
return item.actions.map((action, index) => scenarioInputForNextRouteAction(item, action, index, projectId));
|
|
59417
|
+
}
|
|
59418
|
+
function scenarioInputForNextRouteAction(item, action, index, projectId) {
|
|
59419
|
+
const label = item.kind === "page" ? "page action" : "API action";
|
|
59420
|
+
const fixtureStep = item.fixtureParams.length > 0 ? `Bind dynamic fixture values for ${item.fixtureParams.map((name) => `:${name}`).join(", ")} before exercising this action.` : undefined;
|
|
59421
|
+
const dynamicStep = item.dynamic ? "Substitute dynamic path parameters with valid fixture values from the target org before opening or calling the route." : undefined;
|
|
59422
|
+
const destructiveGuard = action.destructive ? "If the action reaches a destructive confirmation or mutating final step, verify the warning/cancel path and stop before confirming." : undefined;
|
|
59423
|
+
const pageSteps = [
|
|
59424
|
+
fixtureStep,
|
|
59425
|
+
dynamicStep,
|
|
59426
|
+
`Open the Next.js page ${item.routePath}.`,
|
|
59427
|
+
"Wait for the route to finish loading and verify it does not show a blank shell, framework error page, or unexpected auth loop.",
|
|
59428
|
+
`Locate source-discovered ${action.kind} "${action.label}" from ${action.sourceFile}.`,
|
|
59429
|
+
formatActionStep(action),
|
|
59430
|
+
destructiveGuard,
|
|
59431
|
+
action.kind === "input" ? "Fill the input with safe test data and verify the UI accepts, validates, or rejects it predictably." : "Verify the action produces the expected navigation, modal, toast, table change, validation state, or disabled state.",
|
|
59432
|
+
"Verify the route stays within the expected org/workspace context and does not emit console errors."
|
|
59433
|
+
].filter(Boolean);
|
|
59434
|
+
const apiSteps = [
|
|
59435
|
+
fixtureStep,
|
|
59436
|
+
dynamicStep,
|
|
59437
|
+
`Call ${action.label} ${item.routePath} using safe fixture data.`,
|
|
59438
|
+
action.destructive ? "Use a harmless dry-run/no-op fixture when available; otherwise verify authentication, authorization, validation, or confirmation blocks without creating destructive side effects." : "Verify the method accepts valid safe input and rejects invalid input with a stable response shape.",
|
|
59439
|
+
"Verify expected authentication, authorization, validation, and tenant isolation behavior.",
|
|
59440
|
+
"Verify response status, JSON shape, and error messages are stable and regression-safe."
|
|
59441
|
+
].filter(Boolean);
|
|
59442
|
+
return {
|
|
59443
|
+
name: `Next ${label}: ${item.routePath} :: ${action.kind} ${action.label}`,
|
|
59444
|
+
description: `Source-discovered ${label} ${index + 1} from ${action.sourceFile}. Verify ${action.kind} "${action.label}" on ${item.routePath}.`,
|
|
59445
|
+
steps: item.kind === "page" ? pageSteps : apiSteps,
|
|
59446
|
+
tags: actionTagsForRoute(item, action),
|
|
59447
|
+
priority: action.destructive ? "critical" : item.priority,
|
|
59448
|
+
targetPath: item.routePath,
|
|
59449
|
+
requiresAuth: item.requiresAuth,
|
|
59450
|
+
assertions: item.kind === "page" ? SAFE_PAGE_ASSERTIONS : [],
|
|
59451
|
+
metadata: {
|
|
59452
|
+
source: "next-route-action-inventory",
|
|
59453
|
+
routeFile: item.file,
|
|
59454
|
+
routeKind: item.kind,
|
|
59455
|
+
routePath: item.routePath,
|
|
59456
|
+
category: item.category,
|
|
59457
|
+
methods: item.methods,
|
|
59458
|
+
dynamic: item.dynamic,
|
|
59459
|
+
fixtureParams: item.fixtureParams,
|
|
59460
|
+
actionIndex: index,
|
|
59461
|
+
action,
|
|
59462
|
+
groups: item.groups
|
|
59463
|
+
},
|
|
59464
|
+
parameters: item.fixtureParams.length > 0 ? {
|
|
59465
|
+
routeFixtures: defaultRouteFixturesForParams(item.fixtureParams),
|
|
59466
|
+
routeFixtureParams: item.fixtureParams
|
|
59467
|
+
} : undefined,
|
|
59468
|
+
projectId
|
|
59469
|
+
};
|
|
59470
|
+
}
|
|
59413
59471
|
function importNextRouteInventory(options) {
|
|
59414
59472
|
const inventory = discoverNextRouteInventory(options);
|
|
59415
59473
|
let created = 0;
|
|
59416
59474
|
let updated = 0;
|
|
59417
59475
|
let deduped = 0;
|
|
59418
59476
|
const scenarios = [];
|
|
59477
|
+
const actionScenarios = [];
|
|
59419
59478
|
const workflows = [];
|
|
59420
59479
|
if (options.createScenarios) {
|
|
59421
59480
|
for (const item of inventory.items) {
|
|
@@ -59429,10 +59488,28 @@ function importNextRouteInventory(options) {
|
|
|
59429
59488
|
deduped++;
|
|
59430
59489
|
}
|
|
59431
59490
|
}
|
|
59491
|
+
if (options.createActionScenarios) {
|
|
59492
|
+
for (const item of inventory.items) {
|
|
59493
|
+
for (const input of scenarioInputsForNextRouteActions(item, options.projectId)) {
|
|
59494
|
+
const result = upsertScenario(input);
|
|
59495
|
+
scenarios.push(result.scenario);
|
|
59496
|
+
actionScenarios.push(result.scenario);
|
|
59497
|
+
if (result.action === "created")
|
|
59498
|
+
created++;
|
|
59499
|
+
else if (result.action === "updated")
|
|
59500
|
+
updated++;
|
|
59501
|
+
else
|
|
59502
|
+
deduped++;
|
|
59503
|
+
}
|
|
59504
|
+
}
|
|
59505
|
+
}
|
|
59432
59506
|
if (options.createWorkflows) {
|
|
59433
59507
|
workflows.push(...upsertRouteInventoryWorkflows(inventory, options));
|
|
59434
59508
|
}
|
|
59435
|
-
|
|
59509
|
+
if (options.createActionWorkflows) {
|
|
59510
|
+
workflows.push(...upsertRouteInventoryActionWorkflows(inventory, options));
|
|
59511
|
+
}
|
|
59512
|
+
return { inventory, created, updated, deduped, scenarios, actionScenarios, workflows };
|
|
59436
59513
|
}
|
|
59437
59514
|
function upsertRouteInventoryWorkflows(inventory, options) {
|
|
59438
59515
|
const workflows = [];
|
|
@@ -59462,6 +59539,57 @@ function upsertRouteInventoryWorkflows(inventory, options) {
|
|
|
59462
59539
|
}
|
|
59463
59540
|
return workflows;
|
|
59464
59541
|
}
|
|
59542
|
+
function upsertRouteInventoryActionWorkflows(inventory, options) {
|
|
59543
|
+
const workflows = [];
|
|
59544
|
+
const grouping = options.actionWorkflowGrouping ?? "route";
|
|
59545
|
+
const existingWorkflows = listTestingWorkflows({ projectId: options.projectId, enabled: undefined });
|
|
59546
|
+
if (grouping === "area-kind") {
|
|
59547
|
+
const keys = new Set;
|
|
59548
|
+
for (const item of inventory.items) {
|
|
59549
|
+
for (const action of item.actions) {
|
|
59550
|
+
keys.add(`${item.category}|${action.kind}`);
|
|
59551
|
+
}
|
|
59552
|
+
}
|
|
59553
|
+
for (const key of [...keys].sort()) {
|
|
59554
|
+
const [category, actionKind] = key.split("|");
|
|
59555
|
+
const name = `Next action inventory ${category} ${actionKind}`;
|
|
59556
|
+
const scenarioTags = ["next-action", `area:${category}`, `action:${actionKind}`];
|
|
59557
|
+
workflows.push(upsertTestingWorkflow(existingWorkflows, name, {
|
|
59558
|
+
name,
|
|
59559
|
+
description: `Source-discovered ${actionKind} action coverage for ${category} routes.`,
|
|
59560
|
+
projectId: options.projectId,
|
|
59561
|
+
scenarioFilter: { tags: scenarioTags },
|
|
59562
|
+
execution: workflowExecutionFromOptions(options)
|
|
59563
|
+
}));
|
|
59564
|
+
}
|
|
59565
|
+
return workflows;
|
|
59566
|
+
}
|
|
59567
|
+
for (const item of inventory.items.filter((route) => route.actions.length > 0)) {
|
|
59568
|
+
const name = `Next action inventory ${item.kind} ${item.routePath}`;
|
|
59569
|
+
const scenarioTags = ["next-action", `route:${item.kind}`, `route-path:${item.routePath}`];
|
|
59570
|
+
workflows.push(upsertTestingWorkflow(existingWorkflows, name, {
|
|
59571
|
+
name,
|
|
59572
|
+
description: `Source-discovered action coverage for ${item.kind} route ${item.routePath}.`,
|
|
59573
|
+
projectId: options.projectId,
|
|
59574
|
+
scenarioFilter: { tags: scenarioTags },
|
|
59575
|
+
execution: workflowExecutionFromOptions(options)
|
|
59576
|
+
}));
|
|
59577
|
+
}
|
|
59578
|
+
return workflows;
|
|
59579
|
+
}
|
|
59580
|
+
function workflowExecutionFromOptions(options) {
|
|
59581
|
+
return {
|
|
59582
|
+
target: options.workflowTarget ?? "sandbox",
|
|
59583
|
+
provider: options.workflowProvider,
|
|
59584
|
+
sandboxCleanup: "delete",
|
|
59585
|
+
sandboxSyncStrategy: "rsync",
|
|
59586
|
+
...options.workflowExecution
|
|
59587
|
+
};
|
|
59588
|
+
}
|
|
59589
|
+
function upsertTestingWorkflow(existingWorkflows, name, input) {
|
|
59590
|
+
const existing = existingWorkflows.find((workflow) => workflow.name === name);
|
|
59591
|
+
return existing ? updateTestingWorkflow(existing.id, input) : createTestingWorkflow(input);
|
|
59592
|
+
}
|
|
59465
59593
|
function resolveAppDir(rootDir, appDir) {
|
|
59466
59594
|
const candidates = appDir ? [resolve3(rootDir, appDir)] : [
|
|
59467
59595
|
join20(rootDir, "packages", "web", "app"),
|
|
@@ -59792,6 +59920,19 @@ function tagsForRoute(input) {
|
|
|
59792
59920
|
tags.add("api");
|
|
59793
59921
|
return [...tags];
|
|
59794
59922
|
}
|
|
59923
|
+
function actionTagsForRoute(item, action) {
|
|
59924
|
+
const tags = new Set([
|
|
59925
|
+
...item.tags,
|
|
59926
|
+
"next-action",
|
|
59927
|
+
`action:${action.kind}`,
|
|
59928
|
+
`route-path:${item.routePath}`
|
|
59929
|
+
]);
|
|
59930
|
+
if (action.destructive)
|
|
59931
|
+
tags.add("destructive-action");
|
|
59932
|
+
if (action.requiresFixture)
|
|
59933
|
+
tags.add("fixture-required");
|
|
59934
|
+
return [...tags];
|
|
59935
|
+
}
|
|
59795
59936
|
function priorityForRoute(routePath, category, kind) {
|
|
59796
59937
|
if (category === "auth")
|
|
59797
59938
|
return "critical";
|
|
@@ -60463,8 +60604,11 @@ var exports_workflow_fanout = {};
|
|
|
60463
60604
|
__export(exports_workflow_fanout, {
|
|
60464
60605
|
runWorkflowFanout: () => runWorkflowFanout,
|
|
60465
60606
|
resolveWorkflowFanoutSelection: () => resolveWorkflowFanoutSelection,
|
|
60466
|
-
normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount
|
|
60607
|
+
normalizeFanoutWorkerCount: () => normalizeFanoutWorkerCount,
|
|
60608
|
+
checkWorkflowFanoutReadiness: () => checkWorkflowFanoutReadiness
|
|
60467
60609
|
});
|
|
60610
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
60611
|
+
import { existsSync as existsSync19, statSync as statSync5 } from "fs";
|
|
60468
60612
|
function splitWorkflowIds(ids) {
|
|
60469
60613
|
return (ids ?? []).flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
60470
60614
|
}
|
|
@@ -60497,6 +60641,89 @@ function resolveWorkflowFanoutSelection(options) {
|
|
|
60497
60641
|
}
|
|
60498
60642
|
return filtered;
|
|
60499
60643
|
}
|
|
60644
|
+
async function checkWorkflowFanoutReadiness(workflows, dependencies = {}) {
|
|
60645
|
+
const checks = [];
|
|
60646
|
+
const env = dependencies.env ?? process.env;
|
|
60647
|
+
for (const [provider, providerWorkflows] of groupWorkflowsByProvider(workflows)) {
|
|
60648
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
60649
|
+
if (!envKey) {
|
|
60650
|
+
checks.push({
|
|
60651
|
+
name: `provider:${provider}`,
|
|
60652
|
+
ok: true,
|
|
60653
|
+
required: false,
|
|
60654
|
+
message: `No built-in credential preflight for sandbox provider "${provider}"`,
|
|
60655
|
+
workflows: providerWorkflows.map((workflow) => workflow.name)
|
|
60656
|
+
});
|
|
60657
|
+
continue;
|
|
60658
|
+
}
|
|
60659
|
+
const apiKey = await resolveProviderApiKey(provider, env, dependencies.providerApiKeyResolver);
|
|
60660
|
+
checks.push({
|
|
60661
|
+
name: `provider:${provider}`,
|
|
60662
|
+
ok: Boolean(apiKey),
|
|
60663
|
+
required: true,
|
|
60664
|
+
message: apiKey ? `Sandbox provider "${provider}" credential is available` : `Missing sandbox provider credential for "${provider}". Set ${envKey} or configure providers.${provider}.api_key with sandboxes config`,
|
|
60665
|
+
workflows: providerWorkflows.map((workflow) => workflow.name),
|
|
60666
|
+
details: { provider, envKey }
|
|
60667
|
+
});
|
|
60668
|
+
}
|
|
60669
|
+
const needsRsync = workflows.filter((workflow) => (workflow.execution.sandboxSyncStrategy ?? "rsync") === "rsync" || Boolean(workflow.execution.appSourceDir));
|
|
60670
|
+
if (needsRsync.length > 0) {
|
|
60671
|
+
const commandExists = dependencies.commandExists ?? defaultCommandExists;
|
|
60672
|
+
const rsyncOk = commandExists("rsync");
|
|
60673
|
+
checks.push({
|
|
60674
|
+
name: "tool:rsync",
|
|
60675
|
+
ok: rsyncOk,
|
|
60676
|
+
required: true,
|
|
60677
|
+
message: rsyncOk ? "rsync is available for sandbox uploads and app source bundling" : "Missing rsync. Install rsync or use --sandbox-sync archive for workflows without app source bundling",
|
|
60678
|
+
workflows: needsRsync.map((workflow) => workflow.name)
|
|
60679
|
+
});
|
|
60680
|
+
}
|
|
60681
|
+
const missingAppSources = workflows.filter((workflow) => workflow.execution.appSourceDir).filter((workflow) => {
|
|
60682
|
+
const sourceDir = workflow.execution.appSourceDir;
|
|
60683
|
+
return !existsSync19(sourceDir) || !statSync5(sourceDir).isDirectory();
|
|
60684
|
+
});
|
|
60685
|
+
if (missingAppSources.length > 0) {
|
|
60686
|
+
checks.push({
|
|
60687
|
+
name: "app-source",
|
|
60688
|
+
ok: false,
|
|
60689
|
+
required: true,
|
|
60690
|
+
message: "One or more workflow app source directories are missing or are not directories",
|
|
60691
|
+
workflows: missingAppSources.map((workflow) => workflow.name),
|
|
60692
|
+
details: {
|
|
60693
|
+
sources: missingAppSources.map((workflow) => ({
|
|
60694
|
+
workflowId: workflow.id,
|
|
60695
|
+
workflowName: workflow.name,
|
|
60696
|
+
appSourceDir: workflow.execution.appSourceDir
|
|
60697
|
+
}))
|
|
60698
|
+
}
|
|
60699
|
+
});
|
|
60700
|
+
}
|
|
60701
|
+
const { requiredMissing, optionalMissing } = collectMissingSandboxEnvRefs(workflows, env, dependencies.credentialResolver);
|
|
60702
|
+
if (requiredMissing.length > 0) {
|
|
60703
|
+
checks.push({
|
|
60704
|
+
name: "env:required",
|
|
60705
|
+
ok: false,
|
|
60706
|
+
required: true,
|
|
60707
|
+
message: "One or more required sandbox environment references could not be resolved",
|
|
60708
|
+
workflows: [...new Set(requiredMissing.map((item) => item.workflowName))],
|
|
60709
|
+
details: { missing: requiredMissing }
|
|
60710
|
+
});
|
|
60711
|
+
}
|
|
60712
|
+
if (optionalMissing.length > 0) {
|
|
60713
|
+
checks.push({
|
|
60714
|
+
name: "env:optional",
|
|
60715
|
+
ok: false,
|
|
60716
|
+
required: false,
|
|
60717
|
+
message: "One or more optional sandbox environment references are not set and will be omitted",
|
|
60718
|
+
workflows: [...new Set(optionalMissing.map((item) => item.workflowName))],
|
|
60719
|
+
details: { missing: optionalMissing }
|
|
60720
|
+
});
|
|
60721
|
+
}
|
|
60722
|
+
return {
|
|
60723
|
+
ok: checks.every((check) => check.ok || !check.required),
|
|
60724
|
+
checks
|
|
60725
|
+
};
|
|
60726
|
+
}
|
|
60500
60727
|
async function mapWithConcurrency(items, limit, worker) {
|
|
60501
60728
|
const output = new Array(items.length);
|
|
60502
60729
|
let next = 0;
|
|
@@ -60512,7 +60739,38 @@ async function mapWithConcurrency(items, limit, worker) {
|
|
|
60512
60739
|
async function runWorkflowFanout(options, dependencies = {}) {
|
|
60513
60740
|
const workers = normalizeFanoutWorkerCount(options.workers);
|
|
60514
60741
|
const workflows = resolveWorkflowFanoutSelection(options);
|
|
60515
|
-
const {
|
|
60742
|
+
const {
|
|
60743
|
+
runTestingWorkflow: runOne = runTestingWorkflow,
|
|
60744
|
+
preflight: preflightOverride,
|
|
60745
|
+
providerApiKeyResolver,
|
|
60746
|
+
commandExists,
|
|
60747
|
+
credentialResolver,
|
|
60748
|
+
env,
|
|
60749
|
+
...workflowDependencies
|
|
60750
|
+
} = dependencies;
|
|
60751
|
+
const preflight = preflightOverride ? await preflightOverride(workflows) : await checkWorkflowFanoutReadiness(workflows, {
|
|
60752
|
+
providerApiKeyResolver,
|
|
60753
|
+
commandExists,
|
|
60754
|
+
credentialResolver,
|
|
60755
|
+
env
|
|
60756
|
+
});
|
|
60757
|
+
if (!options.dryRun && !preflight.ok) {
|
|
60758
|
+
const error = `Preflight failed: ${summarizePreflightFailures(preflight)}`;
|
|
60759
|
+
return {
|
|
60760
|
+
status: "failed",
|
|
60761
|
+
workers,
|
|
60762
|
+
total: workflows.length,
|
|
60763
|
+
passed: 0,
|
|
60764
|
+
failed: workflows.length,
|
|
60765
|
+
preflight,
|
|
60766
|
+
items: workflows.map((workflow) => ({
|
|
60767
|
+
workflowId: workflow.id,
|
|
60768
|
+
workflowName: workflow.name,
|
|
60769
|
+
status: "failed",
|
|
60770
|
+
error
|
|
60771
|
+
}))
|
|
60772
|
+
};
|
|
60773
|
+
}
|
|
60516
60774
|
const items = await mapWithConcurrency(workflows, workers, async (workflow) => {
|
|
60517
60775
|
try {
|
|
60518
60776
|
const output = await runOne(workflow.id, {
|
|
@@ -60559,12 +60817,81 @@ async function runWorkflowFanout(options, dependencies = {}) {
|
|
|
60559
60817
|
total: items.length,
|
|
60560
60818
|
passed,
|
|
60561
60819
|
failed,
|
|
60562
|
-
items
|
|
60820
|
+
items,
|
|
60821
|
+
preflight
|
|
60563
60822
|
};
|
|
60564
60823
|
}
|
|
60824
|
+
function groupWorkflowsByProvider(workflows) {
|
|
60825
|
+
const byProvider = new Map;
|
|
60826
|
+
for (const workflow of workflows) {
|
|
60827
|
+
const provider = workflow.execution.provider ?? "e2b";
|
|
60828
|
+
byProvider.set(provider, [...byProvider.get(provider) ?? [], workflow]);
|
|
60829
|
+
}
|
|
60830
|
+
return byProvider;
|
|
60831
|
+
}
|
|
60832
|
+
async function resolveProviderApiKey(provider, env, resolver) {
|
|
60833
|
+
if (resolver)
|
|
60834
|
+
return resolver(provider, env);
|
|
60835
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
60836
|
+
if (envKey && env[envKey])
|
|
60837
|
+
return env[envKey];
|
|
60838
|
+
try {
|
|
60839
|
+
const mod = await import("@hasna/sandboxes");
|
|
60840
|
+
if (provider === "e2b" || provider === "daytona" || provider === "modal") {
|
|
60841
|
+
return mod.getProviderApiKey?.(provider);
|
|
60842
|
+
}
|
|
60843
|
+
} catch {}
|
|
60844
|
+
return;
|
|
60845
|
+
}
|
|
60846
|
+
function defaultCommandExists(command) {
|
|
60847
|
+
const result = spawnSync2(command, ["--version"], { encoding: "utf8" });
|
|
60848
|
+
return !result.error && result.status === 0;
|
|
60849
|
+
}
|
|
60850
|
+
function collectMissingSandboxEnvRefs(workflows, env, credentialResolver) {
|
|
60851
|
+
const requiredMissing = [];
|
|
60852
|
+
const optionalMissing = [];
|
|
60853
|
+
for (const workflow of workflows) {
|
|
60854
|
+
for (const [key, value] of Object.entries(workflow.execution.env ?? {})) {
|
|
60855
|
+
if (value.startsWith("$?")) {
|
|
60856
|
+
const name = value.slice(2).trim();
|
|
60857
|
+
if (name && env[name] === undefined) {
|
|
60858
|
+
optionalMissing.push({ workflowId: workflow.id, workflowName: workflow.name, key, reference: value });
|
|
60859
|
+
}
|
|
60860
|
+
continue;
|
|
60861
|
+
}
|
|
60862
|
+
if (!isResolvableEnvReference(value))
|
|
60863
|
+
continue;
|
|
60864
|
+
if (resolveSandboxEnvReference(value, env, credentialResolver) === null) {
|
|
60865
|
+
requiredMissing.push({ workflowId: workflow.id, workflowName: workflow.name, key, reference: value });
|
|
60866
|
+
}
|
|
60867
|
+
}
|
|
60868
|
+
}
|
|
60869
|
+
return { requiredMissing, optionalMissing };
|
|
60870
|
+
}
|
|
60871
|
+
function isResolvableEnvReference(value) {
|
|
60872
|
+
return value.startsWith("$") || value.startsWith("@secrets:");
|
|
60873
|
+
}
|
|
60874
|
+
function resolveSandboxEnvReference(value, env, credentialResolver) {
|
|
60875
|
+
if (value.startsWith("$")) {
|
|
60876
|
+
const varName = value.slice(1).trim();
|
|
60877
|
+
return varName ? env[varName] ?? null : null;
|
|
60878
|
+
}
|
|
60879
|
+
return (credentialResolver ?? resolveCredential)(value);
|
|
60880
|
+
}
|
|
60881
|
+
function summarizePreflightFailures(preflight) {
|
|
60882
|
+
const requiredFailures = preflight.checks.filter((check) => !check.ok && check.required);
|
|
60883
|
+
return requiredFailures.length > 0 ? requiredFailures.map((check) => check.message).join("; ") : "required checks did not pass";
|
|
60884
|
+
}
|
|
60885
|
+
var PROVIDER_ENV_KEYS;
|
|
60565
60886
|
var init_workflow_fanout = __esm(() => {
|
|
60566
60887
|
init_workflows();
|
|
60567
60888
|
init_workflow_runner();
|
|
60889
|
+
init_secrets_resolver();
|
|
60890
|
+
PROVIDER_ENV_KEYS = {
|
|
60891
|
+
e2b: "E2B_API_KEY",
|
|
60892
|
+
daytona: "DAYTONA_API_KEY",
|
|
60893
|
+
modal: "MODAL_TOKEN_ID"
|
|
60894
|
+
};
|
|
60568
60895
|
});
|
|
60569
60896
|
|
|
60570
60897
|
// node_modules/@ai-sdk/provider/dist/index.mjs
|
|
@@ -95224,7 +95551,7 @@ import chalk6 from "chalk";
|
|
|
95224
95551
|
// package.json
|
|
95225
95552
|
var package_default = {
|
|
95226
95553
|
name: "@hasna/testers",
|
|
95227
|
-
version: "0.0.
|
|
95554
|
+
version: "0.0.53",
|
|
95228
95555
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
95229
95556
|
type: "module",
|
|
95230
95557
|
main: "dist/index.js",
|
|
@@ -97411,7 +97738,7 @@ init_ci();
|
|
|
97411
97738
|
init_assertions();
|
|
97412
97739
|
init_paths();
|
|
97413
97740
|
init_sessions();
|
|
97414
|
-
import { existsSync as
|
|
97741
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync15 } from "fs";
|
|
97415
97742
|
|
|
97416
97743
|
// src/lib/repo-discovery.ts
|
|
97417
97744
|
init_paths();
|
|
@@ -98440,7 +98767,7 @@ var CONFIG_DIR5 = getTestersDir();
|
|
|
98440
98767
|
var CONFIG_PATH4 = join21(CONFIG_DIR5, "config.json");
|
|
98441
98768
|
function getActiveProject() {
|
|
98442
98769
|
try {
|
|
98443
|
-
if (
|
|
98770
|
+
if (existsSync20(CONFIG_PATH4)) {
|
|
98444
98771
|
const raw = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
|
|
98445
98772
|
return raw.activeProject ?? undefined;
|
|
98446
98773
|
}
|
|
@@ -99371,7 +99698,7 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
|
|
|
99371
99698
|
return;
|
|
99372
99699
|
}
|
|
99373
99700
|
const outputDir = opts.output ?? ".";
|
|
99374
|
-
if (!
|
|
99701
|
+
if (!existsSync20(outputDir)) {
|
|
99375
99702
|
mkdirSync15(outputDir, { recursive: true });
|
|
99376
99703
|
}
|
|
99377
99704
|
for (const s2 of scenarios) {
|
|
@@ -99574,11 +99901,11 @@ projectCmd.command("export-open <id>").description("Register a testers project i
|
|
|
99574
99901
|
projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name21, opts) => {
|
|
99575
99902
|
try {
|
|
99576
99903
|
const project = ensureProject(name21, process.cwd());
|
|
99577
|
-
if (!
|
|
99904
|
+
if (!existsSync20(CONFIG_DIR5)) {
|
|
99578
99905
|
mkdirSync15(CONFIG_DIR5, { recursive: true });
|
|
99579
99906
|
}
|
|
99580
99907
|
let config2 = {};
|
|
99581
|
-
if (
|
|
99908
|
+
if (existsSync20(CONFIG_PATH4)) {
|
|
99582
99909
|
try {
|
|
99583
99910
|
config2 = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
|
|
99584
99911
|
} catch {}
|
|
@@ -100193,7 +100520,7 @@ program2.command("ci [provider]").description("Print or write a CI workflow (def
|
|
|
100193
100520
|
if (opts.output) {
|
|
100194
100521
|
const outPath = resolve5(opts.output);
|
|
100195
100522
|
const outDir = outPath.replace(/\/[^/]*$/, "");
|
|
100196
|
-
if (outDir && !
|
|
100523
|
+
if (outDir && !existsSync20(outDir)) {
|
|
100197
100524
|
mkdirSync15(outDir, { recursive: true });
|
|
100198
100525
|
}
|
|
100199
100526
|
writeFileSync7(outPath, workflow, "utf-8");
|
|
@@ -100228,7 +100555,7 @@ program2.command("init").description("Initialize a new testing project").option(
|
|
|
100228
100555
|
}
|
|
100229
100556
|
if (opts.ci === "github") {
|
|
100230
100557
|
const workflowDir = join21(process.cwd(), ".github", "workflows");
|
|
100231
|
-
if (!
|
|
100558
|
+
if (!existsSync20(workflowDir)) {
|
|
100232
100559
|
mkdirSync15(workflowDir, { recursive: true });
|
|
100233
100560
|
}
|
|
100234
100561
|
const workflowPath = join21(workflowDir, "testers.yml");
|
|
@@ -100849,7 +101176,7 @@ Imported ${imported} scenarios from API spec:`));
|
|
|
100849
101176
|
}
|
|
100850
101177
|
});
|
|
100851
101178
|
var inventoryCmd = program2.command("inventory").description("Discover source-derived app route/action inventories");
|
|
100852
|
-
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) => {
|
|
101179
|
+
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-action-scenarios", "Upsert one source-derived scenario per discovered page/API action", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--create-action-workflows", "Upsert action-focused workflows for discovered action scenarios", false).option("--action-workflow-grouping <mode>", "Action workflow grouping: route or area-kind", "route").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) => {
|
|
100853
101180
|
acc.push(val);
|
|
100854
101181
|
return acc;
|
|
100855
101182
|
}, []).option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
|
|
@@ -100857,6 +101184,9 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
|
|
|
100857
101184
|
const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
|
|
100858
101185
|
const projectId = resolveProject2(opts.project) ?? undefined;
|
|
100859
101186
|
const env = parseSandboxEnv(undefined, opts.sandboxEnvOptional);
|
|
101187
|
+
if (!["route", "area-kind"].includes(opts.actionWorkflowGrouping)) {
|
|
101188
|
+
throw new Error("--action-workflow-grouping must be route or area-kind");
|
|
101189
|
+
}
|
|
100860
101190
|
const result = importNextRouteInventory2({
|
|
100861
101191
|
rootDir: root ?? process.cwd(),
|
|
100862
101192
|
appDir: opts.appDir,
|
|
@@ -100865,7 +101195,10 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
|
|
|
100865
101195
|
includeApi: opts.api !== false,
|
|
100866
101196
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
100867
101197
|
createScenarios: opts.createScenarios,
|
|
101198
|
+
createActionScenarios: opts.createActionScenarios,
|
|
100868
101199
|
createWorkflows: opts.createWorkflows,
|
|
101200
|
+
createActionWorkflows: opts.createActionWorkflows,
|
|
101201
|
+
actionWorkflowGrouping: opts.actionWorkflowGrouping,
|
|
100869
101202
|
workflowTarget: opts.workflowTarget,
|
|
100870
101203
|
workflowProvider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
|
|
100871
101204
|
workflowExecution: {
|
|
@@ -100887,6 +101220,7 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
|
|
|
100887
101220
|
log(chalk6.dim(` App: ${result.inventory.appDir}`));
|
|
100888
101221
|
log("");
|
|
100889
101222
|
log(` Routes: ${chalk6.cyan(String(result.inventory.total))} (${result.inventory.pages} pages, ${result.inventory.apiRoutes} API, ${result.inventory.dynamic} dynamic)`);
|
|
101223
|
+
log(` Actions: ${chalk6.cyan(String(result.inventory.actions))}`);
|
|
100890
101224
|
log(` Scenarios: ${chalk6.green(String(result.created))} created, ${chalk6.yellow(String(result.updated))} updated, ${chalk6.dim(String(result.deduped))} deduped`);
|
|
100891
101225
|
log(` Workflows: ${result.workflows.length}`);
|
|
100892
101226
|
log("");
|
|
@@ -100896,8 +101230,12 @@ inventoryCmd.command("next [root]").description("Discover Next.js app routes and
|
|
|
100896
101230
|
log("");
|
|
100897
101231
|
if (!opts.createScenarios)
|
|
100898
101232
|
log(chalk6.dim(" Add --create-scenarios to upsert route scenarios."));
|
|
101233
|
+
if (!opts.createActionScenarios)
|
|
101234
|
+
log(chalk6.dim(" Add --create-action-scenarios to upsert one scenario per discovered action."));
|
|
100899
101235
|
if (!opts.createWorkflows)
|
|
100900
101236
|
log(chalk6.dim(" Add --create-workflows to upsert grouped workflows."));
|
|
101237
|
+
if (!opts.createActionWorkflows)
|
|
101238
|
+
log(chalk6.dim(" Add --create-action-workflows to upsert action-focused workflows."));
|
|
100901
101239
|
log("");
|
|
100902
101240
|
} catch (error40) {
|
|
100903
101241
|
logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
|
|
@@ -102004,6 +102342,19 @@ workflowCmd.command("fanout [ids...]").description("Run multiple saved sandbox w
|
|
|
102004
102342
|
if (opts.json || opts.dryRun) {
|
|
102005
102343
|
log(JSON.stringify(result, null, 2));
|
|
102006
102344
|
} else {
|
|
102345
|
+
const preflightChecks = result.preflight?.checks ?? [];
|
|
102346
|
+
const failedRequiredChecks = preflightChecks.filter((check2) => !check2.ok && check2.required);
|
|
102347
|
+
const warnings = preflightChecks.filter((check2) => !check2.ok && !check2.required);
|
|
102348
|
+
if (failedRequiredChecks.length > 0 || warnings.length > 0) {
|
|
102349
|
+
const label = failedRequiredChecks.length > 0 ? chalk6.red("failed") : chalk6.yellow("warnings");
|
|
102350
|
+
log(chalk6.bold(`Preflight ${label}:`));
|
|
102351
|
+
for (const check2 of failedRequiredChecks) {
|
|
102352
|
+
log(` ${chalk6.red("failed")} ${check2.message}`);
|
|
102353
|
+
}
|
|
102354
|
+
for (const check2 of warnings) {
|
|
102355
|
+
log(` ${chalk6.yellow("warning")} ${check2.message}`);
|
|
102356
|
+
}
|
|
102357
|
+
}
|
|
102007
102358
|
const status = result.status === "passed" ? chalk6.green("passed") : chalk6.red("failed");
|
|
102008
102359
|
log(chalk6.bold(`Sandbox workflow fanout ${status}: ${result.passed}/${result.total} passed with ${result.workers} worker(s)`));
|
|
102009
102360
|
for (const item of result.items) {
|
|
@@ -30,6 +30,7 @@ export interface NextRouteInventory {
|
|
|
30
30
|
pages: number;
|
|
31
31
|
apiRoutes: number;
|
|
32
32
|
dynamic: number;
|
|
33
|
+
actions: number;
|
|
33
34
|
categories: Record<string, number>;
|
|
34
35
|
items: NextRouteInventoryItem[];
|
|
35
36
|
}
|
|
@@ -41,7 +42,10 @@ export interface ImportNextRouteInventoryOptions {
|
|
|
41
42
|
includeApi?: boolean;
|
|
42
43
|
limit?: number;
|
|
43
44
|
createScenarios?: boolean;
|
|
45
|
+
createActionScenarios?: boolean;
|
|
44
46
|
createWorkflows?: boolean;
|
|
47
|
+
createActionWorkflows?: boolean;
|
|
48
|
+
actionWorkflowGrouping?: "route" | "area-kind";
|
|
45
49
|
workflowTarget?: "local" | "sandbox";
|
|
46
50
|
workflowProvider?: string;
|
|
47
51
|
workflowExecution?: Partial<WorkflowExecutionInput>;
|
|
@@ -52,6 +56,7 @@ export interface ImportNextRouteInventoryResult {
|
|
|
52
56
|
updated: number;
|
|
53
57
|
deduped: number;
|
|
54
58
|
scenarios: Scenario[];
|
|
59
|
+
actionScenarios: Scenario[];
|
|
55
60
|
workflows: TestingWorkflow[];
|
|
56
61
|
}
|
|
57
62
|
export declare function discoverNextRouteInventory(options: {
|
|
@@ -62,5 +67,6 @@ export declare function discoverNextRouteInventory(options: {
|
|
|
62
67
|
limit?: number;
|
|
63
68
|
}): NextRouteInventory;
|
|
64
69
|
export declare function scenarioInputForNextRoute(item: NextRouteInventoryItem, projectId?: string): CreateScenarioInput;
|
|
70
|
+
export declare function scenarioInputsForNextRouteActions(item: NextRouteInventoryItem, projectId?: string): CreateScenarioInput[];
|
|
65
71
|
export declare function importNextRouteInventory(options: ImportNextRouteInventoryOptions): ImportNextRouteInventoryResult;
|
|
66
72
|
//# sourceMappingURL=next-route-inventory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"AAKA,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,
|
|
1
|
+
{"version":3,"file":"next-route-inventory.d.ts","sourceRoot":"","sources":["../../src/lib/next-route-inventory.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,11 +26,37 @@ export interface WorkflowFanoutResult {
|
|
|
26
26
|
passed: number;
|
|
27
27
|
failed: number;
|
|
28
28
|
items: WorkflowFanoutItem[];
|
|
29
|
+
preflight?: WorkflowFanoutPreflightResult;
|
|
29
30
|
}
|
|
30
31
|
export interface WorkflowFanoutDependencies extends WorkflowRunnerDependencies {
|
|
31
32
|
runTestingWorkflow?: typeof runTestingWorkflow;
|
|
33
|
+
preflight?: (workflows: TestingWorkflow[]) => WorkflowFanoutPreflightResult | Promise<WorkflowFanoutPreflightResult>;
|
|
34
|
+
providerApiKeyResolver?: (provider: string, env: Record<string, string | undefined>) => string | undefined | Promise<string | undefined>;
|
|
35
|
+
commandExists?: (command: string) => boolean;
|
|
36
|
+
credentialResolver?: (value: string) => string | null;
|
|
37
|
+
env?: Record<string, string | undefined>;
|
|
38
|
+
}
|
|
39
|
+
export interface WorkflowFanoutPreflightCheck {
|
|
40
|
+
name: string;
|
|
41
|
+
ok: boolean;
|
|
42
|
+
required: boolean;
|
|
43
|
+
message: string;
|
|
44
|
+
workflows?: string[];
|
|
45
|
+
details?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
export interface WorkflowFanoutPreflightResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
checks: WorkflowFanoutPreflightCheck[];
|
|
50
|
+
}
|
|
51
|
+
interface WorkflowFanoutPreflightDependencies {
|
|
52
|
+
providerApiKeyResolver?: WorkflowFanoutDependencies["providerApiKeyResolver"];
|
|
53
|
+
commandExists?: WorkflowFanoutDependencies["commandExists"];
|
|
54
|
+
credentialResolver?: WorkflowFanoutDependencies["credentialResolver"];
|
|
55
|
+
env?: Record<string, string | undefined>;
|
|
32
56
|
}
|
|
33
57
|
export declare function normalizeFanoutWorkerCount(value: number | undefined): number;
|
|
34
58
|
export declare function resolveWorkflowFanoutSelection(options: Pick<WorkflowFanoutOptions, "workflowIds" | "projectId" | "tags" | "includeDisabled">): TestingWorkflow[];
|
|
59
|
+
export declare function checkWorkflowFanoutReadiness(workflows: TestingWorkflow[], dependencies?: WorkflowFanoutPreflightDependencies): Promise<WorkflowFanoutPreflightResult>;
|
|
35
60
|
export declare function runWorkflowFanout(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutResult>;
|
|
61
|
+
export {};
|
|
36
62
|
//# sourceMappingURL=workflow-fanout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"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"}
|
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.
|
|
55
|
+
version: "0.0.53",
|
|
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",
|
package/dist/server/index.js
CHANGED
|
@@ -47090,7 +47090,7 @@ import { join as join14 } from "path";
|
|
|
47090
47090
|
// package.json
|
|
47091
47091
|
var package_default = {
|
|
47092
47092
|
name: "@hasna/testers",
|
|
47093
|
-
version: "0.0.
|
|
47093
|
+
version: "0.0.53",
|
|
47094
47094
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
47095
47095
|
type: "module",
|
|
47096
47096
|
main: "dist/index.js",
|