@hasna/testers 0.0.45 → 0.0.47
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 +260 -74
- package/dist/index.js +174 -43
- package/dist/lib/workflow-runner.d.ts +6 -0
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +150 -17
- package/dist/server/index.js +144 -13
- 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");
|
|
@@ -17327,7 +17445,7 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17327
17445
|
workflowId: plan.workflow.id,
|
|
17328
17446
|
workflowName: plan.workflow.name
|
|
17329
17447
|
},
|
|
17330
|
-
sandboxEnvVars: plan.sandbox.env,
|
|
17448
|
+
sandboxEnvVars: resolveSandboxEnv(plan.sandbox.env),
|
|
17331
17449
|
cleanup: plan.sandbox.cleanup,
|
|
17332
17450
|
upload: {
|
|
17333
17451
|
localDir: bundle.localDir,
|
|
@@ -17353,6 +17471,19 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17353
17471
|
bundle.cleanup?.();
|
|
17354
17472
|
}
|
|
17355
17473
|
}
|
|
17474
|
+
function resolveSandboxEnv(env2) {
|
|
17475
|
+
if (!env2 || Object.keys(env2).length === 0)
|
|
17476
|
+
return;
|
|
17477
|
+
const resolved = {};
|
|
17478
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
17479
|
+
const resolvedValue = resolveCredential(value);
|
|
17480
|
+
if (resolvedValue === null) {
|
|
17481
|
+
throw new Error(`Missing sandbox env value for ${key}`);
|
|
17482
|
+
}
|
|
17483
|
+
resolved[key] = resolvedValue;
|
|
17484
|
+
}
|
|
17485
|
+
return resolved;
|
|
17486
|
+
}
|
|
17356
17487
|
async function resolveSandboxesRuntime(dependencies) {
|
|
17357
17488
|
if (dependencies.sandboxes)
|
|
17358
17489
|
return dependencies.sandboxes;
|
|
@@ -17829,11 +17960,11 @@ class Scheduler {
|
|
|
17829
17960
|
}
|
|
17830
17961
|
// src/lib/init.ts
|
|
17831
17962
|
init_paths();
|
|
17832
|
-
import { existsSync as
|
|
17963
|
+
import { existsSync as existsSync12, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync10 } from "fs";
|
|
17833
17964
|
import { join as join15, basename } from "path";
|
|
17834
17965
|
function detectFramework(dir) {
|
|
17835
17966
|
const pkgPath = join15(dir, "package.json");
|
|
17836
|
-
if (!
|
|
17967
|
+
if (!existsSync12(pkgPath))
|
|
17837
17968
|
return null;
|
|
17838
17969
|
let pkg;
|
|
17839
17970
|
try {
|
|
@@ -18061,17 +18192,17 @@ function initProject(options) {
|
|
|
18061
18192
|
}).filter((s) => s !== null);
|
|
18062
18193
|
const configDir = getTestersDir();
|
|
18063
18194
|
const configPath = join15(configDir, "config.json");
|
|
18064
|
-
if (!
|
|
18065
|
-
|
|
18195
|
+
if (!existsSync12(configDir)) {
|
|
18196
|
+
mkdirSync10(configDir, { recursive: true });
|
|
18066
18197
|
}
|
|
18067
18198
|
let config = {};
|
|
18068
|
-
if (
|
|
18199
|
+
if (existsSync12(configPath)) {
|
|
18069
18200
|
try {
|
|
18070
18201
|
config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
18071
18202
|
} catch {}
|
|
18072
18203
|
}
|
|
18073
18204
|
config.activeProject = project.id;
|
|
18074
|
-
|
|
18205
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
18075
18206
|
return { project, scenarios, framework, url };
|
|
18076
18207
|
}
|
|
18077
18208
|
// src/lib/smoke.ts
|
|
@@ -19701,9 +19832,9 @@ function deleteAuthPreset(name) {
|
|
|
19701
19832
|
}
|
|
19702
19833
|
// src/lib/report.ts
|
|
19703
19834
|
init_runs();
|
|
19704
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
19835
|
+
import { readFileSync as readFileSync4, existsSync as existsSync13 } from "fs";
|
|
19705
19836
|
function imageToBase64(filePath) {
|
|
19706
|
-
if (!filePath || !
|
|
19837
|
+
if (!filePath || !existsSync13(filePath))
|
|
19707
19838
|
return "";
|
|
19708
19839
|
try {
|
|
19709
19840
|
const buffer = readFileSync4(filePath);
|
|
@@ -19992,14 +20123,14 @@ async function startWatcher(options) {
|
|
|
19992
20123
|
}
|
|
19993
20124
|
// src/lib/repo-discovery.ts
|
|
19994
20125
|
init_paths();
|
|
19995
|
-
import { existsSync as
|
|
20126
|
+
import { existsSync as existsSync14, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync4, mkdirSync as mkdirSync11, unlinkSync } from "fs";
|
|
19996
20127
|
import { createHash } from "crypto";
|
|
19997
20128
|
import { join as join16, resolve as resolve2, relative as relative2 } from "path";
|
|
19998
20129
|
function getCacheDir() {
|
|
19999
20130
|
const testersDir = getTestersDir();
|
|
20000
20131
|
const cacheDir = join16(testersDir, "repo-index");
|
|
20001
|
-
if (!
|
|
20002
|
-
|
|
20132
|
+
if (!existsSync14(cacheDir)) {
|
|
20133
|
+
mkdirSync11(cacheDir, { recursive: true });
|
|
20003
20134
|
}
|
|
20004
20135
|
return cacheDir;
|
|
20005
20136
|
}
|
|
@@ -20012,10 +20143,10 @@ function getCachePath(repoPath) {
|
|
|
20012
20143
|
function isCacheStale(cached, repoPath) {
|
|
20013
20144
|
for (const spec of cached.specs) {
|
|
20014
20145
|
const fullPath = join16(repoPath, spec.file);
|
|
20015
|
-
if (!
|
|
20146
|
+
if (!existsSync14(fullPath))
|
|
20016
20147
|
return true;
|
|
20017
20148
|
try {
|
|
20018
|
-
const stat =
|
|
20149
|
+
const stat = statSync2(fullPath);
|
|
20019
20150
|
if (stat.mtimeMs !== spec.mtimeMs)
|
|
20020
20151
|
return true;
|
|
20021
20152
|
} catch {
|
|
@@ -20024,10 +20155,10 @@ function isCacheStale(cached, repoPath) {
|
|
|
20024
20155
|
}
|
|
20025
20156
|
if (cached.configPath) {
|
|
20026
20157
|
const configFullPath = join16(repoPath, cached.configPath);
|
|
20027
|
-
if (!
|
|
20158
|
+
if (!existsSync14(configFullPath))
|
|
20028
20159
|
return true;
|
|
20029
20160
|
try {
|
|
20030
|
-
|
|
20161
|
+
statSync2(configFullPath);
|
|
20031
20162
|
const age = Date.now() - new Date(cached.snapshotAt).getTime();
|
|
20032
20163
|
if (age > 3600000)
|
|
20033
20164
|
return true;
|
|
@@ -20039,7 +20170,7 @@ function isCacheStale(cached, repoPath) {
|
|
|
20039
20170
|
}
|
|
20040
20171
|
function loadCache(repoPath) {
|
|
20041
20172
|
const cachePath = getCachePath(repoPath);
|
|
20042
|
-
if (!
|
|
20173
|
+
if (!existsSync14(cachePath))
|
|
20043
20174
|
return null;
|
|
20044
20175
|
try {
|
|
20045
20176
|
const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
@@ -20050,14 +20181,14 @@ function loadCache(repoPath) {
|
|
|
20050
20181
|
}
|
|
20051
20182
|
function saveCache(snapshot) {
|
|
20052
20183
|
const cachePath = getCachePath(snapshot.repoPath);
|
|
20053
|
-
|
|
20184
|
+
writeFileSync4(cachePath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20054
20185
|
}
|
|
20055
20186
|
function detectPackageManager(repoPath) {
|
|
20056
20187
|
const result = {
|
|
20057
|
-
npm:
|
|
20058
|
-
yarn:
|
|
20059
|
-
pnpm:
|
|
20060
|
-
bun:
|
|
20188
|
+
npm: existsSync14(join16(repoPath, "package-lock.json")),
|
|
20189
|
+
yarn: existsSync14(join16(repoPath, "yarn.lock")),
|
|
20190
|
+
pnpm: existsSync14(join16(repoPath, "pnpm-lock.yaml")),
|
|
20191
|
+
bun: existsSync14(join16(repoPath, "bun.lockb")) || existsSync14(join16(repoPath, "bun.lock")),
|
|
20061
20192
|
preferred: "npm"
|
|
20062
20193
|
};
|
|
20063
20194
|
if (result.bun)
|
|
@@ -20072,7 +20203,7 @@ function detectPackageManager(repoPath) {
|
|
|
20072
20203
|
}
|
|
20073
20204
|
function detectDevScripts(repoPath) {
|
|
20074
20205
|
const pkgPath = join16(repoPath, "package.json");
|
|
20075
|
-
if (!
|
|
20206
|
+
if (!existsSync14(pkgPath)) {
|
|
20076
20207
|
return { dev: null, test: null, seed: null, build: null };
|
|
20077
20208
|
}
|
|
20078
20209
|
let scripts;
|
|
@@ -20098,7 +20229,7 @@ function findPlaywrightConfig(repoPath) {
|
|
|
20098
20229
|
"playwright-ct.config.js"
|
|
20099
20230
|
];
|
|
20100
20231
|
for (const name of candidates) {
|
|
20101
|
-
if (
|
|
20232
|
+
if (existsSync14(join16(repoPath, name)))
|
|
20102
20233
|
return name;
|
|
20103
20234
|
}
|
|
20104
20235
|
return null;
|
|
@@ -20157,7 +20288,7 @@ function findSpecFiles(repoPath, globPatterns) {
|
|
|
20157
20288
|
const dirsToSearch = ["", ".", "tests", "e2e", "test", "__tests__", "specs", "src"];
|
|
20158
20289
|
for (const dir of dirsToSearch) {
|
|
20159
20290
|
const searchDir = dir ? join16(repoPath, dir) : repoPath;
|
|
20160
|
-
if (!
|
|
20291
|
+
if (!existsSync14(searchDir))
|
|
20161
20292
|
continue;
|
|
20162
20293
|
try {
|
|
20163
20294
|
const files = walkDir(searchDir);
|
|
@@ -20169,7 +20300,7 @@ function findSpecFiles(repoPath, globPatterns) {
|
|
|
20169
20300
|
seen.add(relativePath);
|
|
20170
20301
|
const content = readFileSync5(file, "utf-8");
|
|
20171
20302
|
const contentHash = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
20172
|
-
const stat =
|
|
20303
|
+
const stat = statSync2(file);
|
|
20173
20304
|
specs.push({
|
|
20174
20305
|
file: relativePath,
|
|
20175
20306
|
fromGlob: pattern,
|
|
@@ -20209,7 +20340,7 @@ function matchesGlob(filePath, pattern) {
|
|
|
20209
20340
|
}
|
|
20210
20341
|
function detectSuggestedUrl(repoPath) {
|
|
20211
20342
|
const pkgPath = join16(repoPath, "package.json");
|
|
20212
|
-
if (!
|
|
20343
|
+
if (!existsSync14(pkgPath))
|
|
20213
20344
|
return null;
|
|
20214
20345
|
try {
|
|
20215
20346
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
@@ -20229,10 +20360,10 @@ function detectSuggestedUrl(repoPath) {
|
|
|
20229
20360
|
}
|
|
20230
20361
|
function checkPlaywrightBrowserInstalled(repoPath) {
|
|
20231
20362
|
const cacheDir = join16(repoPath, "node_modules", ".cache", "ms-playwright");
|
|
20232
|
-
if (
|
|
20363
|
+
if (existsSync14(cacheDir))
|
|
20233
20364
|
return true;
|
|
20234
20365
|
const globalCache = join16(repoPath, ".cache", "ms-playwright");
|
|
20235
|
-
if (
|
|
20366
|
+
if (existsSync14(globalCache))
|
|
20236
20367
|
return true;
|
|
20237
20368
|
return false;
|
|
20238
20369
|
}
|
|
@@ -20272,7 +20403,7 @@ function discoverRepo(opts) {
|
|
|
20272
20403
|
const specs = findSpecFiles(repoPath, globPatterns);
|
|
20273
20404
|
const packageManager = detectPackageManager(repoPath);
|
|
20274
20405
|
const devScripts = detectDevScripts(repoPath);
|
|
20275
|
-
const playwrightInstalled =
|
|
20406
|
+
const playwrightInstalled = existsSync14(join16(repoPath, "node_modules", "playwright")) || existsSync14(join16(repoPath, "node_modules", "@playwright", "test"));
|
|
20276
20407
|
const browsersInstalled = checkPlaywrightBrowserInstalled(repoPath);
|
|
20277
20408
|
const configExists = configPath !== null;
|
|
20278
20409
|
const specsFound = specs.length > 0;
|
|
@@ -20331,11 +20462,11 @@ function discoverRepo(opts) {
|
|
|
20331
20462
|
}
|
|
20332
20463
|
function clearDiscoveryCache(repoPath) {
|
|
20333
20464
|
const cacheDir = getCacheDir();
|
|
20334
|
-
if (!
|
|
20465
|
+
if (!existsSync14(cacheDir))
|
|
20335
20466
|
return;
|
|
20336
20467
|
if (repoPath) {
|
|
20337
20468
|
const cachePath = getCachePath(repoPath);
|
|
20338
|
-
if (
|
|
20469
|
+
if (existsSync14(cachePath)) {
|
|
20339
20470
|
unlinkSync(cachePath);
|
|
20340
20471
|
}
|
|
20341
20472
|
} else {
|
|
@@ -20348,7 +20479,7 @@ function clearDiscoveryCache(repoPath) {
|
|
|
20348
20479
|
}
|
|
20349
20480
|
function getDiscoveryCacheInfo(repoPath) {
|
|
20350
20481
|
const cachePath = getCachePath(repoPath);
|
|
20351
|
-
if (!
|
|
20482
|
+
if (!existsSync14(cachePath))
|
|
20352
20483
|
return null;
|
|
20353
20484
|
const cached = loadCache(repoPath);
|
|
20354
20485
|
if (!cached)
|
|
@@ -20364,11 +20495,11 @@ init_runs();
|
|
|
20364
20495
|
init_database();
|
|
20365
20496
|
init_paths();
|
|
20366
20497
|
import { execSync as execSync2 } from "child_process";
|
|
20367
|
-
import { existsSync as
|
|
20498
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
20368
20499
|
import { join as join17 } from "path";
|
|
20369
20500
|
function resolvePlaywrightCmd(repoPath) {
|
|
20370
20501
|
const localPw = join17(repoPath, "node_modules", ".bin", "playwright");
|
|
20371
|
-
if (
|
|
20502
|
+
if (existsSync15(localPw)) {
|
|
20372
20503
|
return [localPw, "test"];
|
|
20373
20504
|
}
|
|
20374
20505
|
return ["npx", "playwright", "test"];
|
|
@@ -20564,9 +20695,9 @@ async function runRepoTests(opts) {
|
|
|
20564
20695
|
const resultRecord = { id: resultId };
|
|
20565
20696
|
if (result.stdout || result.stderr) {
|
|
20566
20697
|
const reportersDir = join17(getTestersDir(), "repo-run-output");
|
|
20567
|
-
|
|
20698
|
+
mkdirSync12(reportersDir, { recursive: true });
|
|
20568
20699
|
const outputFile = join17(reportersDir, `${resultRecord.id}.log`);
|
|
20569
|
-
|
|
20700
|
+
writeFileSync5(outputFile, `=== stdout ===
|
|
20570
20701
|
${result.stdout}
|
|
20571
20702
|
|
|
20572
20703
|
=== 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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"}
|