@hasna/testers 0.0.45 → 0.0.46
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 +192 -67
- package/dist/index.js +160 -42
- package/dist/lib/workflow-runner.d.ts +6 -0
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +135 -16
- package/dist/server/index.js +130 -12
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,12 @@ function workflowExecutionFromValue(value) {
|
|
|
59
59
|
const sandboxSyncStrategy = syncStrategyValue(input["sandboxSyncStrategy"]);
|
|
60
60
|
const setupCommand = stringValue(input["setupCommand"]);
|
|
61
61
|
const packageSpec = stringValue(input["packageSpec"]);
|
|
62
|
+
const appSourceDir = stringValue(input["appSourceDir"]);
|
|
63
|
+
const appRemoteDir = stringValue(input["appRemoteDir"]);
|
|
64
|
+
const appStartCommand = stringValue(input["appStartCommand"]);
|
|
65
|
+
const appUrl = stringValue(input["appUrl"]);
|
|
66
|
+
const appWaitUrl = stringValue(input["appWaitUrl"]);
|
|
67
|
+
const appWaitTimeoutMs = numberValue(input["appWaitTimeoutMs"]);
|
|
62
68
|
const timeoutMs = numberValue(input["timeoutMs"]);
|
|
63
69
|
const env = stringMap(input["env"]);
|
|
64
70
|
return {
|
|
@@ -70,6 +76,12 @@ function workflowExecutionFromValue(value) {
|
|
|
70
76
|
...sandboxSyncStrategy ? { sandboxSyncStrategy } : {},
|
|
71
77
|
...setupCommand ? { setupCommand } : {},
|
|
72
78
|
...packageSpec ? { packageSpec } : {},
|
|
79
|
+
...appSourceDir ? { appSourceDir } : {},
|
|
80
|
+
...appRemoteDir ? { appRemoteDir } : {},
|
|
81
|
+
...appStartCommand ? { appStartCommand } : {},
|
|
82
|
+
...appUrl ? { appUrl } : {},
|
|
83
|
+
...appWaitUrl ? { appWaitUrl } : {},
|
|
84
|
+
...appWaitTimeoutMs !== undefined ? { appWaitTimeoutMs } : {},
|
|
73
85
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
74
86
|
...env ? { env } : {}
|
|
75
87
|
};
|
|
@@ -17200,9 +17212,20 @@ function estimateCost(model, tokens) {
|
|
|
17200
17212
|
}
|
|
17201
17213
|
// src/lib/workflow-runner.ts
|
|
17202
17214
|
init_database();
|
|
17203
|
-
import {
|
|
17215
|
+
import { spawnSync } from "child_process";
|
|
17216
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, mkdtempSync, rmSync, statSync } from "fs";
|
|
17204
17217
|
import { tmpdir } from "os";
|
|
17205
|
-
import { join as join14 } from "path";
|
|
17218
|
+
import { join as join14, posix as pathPosix } from "path";
|
|
17219
|
+
var APP_SOURCE_EXCLUDES = [
|
|
17220
|
+
"node_modules",
|
|
17221
|
+
".git",
|
|
17222
|
+
"dist",
|
|
17223
|
+
".next",
|
|
17224
|
+
".turbo",
|
|
17225
|
+
".cache",
|
|
17226
|
+
".venv",
|
|
17227
|
+
"__pycache__"
|
|
17228
|
+
];
|
|
17206
17229
|
function buildWorkflowRunPlan(workflow, options) {
|
|
17207
17230
|
const runOptions = {
|
|
17208
17231
|
url: options.url,
|
|
@@ -17244,10 +17267,16 @@ function createWorkflowDatabaseBundle(workflow, plan) {
|
|
|
17244
17267
|
if (!plan.sandbox)
|
|
17245
17268
|
throw new Error(`Workflow is not configured for sandbox execution: ${workflow.name}`);
|
|
17246
17269
|
const localDir = mkdtempSync(join14(tmpdir(), `testers-workflow-${workflow.id.slice(0, 8)}-`));
|
|
17247
|
-
|
|
17270
|
+
const stateDir = join14(localDir, ".testers-state");
|
|
17271
|
+
mkdirSync9(stateDir, { recursive: true });
|
|
17272
|
+
writeDatabaseSnapshot(join14(stateDir, "testers.db"));
|
|
17273
|
+
if (plan.sandbox.appSourceDir && plan.sandbox.appRemoteDir) {
|
|
17274
|
+
const relativeAppDir = relativeRemotePath(plan.sandbox.remoteDir, plan.sandbox.appRemoteDir);
|
|
17275
|
+
copyAppSource(plan.sandbox.appSourceDir, join14(localDir, relativeAppDir));
|
|
17276
|
+
}
|
|
17248
17277
|
return {
|
|
17249
17278
|
localDir,
|
|
17250
|
-
remoteDir: plan.sandbox.
|
|
17279
|
+
remoteDir: plan.sandbox.remoteDir,
|
|
17251
17280
|
cleanup: () => rmSync(localDir, { recursive: true, force: true })
|
|
17252
17281
|
};
|
|
17253
17282
|
}
|
|
@@ -17258,15 +17287,63 @@ function validatePersonaIds(workflow) {
|
|
|
17258
17287
|
}
|
|
17259
17288
|
}
|
|
17260
17289
|
}
|
|
17290
|
+
function relativeRemotePath(remoteDir, remoteChildDir) {
|
|
17291
|
+
if (!remoteChildDir.startsWith("/")) {
|
|
17292
|
+
const relative3 = remoteChildDir.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
17293
|
+
if (!relative3 || relative3 === ".") {
|
|
17294
|
+
throw new Error("Sandbox app remote directory must be a child directory, not the workflow root");
|
|
17295
|
+
}
|
|
17296
|
+
return relative3;
|
|
17297
|
+
}
|
|
17298
|
+
const base = remoteDir.replace(/\/+$/, "") || "/";
|
|
17299
|
+
const child = remoteChildDir.replace(/\/+$/, "") || "/";
|
|
17300
|
+
const relative2 = pathPosix.relative(base, child);
|
|
17301
|
+
if (!relative2 || relative2 === "." || relative2.startsWith("..") || pathPosix.isAbsolute(relative2)) {
|
|
17302
|
+
throw new Error(`Sandbox app remote directory must be inside the workflow remote directory (${remoteDir}): ${remoteChildDir}`);
|
|
17303
|
+
}
|
|
17304
|
+
return relative2;
|
|
17305
|
+
}
|
|
17306
|
+
function copyAppSource(sourceDir, targetDir) {
|
|
17307
|
+
if (!existsSync11(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
17308
|
+
throw new Error(`Sandbox app source directory does not exist or is not a directory: ${sourceDir}`);
|
|
17309
|
+
}
|
|
17310
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
17311
|
+
const result = spawnSync("rsync", [
|
|
17312
|
+
"-a",
|
|
17313
|
+
"--delete",
|
|
17314
|
+
...APP_SOURCE_EXCLUDES.flatMap((item) => ["--exclude", item]),
|
|
17315
|
+
`${sourceDir.replace(/\/+$/, "")}/`,
|
|
17316
|
+
`${targetDir.replace(/\/+$/, "")}/`
|
|
17317
|
+
], { encoding: "utf8" });
|
|
17318
|
+
if (result.error) {
|
|
17319
|
+
throw new Error(`Failed to rsync sandbox app source: ${result.error.message}`);
|
|
17320
|
+
}
|
|
17321
|
+
if (result.status !== 0) {
|
|
17322
|
+
throw new Error(`Failed to rsync sandbox app source (${result.status}): ${result.stderr.trim()}`);
|
|
17323
|
+
}
|
|
17324
|
+
}
|
|
17325
|
+
function writeDatabaseSnapshot(targetPath) {
|
|
17326
|
+
getDatabase().exec(`VACUUM INTO ${sqlString(targetPath)}`);
|
|
17327
|
+
}
|
|
17328
|
+
function sqlString(value) {
|
|
17329
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
17330
|
+
}
|
|
17261
17331
|
function buildSandboxPlan(workflow, execution, runOptions) {
|
|
17262
17332
|
const remoteDir = execution.sandboxRemoteDir ?? `/tmp/testers-workflow-${workflow.id.slice(0, 8)}`;
|
|
17263
17333
|
const stateRemoteDir = `${remoteDir.replace(/\/+$/, "")}/.testers-state`;
|
|
17334
|
+
const appRemoteDir = execution.appSourceDir ? execution.appRemoteDir ?? `${remoteDir.replace(/\/+$/, "")}/app` : execution.appRemoteDir;
|
|
17264
17335
|
return {
|
|
17265
17336
|
provider: execution.provider,
|
|
17266
17337
|
image: execution.sandboxImage,
|
|
17267
17338
|
name: `testers-${workflow.id.slice(0, 8)}`,
|
|
17268
17339
|
remoteDir,
|
|
17269
17340
|
stateRemoteDir,
|
|
17341
|
+
...execution.appSourceDir ? { appSourceDir: execution.appSourceDir } : {},
|
|
17342
|
+
...appRemoteDir ? { appRemoteDir } : {},
|
|
17343
|
+
...execution.appStartCommand ? { appStartCommand: execution.appStartCommand } : {},
|
|
17344
|
+
...execution.appUrl ? { appUrl: execution.appUrl } : {},
|
|
17345
|
+
...execution.appWaitUrl ? { appWaitUrl: execution.appWaitUrl } : {},
|
|
17346
|
+
...execution.appWaitTimeoutMs !== undefined ? { appWaitTimeoutMs: execution.appWaitTimeoutMs } : {},
|
|
17270
17347
|
cleanup: execution.sandboxCleanup ?? "delete",
|
|
17271
17348
|
syncStrategy: execution.sandboxSyncStrategy ?? "rsync",
|
|
17272
17349
|
timeoutMs: execution.timeoutMs,
|
|
@@ -17274,6 +17351,12 @@ function buildSandboxPlan(workflow, execution, runOptions) {
|
|
|
17274
17351
|
command: buildSandboxCommand({
|
|
17275
17352
|
runOptions,
|
|
17276
17353
|
remoteDir,
|
|
17354
|
+
stateRemoteDir,
|
|
17355
|
+
appRemoteDir,
|
|
17356
|
+
appStartCommand: execution.appStartCommand,
|
|
17357
|
+
appUrl: execution.appUrl,
|
|
17358
|
+
appWaitUrl: execution.appWaitUrl,
|
|
17359
|
+
appWaitTimeoutMs: execution.appWaitTimeoutMs,
|
|
17277
17360
|
dbPath: `${stateRemoteDir}/testers.db`,
|
|
17278
17361
|
setupCommand: execution.setupCommand,
|
|
17279
17362
|
packageSpec: execution.packageSpec ?? "@hasna/testers"
|
|
@@ -17281,11 +17364,12 @@ function buildSandboxPlan(workflow, execution, runOptions) {
|
|
|
17281
17364
|
};
|
|
17282
17365
|
}
|
|
17283
17366
|
function buildSandboxCommand(input) {
|
|
17367
|
+
const targetUrl = input.appUrl ?? input.runOptions.url;
|
|
17284
17368
|
const args = [
|
|
17285
17369
|
"bunx",
|
|
17286
17370
|
input.packageSpec,
|
|
17287
17371
|
"run",
|
|
17288
|
-
|
|
17372
|
+
targetUrl,
|
|
17289
17373
|
...input.runOptions.scenarioIds?.length ? ["--scenario", input.runOptions.scenarioIds.join(",")] : [],
|
|
17290
17374
|
...input.runOptions.tags?.length ? input.runOptions.tags.flatMap((tag) => ["--tag", tag]) : [],
|
|
17291
17375
|
...input.runOptions.priority ? ["--priority", input.runOptions.priority] : [],
|
|
@@ -17301,12 +17385,46 @@ function buildSandboxCommand(input) {
|
|
|
17301
17385
|
return [
|
|
17302
17386
|
"set -euo pipefail",
|
|
17303
17387
|
`mkdir -p ${shellQuote(input.remoteDir)}`,
|
|
17304
|
-
`
|
|
17388
|
+
`mkdir -p ${shellQuote(input.stateRemoteDir)}`,
|
|
17389
|
+
input.appRemoteDir ? `mkdir -p ${shellQuote(input.appRemoteDir)}` : undefined,
|
|
17390
|
+
`cd ${shellQuote(input.appRemoteDir ?? input.remoteDir)}`,
|
|
17305
17391
|
input.setupCommand,
|
|
17392
|
+
buildAppStartCommand(input),
|
|
17306
17393
|
`HASNA_TESTERS_DB_PATH=${shellQuote(input.dbPath)} ${args.map(shellQuote).join(" ")}`
|
|
17307
17394
|
].filter(Boolean).join(`
|
|
17308
17395
|
`);
|
|
17309
17396
|
}
|
|
17397
|
+
function buildAppStartCommand(input) {
|
|
17398
|
+
if (!input.appStartCommand)
|
|
17399
|
+
return;
|
|
17400
|
+
const waitUrl = input.appWaitUrl ?? input.appUrl ?? input.runOptions.url;
|
|
17401
|
+
const waitTimeoutMs = input.appWaitTimeoutMs ?? 120000;
|
|
17402
|
+
return [
|
|
17403
|
+
`( ${input.appStartCommand} ) > ${shellQuote(`${input.stateRemoteDir}/app.log`)} 2>&1 &`,
|
|
17404
|
+
"APP_PID=$!",
|
|
17405
|
+
`echo "$APP_PID" > ${shellQuote(`${input.stateRemoteDir}/app.pid`)}`,
|
|
17406
|
+
`trap 'kill "$APP_PID" 2>/dev/null || true' EXIT`,
|
|
17407
|
+
waitUrl ? buildWaitForUrlCommand(waitUrl, waitTimeoutMs) : undefined
|
|
17408
|
+
].filter(Boolean).join(`
|
|
17409
|
+
`);
|
|
17410
|
+
}
|
|
17411
|
+
function buildWaitForUrlCommand(url, timeoutMs) {
|
|
17412
|
+
const script = `
|
|
17413
|
+
const url = ${JSON.stringify(url)};
|
|
17414
|
+
const timeoutMs = ${JSON.stringify(timeoutMs)};
|
|
17415
|
+
const deadline = Date.now() + timeoutMs;
|
|
17416
|
+
while (Date.now() <= deadline) {
|
|
17417
|
+
try {
|
|
17418
|
+
const response = await fetch(url);
|
|
17419
|
+
if (response.status >= 200 && response.status < 500) process.exit(0);
|
|
17420
|
+
} catch {}
|
|
17421
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
17422
|
+
}
|
|
17423
|
+
console.error(\`Timed out waiting for \${url} after \${timeoutMs}ms\`);
|
|
17424
|
+
process.exit(1);
|
|
17425
|
+
`.trim();
|
|
17426
|
+
return `bun -e ${shellQuote(script)}`;
|
|
17427
|
+
}
|
|
17310
17428
|
async function runViaSandbox(plan, dependencies) {
|
|
17311
17429
|
if (!plan.sandbox)
|
|
17312
17430
|
throw new Error("Workflow does not have a sandbox plan");
|
|
@@ -17829,11 +17947,11 @@ class Scheduler {
|
|
|
17829
17947
|
}
|
|
17830
17948
|
// src/lib/init.ts
|
|
17831
17949
|
init_paths();
|
|
17832
|
-
import { existsSync as
|
|
17950
|
+
import { existsSync as existsSync12, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync10 } from "fs";
|
|
17833
17951
|
import { join as join15, basename } from "path";
|
|
17834
17952
|
function detectFramework(dir) {
|
|
17835
17953
|
const pkgPath = join15(dir, "package.json");
|
|
17836
|
-
if (!
|
|
17954
|
+
if (!existsSync12(pkgPath))
|
|
17837
17955
|
return null;
|
|
17838
17956
|
let pkg;
|
|
17839
17957
|
try {
|
|
@@ -18061,17 +18179,17 @@ function initProject(options) {
|
|
|
18061
18179
|
}).filter((s) => s !== null);
|
|
18062
18180
|
const configDir = getTestersDir();
|
|
18063
18181
|
const configPath = join15(configDir, "config.json");
|
|
18064
|
-
if (!
|
|
18065
|
-
|
|
18182
|
+
if (!existsSync12(configDir)) {
|
|
18183
|
+
mkdirSync10(configDir, { recursive: true });
|
|
18066
18184
|
}
|
|
18067
18185
|
let config = {};
|
|
18068
|
-
if (
|
|
18186
|
+
if (existsSync12(configPath)) {
|
|
18069
18187
|
try {
|
|
18070
18188
|
config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
18071
18189
|
} catch {}
|
|
18072
18190
|
}
|
|
18073
18191
|
config.activeProject = project.id;
|
|
18074
|
-
|
|
18192
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
18075
18193
|
return { project, scenarios, framework, url };
|
|
18076
18194
|
}
|
|
18077
18195
|
// src/lib/smoke.ts
|
|
@@ -19701,9 +19819,9 @@ function deleteAuthPreset(name) {
|
|
|
19701
19819
|
}
|
|
19702
19820
|
// src/lib/report.ts
|
|
19703
19821
|
init_runs();
|
|
19704
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
19822
|
+
import { readFileSync as readFileSync4, existsSync as existsSync13 } from "fs";
|
|
19705
19823
|
function imageToBase64(filePath) {
|
|
19706
|
-
if (!filePath || !
|
|
19824
|
+
if (!filePath || !existsSync13(filePath))
|
|
19707
19825
|
return "";
|
|
19708
19826
|
try {
|
|
19709
19827
|
const buffer = readFileSync4(filePath);
|
|
@@ -19992,14 +20110,14 @@ async function startWatcher(options) {
|
|
|
19992
20110
|
}
|
|
19993
20111
|
// src/lib/repo-discovery.ts
|
|
19994
20112
|
init_paths();
|
|
19995
|
-
import { existsSync as
|
|
20113
|
+
import { existsSync as existsSync14, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync4, mkdirSync as mkdirSync11, unlinkSync } from "fs";
|
|
19996
20114
|
import { createHash } from "crypto";
|
|
19997
20115
|
import { join as join16, resolve as resolve2, relative as relative2 } from "path";
|
|
19998
20116
|
function getCacheDir() {
|
|
19999
20117
|
const testersDir = getTestersDir();
|
|
20000
20118
|
const cacheDir = join16(testersDir, "repo-index");
|
|
20001
|
-
if (!
|
|
20002
|
-
|
|
20119
|
+
if (!existsSync14(cacheDir)) {
|
|
20120
|
+
mkdirSync11(cacheDir, { recursive: true });
|
|
20003
20121
|
}
|
|
20004
20122
|
return cacheDir;
|
|
20005
20123
|
}
|
|
@@ -20012,10 +20130,10 @@ function getCachePath(repoPath) {
|
|
|
20012
20130
|
function isCacheStale(cached, repoPath) {
|
|
20013
20131
|
for (const spec of cached.specs) {
|
|
20014
20132
|
const fullPath = join16(repoPath, spec.file);
|
|
20015
|
-
if (!
|
|
20133
|
+
if (!existsSync14(fullPath))
|
|
20016
20134
|
return true;
|
|
20017
20135
|
try {
|
|
20018
|
-
const stat =
|
|
20136
|
+
const stat = statSync2(fullPath);
|
|
20019
20137
|
if (stat.mtimeMs !== spec.mtimeMs)
|
|
20020
20138
|
return true;
|
|
20021
20139
|
} catch {
|
|
@@ -20024,10 +20142,10 @@ function isCacheStale(cached, repoPath) {
|
|
|
20024
20142
|
}
|
|
20025
20143
|
if (cached.configPath) {
|
|
20026
20144
|
const configFullPath = join16(repoPath, cached.configPath);
|
|
20027
|
-
if (!
|
|
20145
|
+
if (!existsSync14(configFullPath))
|
|
20028
20146
|
return true;
|
|
20029
20147
|
try {
|
|
20030
|
-
|
|
20148
|
+
statSync2(configFullPath);
|
|
20031
20149
|
const age = Date.now() - new Date(cached.snapshotAt).getTime();
|
|
20032
20150
|
if (age > 3600000)
|
|
20033
20151
|
return true;
|
|
@@ -20039,7 +20157,7 @@ function isCacheStale(cached, repoPath) {
|
|
|
20039
20157
|
}
|
|
20040
20158
|
function loadCache(repoPath) {
|
|
20041
20159
|
const cachePath = getCachePath(repoPath);
|
|
20042
|
-
if (!
|
|
20160
|
+
if (!existsSync14(cachePath))
|
|
20043
20161
|
return null;
|
|
20044
20162
|
try {
|
|
20045
20163
|
const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
@@ -20050,14 +20168,14 @@ function loadCache(repoPath) {
|
|
|
20050
20168
|
}
|
|
20051
20169
|
function saveCache(snapshot) {
|
|
20052
20170
|
const cachePath = getCachePath(snapshot.repoPath);
|
|
20053
|
-
|
|
20171
|
+
writeFileSync4(cachePath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20054
20172
|
}
|
|
20055
20173
|
function detectPackageManager(repoPath) {
|
|
20056
20174
|
const result = {
|
|
20057
|
-
npm:
|
|
20058
|
-
yarn:
|
|
20059
|
-
pnpm:
|
|
20060
|
-
bun:
|
|
20175
|
+
npm: existsSync14(join16(repoPath, "package-lock.json")),
|
|
20176
|
+
yarn: existsSync14(join16(repoPath, "yarn.lock")),
|
|
20177
|
+
pnpm: existsSync14(join16(repoPath, "pnpm-lock.yaml")),
|
|
20178
|
+
bun: existsSync14(join16(repoPath, "bun.lockb")) || existsSync14(join16(repoPath, "bun.lock")),
|
|
20061
20179
|
preferred: "npm"
|
|
20062
20180
|
};
|
|
20063
20181
|
if (result.bun)
|
|
@@ -20072,7 +20190,7 @@ function detectPackageManager(repoPath) {
|
|
|
20072
20190
|
}
|
|
20073
20191
|
function detectDevScripts(repoPath) {
|
|
20074
20192
|
const pkgPath = join16(repoPath, "package.json");
|
|
20075
|
-
if (!
|
|
20193
|
+
if (!existsSync14(pkgPath)) {
|
|
20076
20194
|
return { dev: null, test: null, seed: null, build: null };
|
|
20077
20195
|
}
|
|
20078
20196
|
let scripts;
|
|
@@ -20098,7 +20216,7 @@ function findPlaywrightConfig(repoPath) {
|
|
|
20098
20216
|
"playwright-ct.config.js"
|
|
20099
20217
|
];
|
|
20100
20218
|
for (const name of candidates) {
|
|
20101
|
-
if (
|
|
20219
|
+
if (existsSync14(join16(repoPath, name)))
|
|
20102
20220
|
return name;
|
|
20103
20221
|
}
|
|
20104
20222
|
return null;
|
|
@@ -20157,7 +20275,7 @@ function findSpecFiles(repoPath, globPatterns) {
|
|
|
20157
20275
|
const dirsToSearch = ["", ".", "tests", "e2e", "test", "__tests__", "specs", "src"];
|
|
20158
20276
|
for (const dir of dirsToSearch) {
|
|
20159
20277
|
const searchDir = dir ? join16(repoPath, dir) : repoPath;
|
|
20160
|
-
if (!
|
|
20278
|
+
if (!existsSync14(searchDir))
|
|
20161
20279
|
continue;
|
|
20162
20280
|
try {
|
|
20163
20281
|
const files = walkDir(searchDir);
|
|
@@ -20169,7 +20287,7 @@ function findSpecFiles(repoPath, globPatterns) {
|
|
|
20169
20287
|
seen.add(relativePath);
|
|
20170
20288
|
const content = readFileSync5(file, "utf-8");
|
|
20171
20289
|
const contentHash = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
20172
|
-
const stat =
|
|
20290
|
+
const stat = statSync2(file);
|
|
20173
20291
|
specs.push({
|
|
20174
20292
|
file: relativePath,
|
|
20175
20293
|
fromGlob: pattern,
|
|
@@ -20209,7 +20327,7 @@ function matchesGlob(filePath, pattern) {
|
|
|
20209
20327
|
}
|
|
20210
20328
|
function detectSuggestedUrl(repoPath) {
|
|
20211
20329
|
const pkgPath = join16(repoPath, "package.json");
|
|
20212
|
-
if (!
|
|
20330
|
+
if (!existsSync14(pkgPath))
|
|
20213
20331
|
return null;
|
|
20214
20332
|
try {
|
|
20215
20333
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
@@ -20229,10 +20347,10 @@ function detectSuggestedUrl(repoPath) {
|
|
|
20229
20347
|
}
|
|
20230
20348
|
function checkPlaywrightBrowserInstalled(repoPath) {
|
|
20231
20349
|
const cacheDir = join16(repoPath, "node_modules", ".cache", "ms-playwright");
|
|
20232
|
-
if (
|
|
20350
|
+
if (existsSync14(cacheDir))
|
|
20233
20351
|
return true;
|
|
20234
20352
|
const globalCache = join16(repoPath, ".cache", "ms-playwright");
|
|
20235
|
-
if (
|
|
20353
|
+
if (existsSync14(globalCache))
|
|
20236
20354
|
return true;
|
|
20237
20355
|
return false;
|
|
20238
20356
|
}
|
|
@@ -20272,7 +20390,7 @@ function discoverRepo(opts) {
|
|
|
20272
20390
|
const specs = findSpecFiles(repoPath, globPatterns);
|
|
20273
20391
|
const packageManager = detectPackageManager(repoPath);
|
|
20274
20392
|
const devScripts = detectDevScripts(repoPath);
|
|
20275
|
-
const playwrightInstalled =
|
|
20393
|
+
const playwrightInstalled = existsSync14(join16(repoPath, "node_modules", "playwright")) || existsSync14(join16(repoPath, "node_modules", "@playwright", "test"));
|
|
20276
20394
|
const browsersInstalled = checkPlaywrightBrowserInstalled(repoPath);
|
|
20277
20395
|
const configExists = configPath !== null;
|
|
20278
20396
|
const specsFound = specs.length > 0;
|
|
@@ -20331,11 +20449,11 @@ function discoverRepo(opts) {
|
|
|
20331
20449
|
}
|
|
20332
20450
|
function clearDiscoveryCache(repoPath) {
|
|
20333
20451
|
const cacheDir = getCacheDir();
|
|
20334
|
-
if (!
|
|
20452
|
+
if (!existsSync14(cacheDir))
|
|
20335
20453
|
return;
|
|
20336
20454
|
if (repoPath) {
|
|
20337
20455
|
const cachePath = getCachePath(repoPath);
|
|
20338
|
-
if (
|
|
20456
|
+
if (existsSync14(cachePath)) {
|
|
20339
20457
|
unlinkSync(cachePath);
|
|
20340
20458
|
}
|
|
20341
20459
|
} else {
|
|
@@ -20348,7 +20466,7 @@ function clearDiscoveryCache(repoPath) {
|
|
|
20348
20466
|
}
|
|
20349
20467
|
function getDiscoveryCacheInfo(repoPath) {
|
|
20350
20468
|
const cachePath = getCachePath(repoPath);
|
|
20351
|
-
if (!
|
|
20469
|
+
if (!existsSync14(cachePath))
|
|
20352
20470
|
return null;
|
|
20353
20471
|
const cached = loadCache(repoPath);
|
|
20354
20472
|
if (!cached)
|
|
@@ -20364,11 +20482,11 @@ init_runs();
|
|
|
20364
20482
|
init_database();
|
|
20365
20483
|
init_paths();
|
|
20366
20484
|
import { execSync as execSync2 } from "child_process";
|
|
20367
|
-
import { existsSync as
|
|
20485
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
20368
20486
|
import { join as join17 } from "path";
|
|
20369
20487
|
function resolvePlaywrightCmd(repoPath) {
|
|
20370
20488
|
const localPw = join17(repoPath, "node_modules", ".bin", "playwright");
|
|
20371
|
-
if (
|
|
20489
|
+
if (existsSync15(localPw)) {
|
|
20372
20490
|
return [localPw, "test"];
|
|
20373
20491
|
}
|
|
20374
20492
|
return ["npx", "playwright", "test"];
|
|
@@ -20564,9 +20682,9 @@ async function runRepoTests(opts) {
|
|
|
20564
20682
|
const resultRecord = { id: resultId };
|
|
20565
20683
|
if (result.stdout || result.stderr) {
|
|
20566
20684
|
const reportersDir = join17(getTestersDir(), "repo-run-output");
|
|
20567
|
-
|
|
20685
|
+
mkdirSync12(reportersDir, { recursive: true });
|
|
20568
20686
|
const outputFile = join17(reportersDir, `${resultRecord.id}.log`);
|
|
20569
|
-
|
|
20687
|
+
writeFileSync5(outputFile, `=== stdout ===
|
|
20570
20688
|
${result.stdout}
|
|
20571
20689
|
|
|
20572
20690
|
=== stderr ===
|
|
@@ -14,6 +14,12 @@ export interface WorkflowSandboxPlan {
|
|
|
14
14
|
name: string;
|
|
15
15
|
remoteDir: string;
|
|
16
16
|
stateRemoteDir: string;
|
|
17
|
+
appSourceDir?: string;
|
|
18
|
+
appRemoteDir?: string;
|
|
19
|
+
appStartCommand?: string;
|
|
20
|
+
appUrl?: string;
|
|
21
|
+
appWaitUrl?: string;
|
|
22
|
+
appWaitTimeoutMs?: number;
|
|
17
23
|
command: string;
|
|
18
24
|
cleanup: WorkflowSandboxCleanup;
|
|
19
25
|
syncStrategy: WorkflowSandboxSyncStrategy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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"}
|