bangonit 0.2.1 → 0.3.0
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 +18 -29
- package/app/desktopapp/package.json +1 -38
- package/app/webapp/.next/BUILD_ID +1 -1
- package/app/webapp/.next/app-build-manifest.json +6 -6
- package/app/webapp/.next/build-manifest.json +3 -3
- package/app/webapp/.next/next-minimal-server.js.nft.json +1 -1
- package/app/webapp/.next/next-server.js.nft.json +1 -1
- package/app/webapp/.next/prerender-manifest.json +1 -1
- package/app/webapp/.next/required-server-files.json +1 -1
- package/app/webapp/.next/server/app/_not-found/page.js +1 -1
- package/app/webapp/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/app/_not-found.html +1 -1
- package/app/webapp/.next/server/app/_not-found.rsc +1 -1
- package/app/webapp/.next/server/app/api/chat/route.js +1 -1
- package/app/webapp/.next/server/app/api/screenshot/route.js +1 -1
- package/app/webapp/.next/server/app/app/page.js +2 -2
- package/app/webapp/.next/server/app/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/app/app.html +1 -1
- package/app/webapp/.next/server/app/app.rsc +2 -2
- package/app/webapp/.next/server/app/index.html +1 -1
- package/app/webapp/.next/server/app/index.rsc +1 -1
- package/app/webapp/.next/server/app/page.js +1 -1
- package/app/webapp/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/chunks/679.js +1 -1
- package/app/webapp/.next/server/middleware-build-manifest.js +1 -1
- package/app/webapp/.next/server/pages/404.html +1 -1
- package/app/webapp/.next/server/pages/500.html +1 -1
- package/app/webapp/.next/server/server-reference-manifest.json +1 -1
- package/app/webapp/.next/static/chunks/app/app/{page-41ee880e93966e12.js → page-d38c1e48d37def82.js} +1 -1
- package/app/webapp/.next/static/chunks/app/layout-57acb80d8da0067a.js +1 -0
- package/app/webapp/.next/static/chunks/{main-app-76384b941f0b51cb.js → main-app-106dd83f859b9dfa.js} +1 -1
- package/app/webapp/.next/trace +2 -2
- package/app/webapp/.next/types/app/api/chat/route.ts +1 -1
- package/app/webapp/.next/types/app/api/screenshot/route.ts +1 -1
- package/app/webapp/.next/types/app/app/page.ts +1 -1
- package/app/webapp/.next/types/app/layout.ts +1 -1
- package/app/webapp/.next/types/app/page.ts +1 -1
- package/bin/bangonit.js +108 -220
- package/package.json +8 -2
- package/app/webapp/.next/static/chunks/app/layout-40f50d9380154ecf.js +0 -1
- package/scripts/regen-replays.sh +0 -46
- /package/app/webapp/.next/static/{BkuEihcexhIRjEt7SHF91 → TaLpPsk5rC30wNNcyfUN3}/_buildManifest.js +0 -0
- /package/app/webapp/.next/static/{BkuEihcexhIRjEt7SHF91 → TaLpPsk5rC30wNNcyfUN3}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /
|
|
1
|
+
// File: /Users/pete/repos/growthgirl/app/webapp/src/app/api/screenshot/route.ts
|
|
2
2
|
import * as entry from '../../../../../src/app/api/screenshot/route.js'
|
|
3
3
|
import type { NextRequest } from 'next/server.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /
|
|
1
|
+
// File: /Users/pete/repos/growthgirl/app/webapp/src/app/app/page.tsx
|
|
2
2
|
import * as entry from '../../../../src/app/app/page.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /
|
|
1
|
+
// File: /Users/pete/repos/growthgirl/app/webapp/src/app/layout.tsx
|
|
2
2
|
import * as entry from '../../../src/app/layout.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /
|
|
1
|
+
// File: /Users/pete/repos/growthgirl/app/webapp/src/app/page.tsx
|
|
2
2
|
import * as entry from '../../../src/app/page.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
package/bin/bangonit.js
CHANGED
|
@@ -34,6 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
return result;
|
|
35
35
|
};
|
|
36
36
|
})();
|
|
37
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
38
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
39
|
+
};
|
|
37
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
41
|
const child_process_1 = require("child_process");
|
|
39
42
|
const path = __importStar(require("path"));
|
|
@@ -42,6 +45,9 @@ const net = __importStar(require("net"));
|
|
|
42
45
|
const readline = __importStar(require("readline"));
|
|
43
46
|
const TOML = __importStar(require("@iarna/toml"));
|
|
44
47
|
const Minio = __importStar(require("minio"));
|
|
48
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
49
|
+
const helpers_1 = require("yargs/helpers");
|
|
50
|
+
const is_ci_1 = __importDefault(require("is-ci"));
|
|
45
51
|
const ROOT = path.resolve(__dirname, "..");
|
|
46
52
|
const WEBAPP_DIR = path.join(ROOT, "app", "webapp");
|
|
47
53
|
const DESKTOP_DIR = path.join(ROOT, "app", "desktopapp");
|
|
@@ -192,12 +198,10 @@ function createS3Client(opts) {
|
|
|
192
198
|
const secretKey = opts.secretKey || process.env.AWS_SECRET_ACCESS_KEY || "";
|
|
193
199
|
const region = opts.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1";
|
|
194
200
|
if (opts.endpoint) {
|
|
195
|
-
// Custom endpoint (DigitalOcean Spaces, MinIO, etc.)
|
|
196
201
|
const useSSL = !opts.endpoint.startsWith("http://");
|
|
197
202
|
const endPoint = opts.endpoint.replace(/^https?:\/\//, "");
|
|
198
203
|
return new Minio.Client({ endPoint, useSSL, accessKey, secretKey, region });
|
|
199
204
|
}
|
|
200
|
-
// Default: AWS S3
|
|
201
205
|
return new Minio.Client({
|
|
202
206
|
endPoint: "s3.amazonaws.com",
|
|
203
207
|
useSSL: true,
|
|
@@ -224,7 +228,6 @@ async function uploadToS3(localDir, opts) {
|
|
|
224
228
|
await uploadDir(client, localDir, opts.bucket, opts.prefix);
|
|
225
229
|
const region = opts.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1";
|
|
226
230
|
if (opts.endpoint) {
|
|
227
|
-
// Custom endpoint URL
|
|
228
231
|
const proto = opts.endpoint.startsWith("http://") ? "http" : "https";
|
|
229
232
|
const host = opts.endpoint.replace(/^https?:\/\//, "");
|
|
230
233
|
return `${proto}://${opts.bucket}.${host}/${opts.prefix}/index.html`;
|
|
@@ -269,6 +272,7 @@ async function initProject() {
|
|
|
269
272
|
console.log(`\n ${c.bold}${c.magenta}Bang On It! init${c.reset}\n`);
|
|
270
273
|
const testplans = await p.ask("Test plans directory", "testplans");
|
|
271
274
|
const apiKey = await p.ask("Anthropic API key (empty to use env var)", "");
|
|
275
|
+
const recordingsDir = await p.ask("Recordings directory", "recordings");
|
|
272
276
|
const s3Bucket = await p.ask("S3 bucket for recordings (empty to skip)", "");
|
|
273
277
|
let s3Endpoint = "";
|
|
274
278
|
let s3Region = "";
|
|
@@ -280,6 +284,7 @@ async function initProject() {
|
|
|
280
284
|
}
|
|
281
285
|
p.close();
|
|
282
286
|
let toml = `testplans = "${testplans}"\n`;
|
|
287
|
+
toml += `recordings_dir = "${recordingsDir}"\n`;
|
|
283
288
|
if (apiKey) {
|
|
284
289
|
toml += `anthropic_api_key = "${apiKey}"\n`;
|
|
285
290
|
}
|
|
@@ -288,15 +293,12 @@ async function initProject() {
|
|
|
288
293
|
}
|
|
289
294
|
if (s3Bucket) {
|
|
290
295
|
toml += `\n[s3]\nbucket = "${s3Bucket}"\n`;
|
|
291
|
-
if (s3Endpoint)
|
|
296
|
+
if (s3Endpoint)
|
|
292
297
|
toml += `endpoint = "${s3Endpoint}"\n`;
|
|
293
|
-
|
|
294
|
-
if (s3Region && s3Region !== "us-east-1") {
|
|
298
|
+
if (s3Region && s3Region !== "us-east-1")
|
|
295
299
|
toml += `region = "${s3Region}"\n`;
|
|
296
|
-
|
|
297
|
-
if (s3Prefix && s3Prefix !== "bangonit") {
|
|
300
|
+
if (s3Prefix && s3Prefix !== "bangonit")
|
|
298
301
|
toml += `prefix = "${s3Prefix}"\n`;
|
|
299
|
-
}
|
|
300
302
|
toml += `# access_key = "\${AWS_ACCESS_KEY_ID}"\n`;
|
|
301
303
|
toml += `# secret_key = "\${AWS_SECRET_ACCESS_KEY}"\n`;
|
|
302
304
|
}
|
|
@@ -322,7 +324,6 @@ async function initCi() {
|
|
|
322
324
|
const waitUrl = await p.ask("URL to wait for before testing", "http://localhost:3000");
|
|
323
325
|
const testDir = await p.ask("Test plan directory", "testplans");
|
|
324
326
|
const triggerInput = await p.askChoice("Trigger on", ["pr", "push", "both"], "both");
|
|
325
|
-
const s3Bucket = await p.ask("S3 bucket for recordings (empty to skip)", "");
|
|
326
327
|
const timeout = await p.ask("Test timeout in seconds", "300");
|
|
327
328
|
p.close();
|
|
328
329
|
const trigger = triggerInput === "pr" ? "pull_request" :
|
|
@@ -344,94 +345,6 @@ async function initCi() {
|
|
|
344
345
|
push:
|
|
345
346
|
branches: [main, master]`;
|
|
346
347
|
}
|
|
347
|
-
let s3Steps = "";
|
|
348
|
-
let s3Env = "";
|
|
349
|
-
let recordFlag = "";
|
|
350
|
-
if (s3Bucket) {
|
|
351
|
-
recordFlag = " --record";
|
|
352
|
-
s3Env = `
|
|
353
|
-
AWS_ACCESS_KEY_ID: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
354
|
-
AWS_SECRET_ACCESS_KEY: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
355
|
-
AWS_REGION: \${{ secrets.AWS_REGION || 'us-east-1' }}`;
|
|
356
|
-
s3Steps = `
|
|
357
|
-
- name: Upload recordings to S3
|
|
358
|
-
if: always()
|
|
359
|
-
run: |
|
|
360
|
-
if [ -d recordings ]; then
|
|
361
|
-
COMMIT_SHA=\${{ github.sha }}
|
|
362
|
-
RUN_ID=\${{ github.run_id }}
|
|
363
|
-
for dir in recordings/*/; do
|
|
364
|
-
[ -f "\$dir/index.html" ] || continue
|
|
365
|
-
DIRNAME=\$(basename "\$dir")
|
|
366
|
-
aws s3 cp "\$dir" "s3://${s3Bucket}/bangonit/\$COMMIT_SHA/\$DIRNAME/" --recursive --acl public-read --quiet
|
|
367
|
-
done
|
|
368
|
-
fi
|
|
369
|
-
env:
|
|
370
|
-
AWS_ACCESS_KEY_ID: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
371
|
-
AWS_SECRET_ACCESS_KEY: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
372
|
-
AWS_REGION: \${{ secrets.AWS_REGION || 'us-east-1' }}
|
|
373
|
-
|
|
374
|
-
- name: Comment on PR/commit with results
|
|
375
|
-
if: always()
|
|
376
|
-
uses: actions/github-script@v7
|
|
377
|
-
with:
|
|
378
|
-
script: |
|
|
379
|
-
const fs = require('fs');
|
|
380
|
-
const bucket = '${s3Bucket}';
|
|
381
|
-
const sha = context.sha;
|
|
382
|
-
const region = process.env.AWS_REGION || 'us-east-1';
|
|
383
|
-
const baseUrl = region === 'us-east-1'
|
|
384
|
-
? \`https://\${bucket}.s3.amazonaws.com\`
|
|
385
|
-
: \`https://\${bucket}.s3.\${region}.amazonaws.com\`;
|
|
386
|
-
|
|
387
|
-
// Read test output
|
|
388
|
-
let output = '';
|
|
389
|
-
try { output = fs.readFileSync('bangonit-output.json', 'utf-8'); } catch {}
|
|
390
|
-
const results = output ? JSON.parse(output) : null;
|
|
391
|
-
|
|
392
|
-
// Build recording links
|
|
393
|
-
let recordingLinks = '';
|
|
394
|
-
try {
|
|
395
|
-
const dirs = fs.readdirSync('recordings');
|
|
396
|
-
for (const dir of dirs) {
|
|
397
|
-
if (fs.existsSync(\`recordings/\${dir}/index.html\`)) {
|
|
398
|
-
const url = \`\${baseUrl}/bangonit/\${sha}/\${dir}/index.html\`;
|
|
399
|
-
recordingLinks += \`- [\${dir}](\${url})\\n\`;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
} catch {}
|
|
403
|
-
|
|
404
|
-
let body = '## Bang On It! Test Results\\n\\n';
|
|
405
|
-
if (results) {
|
|
406
|
-
const icon = results.status === 'pass' ? ':white_check_mark:' : ':x:';
|
|
407
|
-
body += \`\${icon} **\${results.status.toUpperCase()}**\\n\\n\`;
|
|
408
|
-
if (results.tests) {
|
|
409
|
-
for (const t of results.tests) {
|
|
410
|
-
const ti = t.status === 'pass' ? ':white_check_mark:' : ':x:';
|
|
411
|
-
body += \`\${ti} \${t.name} (\${(t.duration/1000).toFixed(1)}s)\\n\`;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (recordingLinks) {
|
|
416
|
-
body += \`\\n### Recordings\\n\${recordingLinks}\`;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (context.payload.pull_request) {
|
|
420
|
-
await github.rest.issues.createComment({
|
|
421
|
-
owner: context.repo.owner,
|
|
422
|
-
repo: context.repo.repo,
|
|
423
|
-
issue_number: context.payload.pull_request.number,
|
|
424
|
-
body
|
|
425
|
-
});
|
|
426
|
-
} else {
|
|
427
|
-
await github.rest.repos.createCommitComment({
|
|
428
|
-
owner: context.repo.owner,
|
|
429
|
-
repo: context.repo.repo,
|
|
430
|
-
commit_sha: sha,
|
|
431
|
-
body
|
|
432
|
-
});
|
|
433
|
-
}`;
|
|
434
|
-
}
|
|
435
348
|
const workflow = `name: Bang On It! Tests
|
|
436
349
|
|
|
437
350
|
${triggerYaml}
|
|
@@ -441,7 +354,7 @@ jobs:
|
|
|
441
354
|
runs-on: ${baseImage}
|
|
442
355
|
timeout-minutes: ${Math.ceil(parseInt(timeout) / 60) + 5}
|
|
443
356
|
env:
|
|
444
|
-
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
357
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
445
358
|
|
|
446
359
|
steps:
|
|
447
360
|
- uses: actions/checkout@v4
|
|
@@ -472,8 +385,8 @@ jobs:
|
|
|
472
385
|
xvfb-run --auto-servernum boi run ${testDir}/*.md \\
|
|
473
386
|
--headless --exit \\
|
|
474
387
|
--timeout ${timeout} \\
|
|
475
|
-
--output bangonit-output.json
|
|
476
|
-
|
|
388
|
+
--output bangonit-output.json --record
|
|
389
|
+
|
|
477
390
|
- name: Upload test results
|
|
478
391
|
if: always()
|
|
479
392
|
uses: actions/upload-artifact@v4
|
|
@@ -491,15 +404,9 @@ ${s3Steps}
|
|
|
491
404
|
console.log(`\n ${c.green}Created${c.reset} ${path.relative(process.cwd(), outPath)}\n`);
|
|
492
405
|
console.log(` ${c.yellow}Required GitHub secrets:${c.reset}`);
|
|
493
406
|
console.log(` ${c.dim} ANTHROPIC_API_KEY ${c.reset}— your Anthropic API key`);
|
|
494
|
-
if (s3Bucket) {
|
|
495
|
-
console.log(` ${c.dim} AWS_ACCESS_KEY_ID ${c.reset}— AWS credentials for S3 upload`);
|
|
496
|
-
console.log(` ${c.dim} AWS_SECRET_ACCESS_KEY${c.reset}`);
|
|
497
|
-
console.log(` ${c.dim} AWS_REGION ${c.reset}— (optional, defaults to us-east-1)`);
|
|
498
|
-
}
|
|
499
407
|
console.log("");
|
|
500
408
|
}
|
|
501
|
-
|
|
502
|
-
async function run(args, config) {
|
|
409
|
+
async function run(argv, config) {
|
|
503
410
|
loadEnv();
|
|
504
411
|
// Config can provide the API key (supports ${ENV_VAR} interpolation)
|
|
505
412
|
if (config.anthropic_api_key && !process.env.ANTHROPIC_API_KEY) {
|
|
@@ -508,53 +415,48 @@ async function run(args, config) {
|
|
|
508
415
|
if (!process.env.ANTHROPIC_API_KEY) {
|
|
509
416
|
die("Error: ANTHROPIC_API_KEY is not set.\nSet it in your environment, .env file, or .bangonit/config.toml.");
|
|
510
417
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
s3Bucket = args[++i];
|
|
518
|
-
}
|
|
519
|
-
else if ((args[i] === "--filter" || args[i] === "-t") && i + 1 < args.length) {
|
|
520
|
-
filter = args[++i];
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
electronArgs.push(args[i]);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
// Fall back to config for S3
|
|
527
|
-
if (!s3Bucket && config.s3?.bucket) {
|
|
528
|
-
s3Bucket = config.s3.bucket;
|
|
529
|
-
}
|
|
530
|
-
const s3Opts = {
|
|
531
|
-
region: config.s3?.region,
|
|
532
|
-
prefix: config.s3?.prefix || "bangonit",
|
|
533
|
-
endpoint: config.s3?.endpoint,
|
|
534
|
-
accessKey: config.s3?.access_key,
|
|
535
|
-
secretKey: config.s3?.secret_key,
|
|
536
|
-
};
|
|
537
|
-
// If --s3-bucket is set, ensure --record is also set
|
|
538
|
-
if (s3Bucket && !electronArgs.includes("--record")) {
|
|
539
|
-
electronArgs.push("--record");
|
|
540
|
-
}
|
|
541
|
-
// If no test plan files passed and not using --plan/--auto, discover from testplans dir
|
|
542
|
-
const hasFiles = electronArgs.some((a) => !a.startsWith("-"));
|
|
543
|
-
const hasPlan = electronArgs.includes("--plan");
|
|
544
|
-
const hasAuto = electronArgs.includes("--auto");
|
|
545
|
-
if (!hasFiles && !hasPlan && !hasAuto && config.testplans) {
|
|
418
|
+
const recordingsDir = config.recordings_dir
|
|
419
|
+
? path.resolve(process.cwd(), config.recordings_dir)
|
|
420
|
+
: path.join(process.cwd(), "recordings");
|
|
421
|
+
// Discover test plans if no files/plan/auto specified
|
|
422
|
+
const files = [...argv.files];
|
|
423
|
+
if (files.length === 0 && !argv.plan && !argv.auto && config.testplans) {
|
|
546
424
|
const testDirPath = path.resolve(process.cwd(), config.testplans);
|
|
547
|
-
const plans = findTestPlans(testDirPath, filter);
|
|
425
|
+
const plans = findTestPlans(testDirPath, argv.filter);
|
|
548
426
|
if (plans.length > 0) {
|
|
549
|
-
|
|
427
|
+
files.push(...plans);
|
|
550
428
|
}
|
|
551
|
-
else if (filter) {
|
|
552
|
-
die(`No test plans matching "${filter}" found in ${config.testplans}/`);
|
|
429
|
+
else if (argv.filter) {
|
|
430
|
+
die(`No test plans matching "${argv.filter}" found in ${config.testplans}/`);
|
|
553
431
|
}
|
|
554
432
|
}
|
|
555
|
-
else if (
|
|
433
|
+
else if (files.length === 0 && !argv.plan && !argv.auto && argv.filter) {
|
|
556
434
|
die(`--filter requires a testplans directory configured in .bangonit/config.toml`);
|
|
557
435
|
}
|
|
436
|
+
// Build args to forward to Electron
|
|
437
|
+
const electronArgs = [...files];
|
|
438
|
+
if (argv.plan)
|
|
439
|
+
electronArgs.push("--plan", argv.plan);
|
|
440
|
+
if (argv.auto)
|
|
441
|
+
electronArgs.push("--auto");
|
|
442
|
+
if (argv.prompt)
|
|
443
|
+
electronArgs.push("--prompt", argv.prompt);
|
|
444
|
+
if (argv.record)
|
|
445
|
+
electronArgs.push("--record", "--recordings-dir", recordingsDir);
|
|
446
|
+
if (argv.headless)
|
|
447
|
+
electronArgs.push("--headless");
|
|
448
|
+
if (argv.exit)
|
|
449
|
+
electronArgs.push("--exit");
|
|
450
|
+
if (argv.json)
|
|
451
|
+
electronArgs.push("--json");
|
|
452
|
+
if (argv.console)
|
|
453
|
+
electronArgs.push("--console");
|
|
454
|
+
if (argv.output)
|
|
455
|
+
electronArgs.push("--output", argv.output);
|
|
456
|
+
if (argv.concurrency != null)
|
|
457
|
+
electronArgs.push("--concurrency", String(argv.concurrency));
|
|
458
|
+
if (argv.timeout != null)
|
|
459
|
+
electronArgs.push("--timeout", String(argv.timeout));
|
|
558
460
|
const PORT = await getFreePort();
|
|
559
461
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
560
462
|
const nextDir = path.join(WEBAPP_DIR, ".next");
|
|
@@ -626,18 +528,20 @@ async function run(args, config) {
|
|
|
626
528
|
});
|
|
627
529
|
electronProc.on("exit", async (code) => {
|
|
628
530
|
// Upload recordings to S3 if configured
|
|
629
|
-
if (
|
|
630
|
-
const recordingsDir = path.join(process.cwd(), "recordings");
|
|
531
|
+
if (config.s3?.bucket && argv.record) {
|
|
631
532
|
if (fs.existsSync(recordingsDir)) {
|
|
632
533
|
const dirs = fs.readdirSync(recordingsDir).filter((d) => fs.existsSync(path.join(recordingsDir, d, "index.html")));
|
|
534
|
+
const s3Prefix = config.s3.prefix || "bangonit";
|
|
633
535
|
for (const dir of dirs) {
|
|
634
536
|
const localPath = path.join(recordingsDir, dir);
|
|
635
|
-
const prefix = `${s3Opts.prefix}/${dir}`;
|
|
636
537
|
try {
|
|
637
538
|
const url = await uploadToS3(localPath, {
|
|
638
|
-
bucket:
|
|
639
|
-
prefix
|
|
640
|
-
|
|
539
|
+
bucket: config.s3.bucket,
|
|
540
|
+
prefix: `${s3Prefix}/${dir}`,
|
|
541
|
+
region: config.s3.region,
|
|
542
|
+
endpoint: config.s3.endpoint,
|
|
543
|
+
accessKey: config.s3.access_key,
|
|
544
|
+
secretKey: config.s3.secret_key,
|
|
641
545
|
});
|
|
642
546
|
console.log(`\n${c.green}Recording:${c.reset} ${c.cyan}${url}${c.reset}`);
|
|
643
547
|
}
|
|
@@ -648,72 +552,56 @@ async function run(args, config) {
|
|
|
648
552
|
}
|
|
649
553
|
}
|
|
650
554
|
cleanup();
|
|
651
|
-
process.exit(code
|
|
555
|
+
process.exit(code ?? 1);
|
|
652
556
|
});
|
|
653
557
|
}
|
|
654
558
|
// --- main ---
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
// Route to subcommand
|
|
706
|
-
if (filteredArgs[0] === "init" && !filteredArgs[1]) {
|
|
707
|
-
await initProject();
|
|
708
|
-
}
|
|
709
|
-
else if (filteredArgs[0] === "init-ci") {
|
|
710
|
-
await initCi();
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
// "run" command (explicit or implicit)
|
|
714
|
-
const config = loadConfig(configPath);
|
|
715
|
-
const runArgs = filteredArgs[0] === "run" ? filteredArgs.slice(1) : filteredArgs;
|
|
716
|
-
await run(runArgs, config);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
main();
|
|
559
|
+
const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
|
|
560
|
+
(0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
561
|
+
.scriptName("boi")
|
|
562
|
+
.usage("Usage: $0 <command> [options]")
|
|
563
|
+
.command("init", "Create a .bangonit/config.toml config file", {}, () => { initProject(); })
|
|
564
|
+
.command("init-ci", "Generate a GitHub Actions workflow", {}, () => { initCi(); })
|
|
565
|
+
.command(["run [files..]", "$0"], "Run test plans (or launch interactive UI)", (y) => y
|
|
566
|
+
.positional("files", { type: "string", array: true, default: [], describe: "Test plan files" })
|
|
567
|
+
.option("filter", { alias: "t", type: "string", describe: "Filter test plans by name substring" })
|
|
568
|
+
.option("config", { type: "string", describe: "Path to config file (default: .bangonit/config.toml)" })
|
|
569
|
+
.option("plan", { type: "string", describe: "Inline test plan (instead of file)" })
|
|
570
|
+
.option("auto", { type: "boolean", default: false, describe: "Auto-generate test plan from git state" })
|
|
571
|
+
.option("prompt", { type: "string", describe: "Additional instructions appended to test plan" })
|
|
572
|
+
.option("record", { type: "boolean", default: false, describe: "Record session replay" })
|
|
573
|
+
.option("headless", { type: "boolean", default: ciDefaults.headless ?? false, describe: "Run without showing the browser window" })
|
|
574
|
+
.option("exit", { type: "boolean", default: ciDefaults.exit ?? false, describe: "Exit immediately after tests complete" })
|
|
575
|
+
.option("json", { type: "boolean", default: false, describe: "Stream NDJSON events to stdout" })
|
|
576
|
+
.option("console", { type: "boolean", default: false, describe: "Forward browser console logs to stdout" })
|
|
577
|
+
.option("output", { type: "string", describe: "Write JSON results to file" })
|
|
578
|
+
.option("concurrency", { type: "number", describe: "Number of parallel agents (default: 1)" })
|
|
579
|
+
.option("timeout", { type: "number", describe: "Test timeout in seconds (0 = none)" }), (argv) => {
|
|
580
|
+
const config = loadConfig(argv.config);
|
|
581
|
+
run({
|
|
582
|
+
files: argv.files,
|
|
583
|
+
filter: argv.filter,
|
|
584
|
+
plan: argv.plan,
|
|
585
|
+
auto: argv.auto,
|
|
586
|
+
prompt: argv.prompt,
|
|
587
|
+
record: argv.record,
|
|
588
|
+
headless: argv.headless,
|
|
589
|
+
exit: argv.exit,
|
|
590
|
+
json: argv.json,
|
|
591
|
+
console: argv.console,
|
|
592
|
+
output: argv.output,
|
|
593
|
+
concurrency: argv.concurrency,
|
|
594
|
+
timeout: argv.timeout,
|
|
595
|
+
}, config);
|
|
596
|
+
})
|
|
597
|
+
.example("$0 run test.md", "Run a test plan file")
|
|
598
|
+
.example("$0 run --plan 'test login flow'", "Run an inline test plan")
|
|
599
|
+
.example("$0 run --auto", "Auto-discover from git state")
|
|
600
|
+
.example("$0 run -t checkout", "Run test plans matching 'checkout'")
|
|
601
|
+
.example("$0 run", "Launch interactive UI")
|
|
602
|
+
.example("$0 init", "Create .bangonit/config.toml")
|
|
603
|
+
.example("$0 init-ci", "Set up GitHub Actions")
|
|
604
|
+
.strict()
|
|
605
|
+
.help()
|
|
606
|
+
.alias("h", "help")
|
|
607
|
+
.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bangonit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AI-powered E2E testing tool",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bangonit": "bin/bangonit.js",
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@iarna/toml": "^2.2.5",
|
|
52
|
-
"
|
|
52
|
+
"is-ci": "^4.1.0",
|
|
53
|
+
"minio": "^8.0.7",
|
|
54
|
+
"yargs": "^18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/is-ci": "^3.0.4",
|
|
58
|
+
"@types/yargs": "^17.0.35"
|
|
53
59
|
}
|
|
54
60
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{5603:function(n,e,u){Promise.resolve().then(u.t.bind(u,2625,23))},2625:function(){}},function(n){n.O(0,[387,293,528,744],function(){return n(n.s=5603)}),_N_E=n.O()}]);
|
package/scripts/regen-replays.sh
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Regenerate index.html for all existing recordings using the current replay bundle.
|
|
3
|
-
# Usage: ./scripts/regen-replays.sh [recordings-dir]
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
cd "$(dirname "$0")/.."
|
|
7
|
-
|
|
8
|
-
RECORDINGS_DIR="${1:-recordings}"
|
|
9
|
-
REPLAY_JS="app/replay/dist/replay.js"
|
|
10
|
-
REPLAY_CSS="app/replay/dist/replay.css"
|
|
11
|
-
|
|
12
|
-
if [ ! -f "$REPLAY_JS" ] || [ ! -f "$REPLAY_CSS" ]; then
|
|
13
|
-
echo "Replay bundle not found. Building..."
|
|
14
|
-
cd app/replay && npx vite build && cd ../..
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
count=0
|
|
18
|
-
for dir in "$RECORDINGS_DIR"/*/; do
|
|
19
|
-
[ -f "$dir/data.json" ] || continue
|
|
20
|
-
python3 -c "
|
|
21
|
-
import sys
|
|
22
|
-
js = open('$REPLAY_JS').read()
|
|
23
|
-
css = open('$REPLAY_CSS').read()
|
|
24
|
-
data = open('${dir}data.json').read()
|
|
25
|
-
html = '''<!DOCTYPE html>
|
|
26
|
-
<html lang=\"en\">
|
|
27
|
-
<head>
|
|
28
|
-
<meta charset=\"UTF-8\">
|
|
29
|
-
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
|
30
|
-
<title>Session Replay</title>
|
|
31
|
-
<style>''' + css + '''</style>
|
|
32
|
-
</head>
|
|
33
|
-
<body>
|
|
34
|
-
<div id=\"root\"></div>
|
|
35
|
-
<script>window.__REPLAY_DATA__ = ''' + data + ''';</script>
|
|
36
|
-
<script>''' + js + '''</script>
|
|
37
|
-
</body>
|
|
38
|
-
</html>'''
|
|
39
|
-
with open('${dir}index.html', 'w') as f:
|
|
40
|
-
f.write(html)
|
|
41
|
-
"
|
|
42
|
-
echo " Regenerated ${dir}index.html"
|
|
43
|
-
count=$((count + 1))
|
|
44
|
-
done
|
|
45
|
-
|
|
46
|
-
echo "Done — $count recording(s) updated."
|
/package/app/webapp/.next/static/{BkuEihcexhIRjEt7SHF91 → TaLpPsk5rC30wNNcyfUN3}/_buildManifest.js
RENAMED
|
File without changes
|
/package/app/webapp/.next/static/{BkuEihcexhIRjEt7SHF91 → TaLpPsk5rC30wNNcyfUN3}/_ssgManifest.js
RENAMED
|
File without changes
|