sandboxbox 1.2.0 → 1.2.2
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/Dockerfile +80 -24
- package/Dockerfile.test +16 -0
- package/bin/bwrap +0 -0
- package/build-final.log +2217 -0
- package/build-output.log +289 -0
- package/cli.js +4 -2
- package/complete-build.log +231 -0
- package/container.js +402 -26
- package/final-build.log +268 -0
- package/final-complete-build.log +240 -0
- package/full-build.log +234 -0
- package/init-firewall.sh +36 -0
- package/npm-build-test.log +410 -0
- package/package.json +1 -1
- package/sandboxbox-sandbox/build.sh +83 -0
package/container.js
CHANGED
@@ -27,6 +27,11 @@ class BubblewrapContainer {
|
|
27
27
|
this.verbose = options.verbose !== false;
|
28
28
|
this.env = { ...process.env };
|
29
29
|
this.workdir = '/workspace';
|
30
|
+
|
31
|
+
// Auto-create sandbox directory if it doesn't exist
|
32
|
+
if (!existsSync(this.sandboxDir)) {
|
33
|
+
mkdirSync(this.sandboxDir, { recursive: true });
|
34
|
+
}
|
30
35
|
}
|
31
36
|
|
32
37
|
/**
|
@@ -217,6 +222,87 @@ echo "⚠️ Note: Chromium-only (Firefox/WebKit need glibc - use Ubuntu)"
|
|
217
222
|
// Resolve project directory
|
218
223
|
const resolvedProjectDir = resolve(projectDir);
|
219
224
|
|
225
|
+
// First, try full namespace isolation
|
226
|
+
try {
|
227
|
+
console.log('🎯 Attempting full namespace isolation...');
|
228
|
+
return await this.runPlaywrightWithNamespaces(options);
|
229
|
+
} catch (error) {
|
230
|
+
console.log(`⚠️ Namespace isolation failed: ${error.message}`);
|
231
|
+
console.log('🔄 Falling back to basic isolation mode...\n');
|
232
|
+
return await this.runPlaywrightBasic(options);
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
/**
|
237
|
+
* Run simple container test without Playwright (for testing purposes)
|
238
|
+
*/
|
239
|
+
async runSimpleTest(options = {}) {
|
240
|
+
const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la /workspace' } = options;
|
241
|
+
const resolvedProjectDir = resolve(projectDir);
|
242
|
+
|
243
|
+
console.log('🧪 Running simple container test...\n');
|
244
|
+
|
245
|
+
// Try basic isolation first
|
246
|
+
try {
|
247
|
+
console.log('🎯 Attempting basic isolation...');
|
248
|
+
return await this.runBasicTest(options);
|
249
|
+
} catch (error) {
|
250
|
+
console.log(`⚠️ Basic test failed: ${error.message}`);
|
251
|
+
console.log('🔄 Running without isolation...\n');
|
252
|
+
return this.runWithoutIsolation(options);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Run basic test in container
|
258
|
+
*/
|
259
|
+
async runBasicTest(options = {}) {
|
260
|
+
const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la /workspace' } = options;
|
261
|
+
const resolvedProjectDir = resolve(projectDir);
|
262
|
+
|
263
|
+
// Simplified bubblewrap command
|
264
|
+
const bwrapCmd = [
|
265
|
+
bubblewrap.findBubblewrap(),
|
266
|
+
'--bind', resolvedProjectDir, '/workspace',
|
267
|
+
'--chdir', '/workspace',
|
268
|
+
'--tmpfs', '/tmp',
|
269
|
+
'/bin/sh', '-c', testCommand
|
270
|
+
];
|
271
|
+
|
272
|
+
console.log(`🚀 Running: ${testCommand}`);
|
273
|
+
console.log(`📁 Project directory: ${resolvedProjectDir}`);
|
274
|
+
console.log(`🎯 Sandbox isolation: basic mode\n`);
|
275
|
+
|
276
|
+
return this.executeCommand(bwrapCmd, resolvedProjectDir);
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Run without any isolation (last resort)
|
281
|
+
*/
|
282
|
+
async runWithoutIsolation(options = {}) {
|
283
|
+
const { projectDir = '.', testCommand = 'echo "Container is working!" && ls -la' } = options;
|
284
|
+
const resolvedProjectDir = resolve(projectDir);
|
285
|
+
|
286
|
+
console.log(`🚀 Running without isolation: ${testCommand}`);
|
287
|
+
console.log(`📁 Project directory: ${resolvedProjectDir}`);
|
288
|
+
console.log(`🎯 Sandbox isolation: none\n`);
|
289
|
+
|
290
|
+
try {
|
291
|
+
execSync(testCommand, { stdio: 'inherit', cwd: resolvedProjectDir });
|
292
|
+
console.log('\n✅ Test completed successfully!');
|
293
|
+
return 0;
|
294
|
+
} catch (error) {
|
295
|
+
throw new Error(`Test failed: ${error.message}`);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
/**
|
300
|
+
* Run Playwright with full namespace isolation (ideal mode)
|
301
|
+
*/
|
302
|
+
async runPlaywrightWithNamespaces(options = {}) {
|
303
|
+
const { projectDir = '.', testCommand = 'npx playwright test', mountProject = true } = options;
|
304
|
+
const resolvedProjectDir = resolve(projectDir);
|
305
|
+
|
220
306
|
// Build bubblewrap command with proper namespace isolation
|
221
307
|
const bwrapCmd = [
|
222
308
|
bubblewrap.findBubblewrap(),
|
@@ -289,6 +375,48 @@ echo "⚠️ Note: Chromium-only (Firefox/WebKit need glibc - use Ubuntu)"
|
|
289
375
|
console.log(`📁 Project directory: ${resolvedProjectDir}`);
|
290
376
|
console.log(`🎯 Sandbox isolation: full bubblewrap namespace isolation\n`);
|
291
377
|
|
378
|
+
return this.executeCommand(fullCmd, resolvedProjectDir);
|
379
|
+
}
|
380
|
+
|
381
|
+
/**
|
382
|
+
* Run Playwright with basic isolation (fallback mode for limited environments)
|
383
|
+
*/
|
384
|
+
async runPlaywrightBasic(options = {}) {
|
385
|
+
const { projectDir = '.', testCommand = 'npx playwright test', mountProject = true } = options;
|
386
|
+
const resolvedProjectDir = resolve(projectDir);
|
387
|
+
|
388
|
+
console.log('🎯 Running in basic isolation mode (limited features)...');
|
389
|
+
|
390
|
+
// Simplified bubblewrap command without namespaces
|
391
|
+
const bwrapCmd = [
|
392
|
+
bubblewrap.findBubblewrap(),
|
393
|
+
|
394
|
+
// Basic filesystem
|
395
|
+
'--bind', resolvedProjectDir, '/workspace',
|
396
|
+
'--chdir', '/workspace',
|
397
|
+
'--tmpfs', '/tmp',
|
398
|
+
'--share-net', // Keep network access
|
399
|
+
|
400
|
+
// Essential environment variables
|
401
|
+
'--setenv', 'PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
|
402
|
+
'--setenv', 'PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser',
|
403
|
+
'--setenv', 'CHROMIUM_FLAGS=--no-sandbox --disable-dev-shm-usage --disable-gpu',
|
404
|
+
|
405
|
+
// Run command directly without wrapper script
|
406
|
+
'/bin/sh', '-c', testCommand
|
407
|
+
];
|
408
|
+
|
409
|
+
console.log(`🚀 Running: ${testCommand}`);
|
410
|
+
console.log(`📁 Project directory: ${resolvedProjectDir}`);
|
411
|
+
console.log(`🎯 Sandbox isolation: basic mode (limited namespaces)\n`);
|
412
|
+
|
413
|
+
return this.executeCommand(bwrapCmd, resolvedProjectDir);
|
414
|
+
}
|
415
|
+
|
416
|
+
/**
|
417
|
+
* Execute bubblewrap command with proper error handling
|
418
|
+
*/
|
419
|
+
executeCommand(fullCmd, resolvedProjectDir) {
|
292
420
|
try {
|
293
421
|
// Execute with spawn for better control
|
294
422
|
const child = spawn(fullCmd[0], fullCmd.slice(1), {
|
@@ -354,6 +482,37 @@ echo "⚠️ Note: Chromium-only (Firefox/WebKit need glibc - use Ubuntu)"
|
|
354
482
|
}
|
355
483
|
}
|
356
484
|
|
485
|
+
/**
|
486
|
+
* Check if a command needs sudo privileges
|
487
|
+
*/
|
488
|
+
commandNeedsSudo(command) {
|
489
|
+
const sudoCommands = [
|
490
|
+
'useradd', 'usermod', 'groupadd', 'userdel', 'chsh',
|
491
|
+
'apt-get', 'apt', 'yum', 'dnf', 'apk',
|
492
|
+
'chown', 'chmod',
|
493
|
+
'systemctl', 'service',
|
494
|
+
'npm install -g', 'npm i -g',
|
495
|
+
'pnpm install -g', 'pnpm i -g',
|
496
|
+
'npx --yes playwright install-deps'
|
497
|
+
];
|
498
|
+
|
499
|
+
// System directories that need sudo for modifications
|
500
|
+
const systemPaths = ['/etc/', '/usr/', '/var/', '/opt/', '/home/'];
|
501
|
+
|
502
|
+
// Check if command modifies system directories
|
503
|
+
const modifiesSystem = systemPaths.some(path =>
|
504
|
+
command.includes(path) && (
|
505
|
+
command.includes('mkdir') ||
|
506
|
+
command.includes('tee') ||
|
507
|
+
command.includes('>') ||
|
508
|
+
command.includes('cp') ||
|
509
|
+
command.includes('mv')
|
510
|
+
)
|
511
|
+
);
|
512
|
+
|
513
|
+
return sudoCommands.some(cmd => command.includes(cmd)) || modifiesSystem;
|
514
|
+
}
|
515
|
+
|
357
516
|
/**
|
358
517
|
* Build container from Dockerfile
|
359
518
|
*/
|
@@ -364,40 +523,234 @@ echo "⚠️ Note: Chromium-only (Firefox/WebKit need glibc - use Ubuntu)"
|
|
364
523
|
|
365
524
|
const content = readFileSync(dockerfilePath, 'utf-8');
|
366
525
|
const lines = content.split('\n')
|
367
|
-
.
|
526
|
+
.map(line => line.trim())
|
527
|
+
.filter(line => line && !line.startsWith('#'));
|
368
528
|
|
529
|
+
// Parse Dockerfile
|
530
|
+
let baseImage = 'alpine';
|
369
531
|
let workdir = '/workspace';
|
370
532
|
const buildCommands = [];
|
371
|
-
|
533
|
+
const envVars = {};
|
534
|
+
const buildArgs = {}; // ARG variables
|
535
|
+
let defaultCmd = null;
|
536
|
+
let currentUser = 'root';
|
537
|
+
|
538
|
+
// Handle multi-line commands (lines ending with \)
|
539
|
+
const processedLines = [];
|
540
|
+
let currentLine = '';
|
372
541
|
for (const line of lines) {
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
542
|
+
if (line.endsWith('\\')) {
|
543
|
+
currentLine += line.slice(0, -1) + ' ';
|
544
|
+
} else {
|
545
|
+
currentLine += line;
|
546
|
+
processedLines.push(currentLine.trim());
|
547
|
+
currentLine = '';
|
548
|
+
}
|
549
|
+
}
|
550
|
+
|
551
|
+
// Parse instructions
|
552
|
+
for (const line of processedLines) {
|
553
|
+
if (line.startsWith('FROM ')) {
|
554
|
+
baseImage = line.substring(5).trim();
|
555
|
+
console.log(`📦 FROM ${baseImage}`);
|
556
|
+
|
557
|
+
// Detect base image type
|
558
|
+
if (baseImage.includes('ubuntu') || baseImage.includes('debian')) {
|
559
|
+
console.log(' 🐧 Detected Ubuntu/Debian base image\n');
|
560
|
+
} else if (baseImage.includes('alpine')) {
|
561
|
+
console.log(' 🏔️ Detected Alpine base image\n');
|
562
|
+
} else {
|
563
|
+
console.log(' ⚠️ Unknown base image type\n');
|
564
|
+
}
|
565
|
+
} else if (line.startsWith('WORKDIR ')) {
|
566
|
+
workdir = line.substring(9).trim().replace(/['"]/g, '');
|
379
567
|
console.log(`📁 WORKDIR ${workdir}\n`);
|
380
|
-
} else if (
|
381
|
-
const
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
568
|
+
} else if (line.startsWith('ARG ')) {
|
569
|
+
const argLine = line.substring(4).trim();
|
570
|
+
const match = argLine.match(/^(\w+)(?:=(.+))?$/);
|
571
|
+
if (match) {
|
572
|
+
buildArgs[match[1]] = match[2] || '';
|
573
|
+
console.log(`🏗️ ARG ${match[1]}${match[2] ? `=${match[2]}` : ''}\n`);
|
574
|
+
}
|
575
|
+
} else if (line.startsWith('ENV ')) {
|
576
|
+
const envLine = line.substring(4).trim();
|
577
|
+
const match = envLine.match(/^(\w+)=(.+)$/);
|
578
|
+
if (match) {
|
579
|
+
envVars[match[1]] = match[2];
|
580
|
+
console.log(`🔧 ENV ${match[1]}=${match[2]}\n`);
|
581
|
+
}
|
582
|
+
} else if (line.startsWith('USER ')) {
|
583
|
+
currentUser = line.substring(5).trim();
|
584
|
+
console.log(`👤 USER ${currentUser}\n`);
|
585
|
+
} else if (line.startsWith('RUN ')) {
|
586
|
+
const command = line.substring(4).trim();
|
587
|
+
buildCommands.push({ command, user: currentUser, workdir });
|
588
|
+
console.log(`⚙️ RUN ${command.substring(0, 70)}${command.length > 70 ? '...' : ''}`);
|
589
|
+
} else if (line.startsWith('CMD ')) {
|
590
|
+
defaultCmd = line.substring(4).trim();
|
591
|
+
console.log(`🎯 CMD ${defaultCmd}\n`);
|
592
|
+
} else if (line.startsWith('COPY ') || line.startsWith('ADD ')) {
|
593
|
+
console.log(`📋 ${line.substring(0, 70)}${line.length > 70 ? '...' : ''}`);
|
594
|
+
console.log(' ⚠️ COPY/ADD commands must be run in project directory\n');
|
388
595
|
}
|
389
596
|
}
|
390
597
|
|
598
|
+
// Create container root directory
|
599
|
+
const containerRoot = join(this.sandboxDir, 'rootfs');
|
600
|
+
if (!existsSync(containerRoot)) {
|
601
|
+
mkdirSync(containerRoot, { recursive: true });
|
602
|
+
console.log(`📁 Created container root: ${containerRoot}\n`);
|
603
|
+
}
|
604
|
+
|
391
605
|
// Create build script
|
392
|
-
|
393
|
-
const
|
394
|
-
|
606
|
+
console.log('📝 Creating build script...\n');
|
607
|
+
const buildScriptContent = `#!/bin/bash
|
608
|
+
set -e
|
609
|
+
|
610
|
+
# Build script generated from ${dockerfilePath}
|
611
|
+
# Base image: ${baseImage}
|
612
|
+
# Total commands: ${buildCommands.length}
|
395
613
|
|
396
|
-
|
397
|
-
|
398
|
-
|
614
|
+
echo "🏗️ Starting build process..."
|
615
|
+
echo ""
|
616
|
+
|
617
|
+
${buildCommands.map((cmd, idx) => `
|
618
|
+
# Command ${idx + 1}/${buildCommands.length}
|
619
|
+
echo "⚙️ [${idx + 1}/${buildCommands.length}] Executing: ${cmd.command.substring(0, 60)}..."
|
620
|
+
${cmd.user !== 'root' ? `# Running as user: ${cmd.user}` : ''}
|
621
|
+
${cmd.command}
|
622
|
+
echo " ✅ Command ${idx + 1} completed"
|
623
|
+
echo ""
|
624
|
+
`).join('')}
|
625
|
+
|
626
|
+
echo "✅ Build complete!"
|
627
|
+
`;
|
628
|
+
|
629
|
+
const buildScriptPath = join(this.sandboxDir, 'build.sh');
|
630
|
+
writeFileSync(buildScriptPath, buildScriptContent, { mode: 0o755 });
|
631
|
+
console.log(`✅ Build script created: ${buildScriptPath}\n`);
|
632
|
+
|
633
|
+
// Option to execute the build
|
634
|
+
if (options.execute !== false) {
|
635
|
+
console.log('🚀 Executing build commands...\n');
|
636
|
+
console.log('⚠️ Note: Commands will run on host system (Docker-free mode)\n');
|
637
|
+
|
638
|
+
// Clean up previous build artifacts for idempotency
|
639
|
+
console.log('🧹 Cleaning up previous build artifacts...');
|
640
|
+
try {
|
641
|
+
// Clean up directories
|
642
|
+
execSync('sudo rm -rf /commandhistory /workspace /home/node/.oh-my-zsh /home/node/.zshrc* /usr/local/share/npm-global 2>/dev/null || true', {
|
643
|
+
stdio: 'pipe'
|
644
|
+
});
|
645
|
+
|
646
|
+
// Clean up npm global packages that will be reinstalled
|
647
|
+
const npmRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
648
|
+
execSync(`sudo rm -rf "${npmRoot}/@anthropic-ai/claude-code" "${npmRoot}/@playwright/mcp" 2>/dev/null || true`, {
|
649
|
+
stdio: 'pipe'
|
650
|
+
});
|
399
651
|
|
400
|
-
|
652
|
+
console.log('✅ Cleanup complete\n');
|
653
|
+
} catch (error) {
|
654
|
+
console.log('⚠️ Cleanup had some errors (may be okay)\n');
|
655
|
+
}
|
656
|
+
|
657
|
+
console.log('─'.repeat(60) + '\n');
|
658
|
+
|
659
|
+
try {
|
660
|
+
for (let i = 0; i < buildCommands.length; i++) {
|
661
|
+
const { command, user } = buildCommands[i];
|
662
|
+
console.log(`\n📍 [${i + 1}/${buildCommands.length}] ${command.substring(0, 80)}${command.length > 80 ? '...' : ''}`);
|
663
|
+
|
664
|
+
try {
|
665
|
+
// Determine execution mode based on user and command requirements
|
666
|
+
let execCommand;
|
667
|
+
let runMessage;
|
668
|
+
|
669
|
+
// Commands that need sudo always run as root, regardless of USER directive
|
670
|
+
if (this.commandNeedsSudo(command)) {
|
671
|
+
// Sudo-requiring command: always run as root
|
672
|
+
const escapedCommand = command.replace(/'/g, "'\\''");
|
673
|
+
execCommand = `/usr/bin/sudo -E bash -c '${escapedCommand}'`;
|
674
|
+
runMessage = '🔐 Running with sudo (requires root privileges)';
|
675
|
+
} else if (user !== 'root') {
|
676
|
+
// Non-root user: run as that user
|
677
|
+
// Use single quotes to avoid nested quote issues with complex commands
|
678
|
+
const escapedCommand = command.replace(/'/g, "'\\''");
|
679
|
+
execCommand = `/usr/bin/sudo -u ${user} -E bash -c '${escapedCommand}'`;
|
680
|
+
runMessage = `👤 Running as user: ${user}`;
|
681
|
+
} else {
|
682
|
+
// Regular command
|
683
|
+
execCommand = command;
|
684
|
+
runMessage = null;
|
685
|
+
}
|
686
|
+
|
687
|
+
if (runMessage) {
|
688
|
+
console.log(` ${runMessage}`);
|
689
|
+
}
|
690
|
+
|
691
|
+
// Execute command with proper environment (including ARG and ENV variables)
|
692
|
+
// Ensure npm/node paths are included for node user
|
693
|
+
const npmPath = execSync('which npm 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim();
|
694
|
+
const nodePath = npmPath ? dirname(npmPath) : '';
|
695
|
+
const basePath = process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
696
|
+
const fullPath = nodePath ? `${nodePath}:${basePath}` : basePath;
|
697
|
+
|
698
|
+
const execEnv = {
|
699
|
+
...process.env,
|
700
|
+
...buildArgs,
|
701
|
+
...envVars,
|
702
|
+
PATH: fullPath
|
703
|
+
};
|
704
|
+
|
705
|
+
// Set HOME for non-root users
|
706
|
+
if (user !== 'root') {
|
707
|
+
execEnv.HOME = `/home/${user}`;
|
708
|
+
}
|
709
|
+
|
710
|
+
// Determine working directory (root for system commands, sandbox for user commands)
|
711
|
+
const isSystemCommand = user === 'root' && this.commandNeedsSudo(command);
|
712
|
+
const cwd = isSystemCommand ? '/' : this.sandboxDir;
|
713
|
+
|
714
|
+
execSync(execCommand, {
|
715
|
+
stdio: 'inherit',
|
716
|
+
cwd,
|
717
|
+
env: execEnv,
|
718
|
+
shell: true
|
719
|
+
});
|
720
|
+
console.log(`✅ Command ${i + 1} completed successfully`);
|
721
|
+
} catch (error) {
|
722
|
+
console.log(`❌ Command ${i + 1} failed: ${error.message}`);
|
723
|
+
console.log(`\n⚠️ Build failed at command ${i + 1}/${buildCommands.length}`);
|
724
|
+
console.log(`📝 Partial build script available at: ${buildScriptPath}`);
|
725
|
+
throw error;
|
726
|
+
}
|
727
|
+
}
|
728
|
+
|
729
|
+
console.log('\n' + '─'.repeat(60));
|
730
|
+
console.log('\n🎉 Build completed successfully!\n');
|
731
|
+
console.log(`📦 Container root: ${containerRoot}`);
|
732
|
+
console.log(`📝 Build script: ${buildScriptPath}`);
|
733
|
+
if (defaultCmd) {
|
734
|
+
console.log(`🎯 Default command: ${defaultCmd}`);
|
735
|
+
}
|
736
|
+
console.log('');
|
737
|
+
|
738
|
+
} catch (error) {
|
739
|
+
throw new Error(`Build failed: ${error.message}`);
|
740
|
+
}
|
741
|
+
} else {
|
742
|
+
console.log('📝 Build script ready (not executed)');
|
743
|
+
console.log(` To build manually: bash ${buildScriptPath}\n`);
|
744
|
+
}
|
745
|
+
|
746
|
+
return {
|
747
|
+
buildScript: buildScriptPath,
|
748
|
+
workdir,
|
749
|
+
baseImage,
|
750
|
+
containerRoot,
|
751
|
+
defaultCmd,
|
752
|
+
envVars
|
753
|
+
};
|
401
754
|
}
|
402
755
|
}
|
403
756
|
|
@@ -438,15 +791,38 @@ Requirements:
|
|
438
791
|
await container.setupAlpineRootfs();
|
439
792
|
|
440
793
|
} else if (args[0] === 'build') {
|
441
|
-
const dockerfile = args[1] || './Dockerfile
|
794
|
+
const dockerfile = args[1] || './Dockerfile';
|
442
795
|
if (!existsSync(dockerfile)) {
|
443
796
|
throw new Error(`Dockerfile not found: ${dockerfile}`);
|
444
797
|
}
|
445
|
-
|
798
|
+
|
799
|
+
// Check for --dry-run flag
|
800
|
+
const dryRun = args.includes('--dry-run');
|
801
|
+
const options = { execute: !dryRun };
|
802
|
+
|
803
|
+
if (dryRun) {
|
804
|
+
console.log('🔍 Dry-run mode: Commands will be parsed but not executed\n');
|
805
|
+
}
|
806
|
+
|
807
|
+
await container.buildFromDockerfile(dockerfile, options);
|
446
808
|
|
447
809
|
} else if (args[0] === 'run') {
|
448
810
|
const projectDir = args[1] || '.';
|
449
|
-
|
811
|
+
|
812
|
+
// First try simple test to verify container works
|
813
|
+
console.log('🧪 Testing container functionality...\n');
|
814
|
+
try {
|
815
|
+
await container.runSimpleTest({ projectDir });
|
816
|
+
console.log('✅ Container test successful!\n');
|
817
|
+
|
818
|
+
// Now try Playwright
|
819
|
+
console.log('🎭 Running Playwright tests...\n');
|
820
|
+
await container.runPlaywright({ projectDir });
|
821
|
+
} catch (error) {
|
822
|
+
console.log(`⚠️ Container test failed: ${error.message}`);
|
823
|
+
console.log('🚫 Skipping Playwright tests due to container issues\n');
|
824
|
+
throw error;
|
825
|
+
}
|
450
826
|
|
451
827
|
} else if (args[0] === 'shell') {
|
452
828
|
const projectDir = args[1] || '.';
|