#!/usr/bin/env node /** * Unified Test Runner for Pake CLI * * This is a simplified, unified test runner that replaces the scattered * test files with a single, easy-to-use interface. */ import { execSync, spawn } from "child_process"; import fs from "fs"; import path from "path"; import ora from "ora"; import config, { TIMEOUTS, TEST_URLS } from "./config.js"; class PakeTestRunner { constructor() { this.results = []; this.tempFiles = []; this.tempDirs = []; } async runAll(options = {}) { const { unit = true, integration = true, builder = true, pakeCliTests = false, e2e = false, quick = false, realBuild = false, // Add option for real build test } = options; console.log("šŸš€ Pake CLI Test Suite"); console.log("======================\n"); this.validateEnvironment(); let testCount = 0; if (unit && !quick) { console.log("šŸ“‹ Running Unit Tests..."); await this.runUnitTests(); testCount++; } if (integration && !quick) { console.log("\nšŸ”§ Running Integration Tests..."); await this.runIntegrationTests(); testCount++; } if (builder && !quick) { console.log("\nšŸ—ļø Running Builder Tests..."); await this.runBuilderTests(); testCount++; } if (pakeCliTests) { console.log("\nšŸ“¦ Running Pake-CLI GitHub Actions Tests..."); await this.runPakeCliTests(); testCount++; } if (e2e && !quick) { console.log("\nšŸš€ Running End-to-End Tests..."); await this.runE2ETests(); testCount++; } if (realBuild && !quick) { // On macOS, prefer multi-arch test as it's more likely to catch issues if (process.platform === "darwin") { console.log("\nšŸ—ļø Running Real Build Test (Multi-Arch)..."); await this.runMultiArchBuildTest(); testCount++; } else { console.log("\nšŸ—ļø Running Real Build Test..."); await this.runRealBuildTest(); testCount++; } } this.cleanup(); this.displayFinalResults(); const passed = this.results.filter((r) => r.passed).length; const total = this.results.length; return passed === total; } validateEnvironment() { console.log("šŸ” Environment Validation:"); console.log("---------------------------"); // Check if CLI file exists if (!fs.existsSync(config.CLI_PATH)) { console.log("āŒ CLI file not found. Run: npm run cli:build"); process.exit(1); } console.log("āœ… CLI file exists"); // Check if CLI is executable try { execSync(`node "${config.CLI_PATH}" --version`, { encoding: "utf8", timeout: 3000, }); console.log("āœ… CLI is executable"); } catch (error) { console.log("āŒ CLI is not executable"); process.exit(1); } // Platform info console.log(`āœ… Platform: ${process.platform} (${process.arch})`); console.log(`āœ… Node.js: ${process.version}`); const isCI = process.env.CI || process.env.GITHUB_ACTIONS; console.log(`${isCI ? "āœ…" : "ā„¹ļø"} CI Environment: ${isCI ? "Yes" : "No"}`); console.log(); } async runTest(name, testFn, timeout = TIMEOUTS.MEDIUM) { const spinner = ora(`Running ${name}...`).start(); try { const result = await Promise.race([ testFn(), new Promise((_, reject) => setTimeout(() => reject(new Error("Test timeout")), timeout), ), ]); if (result) { spinner.succeed(`${name}: PASS`); this.results.push({ name, passed: true }); } else { spinner.fail(`${name}: FAIL`); this.results.push({ name, passed: false }); } } catch (error) { spinner.fail(`${name}: ERROR - ${error.message.slice(0, 100)}...`); this.results.push({ name, passed: false, error: error.message, }); } } async runUnitTests() { // Version command test await this.runTest( "Version Command", () => { const output = execSync(`node "${config.CLI_PATH}" --version`, { encoding: "utf8", timeout: 3000, }); return /^\d+\.\d+\.\d+/.test(output.trim()); }, TIMEOUTS.QUICK, ); // Help command test await this.runTest( "Help Command", () => { const output = execSync(`node "${config.CLI_PATH}" --help`, { encoding: "utf8", timeout: 3000, }); return output.includes("Usage: cli [url] [options]"); }, TIMEOUTS.QUICK, ); // URL validation test await this.runTest("URL Validation", () => { try { execSync(`node "${config.CLI_PATH}" "invalid-url" --name TestApp`, { encoding: "utf8", timeout: 3000, }); return false; // Should have failed } catch (error) { return error.status !== 0; } }); // Number validation test await this.runTest("Number Validation", () => { try { execSync(`node "${config.CLI_PATH}" https://example.com --width abc`, { encoding: "utf8", timeout: 3000, }); return false; // Should throw error } catch (error) { return error.message.includes("Not a number"); } }); // CLI response time test await this.runTest("CLI Response Time", () => { const start = Date.now(); execSync(`node "${config.CLI_PATH}" --version`, { encoding: "utf8", timeout: 3000, }); const elapsed = Date.now() - start; return elapsed < 2000; }); // Weekly URL accessibility test await this.runTest("Weekly URL Accessibility", () => { try { const testCommand = `node "${config.CLI_PATH}" ${TEST_URLS.WEEKLY} --name "URLTest" --debug`; execSync(`echo "n" | timeout 5s ${testCommand} || true`, { encoding: "utf8", timeout: 8000, }); return true; // If we get here, URL was parsed successfully } catch (error) { return ( !error.message.includes("Invalid URL") && !error.message.includes("invalid") ); } }); } async runIntegrationTests() { // Process spawning test await this.runTest("CLI Process Spawning", () => { return new Promise((resolve) => { const child = spawn("node", [config.CLI_PATH, "--version"], { stdio: ["pipe", "pipe", "pipe"], }); let output = ""; child.stdout.on("data", (data) => { output += data.toString(); }); child.on("close", (code) => { resolve(code === 0 && /\d+\.\d+\.\d+/.test(output)); }); setTimeout(() => { child.kill(); resolve(false); }, 3000); }); }); // File system permissions test await this.runTest("File System Permissions", () => { try { const testFile = "test-write-permission.tmp"; fs.writeFileSync(testFile, "test"); this.trackTempFile(testFile); const cliStats = fs.statSync(config.CLI_PATH); return cliStats.isFile(); } catch { return false; } }); // Dependency resolution test await this.runTest("Dependency Resolution", () => { try { const packageJsonPath = path.join(config.PROJECT_ROOT, "package.json"); const packageJson = JSON.parse( fs.readFileSync(packageJsonPath, "utf8"), ); const essentialDeps = ["commander", "chalk", "fs-extra", "execa"]; return essentialDeps.every( (dep) => packageJson.dependencies && packageJson.dependencies[dep], ); } catch { return false; } }); } async runBuilderTests() { // Platform detection test await this.runTest("Platform Detection", () => { const platform = process.platform; const platformConfigs = { darwin: { ext: ".dmg", multiArch: true }, win32: { ext: ".msi", multiArch: false }, linux: { ext: ".deb", multiArch: false }, }; const config = platformConfigs[platform]; return config && typeof config.ext === "string"; }); // Architecture detection test await this.runTest("Architecture Detection", () => { const currentArch = process.arch; const macArch = currentArch === "arm64" ? "aarch64" : currentArch; const linuxArch = currentArch === "x64" ? "amd64" : currentArch; return typeof macArch === "string" && typeof linuxArch === "string"; }); // File naming pattern test await this.runTest("File Naming Patterns", () => { const testNames = ["Simple App", "App-With_Symbols", "CamelCaseApp"]; return testNames.every((name) => { const processed = name.toLowerCase().replace(/\s+/g, ""); return processed.length > 0; }); }); } async runPakeCliTests() { // Package installation test await this.runTest( "pake-cli Package Installation", async () => { try { execSync("npm install pake-cli@latest --no-package-lock", { encoding: "utf8", timeout: 60000, cwd: "/tmp", }); const pakeCliPath = "/tmp/node_modules/.bin/pake"; return fs.existsSync(pakeCliPath); } catch (error) { console.error("Package installation failed:", error.message); return false; } }, TIMEOUTS.LONG, ); // Version command test await this.runTest("pake-cli Version Command", async () => { try { const version = execSync("npx pake --version", { encoding: "utf8", timeout: 10000, }); return /^\d+\.\d+\.\d+/.test(version.trim()); } catch { return false; } }); // Configuration validation test await this.runTest("Configuration Validation", async () => { try { const validateConfig = (config) => { const required = ["url", "name", "width", "height"]; const hasRequired = required.every((field) => config.hasOwnProperty(field), ); const validTypes = typeof config.url === "string" && typeof config.name === "string" && typeof config.width === "number" && typeof config.height === "number"; let validUrl = false; try { new URL(config.url); validUrl = true; } catch {} const validName = config.name.length > 0; return hasRequired && validTypes && validUrl && validName; }; const testConfig = { url: "https://github.com", name: "github", width: 1200, height: 780, }; return validateConfig(testConfig); } catch { return false; } }); } async runE2ETests() { // GitHub.com CLI build test await this.runTest( "GitHub.com CLI Build Test", async () => { return new Promise((resolve, reject) => { const testName = "GitHubApp"; const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --debug --width 1200 --height 780`; const child = spawn(command, { shell: true, cwd: config.PROJECT_ROOT, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, PAKE_E2E_TEST: "1", PAKE_CREATE_APP: "1", }, }); let buildStarted = false; let configGenerated = false; child.stdout.on("data", (data) => { const output = data.toString(); if ( output.includes("Building app") || output.includes("Compiling") || output.includes("Installing package") || output.includes("Bundling") ) { buildStarted = true; } if ( output.includes("GitHub") && (output.includes("config") || output.includes("name")) ) { configGenerated = true; } }); child.stderr.on("data", (data) => { const output = data.toString(); if ( output.includes("Building app") || output.includes("Compiling") || output.includes("Installing package") || output.includes("Bundling") || output.includes("Finished") || output.includes("Built application at:") ) { buildStarted = true; } }); // Kill process after 60 seconds if build started const timeout = setTimeout(() => { child.kill("SIGTERM"); const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`); const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`); this.trackTempFile(appFile); this.trackTempFile(dmgFile); if (buildStarted) { console.log( `āœ“ GitHub.com CLI build started successfully (${testName})`, ); resolve(true); } else { reject( new Error("GitHub.com CLI build did not start within timeout"), ); } }, 60000); child.on("close", () => { clearTimeout(timeout); const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`); const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`); this.trackTempFile(appFile); this.trackTempFile(dmgFile); if (buildStarted) { resolve(true); } else { reject( new Error("GitHub.com CLI build process ended before starting"), ); } }); child.on("error", (error) => { reject( new Error(`GitHub.com CLI build process error: ${error.message}`), ); }); child.stdin.end(); }); }, 70000, // 70 seconds timeout ); // Configuration verification test await this.runTest( "Configuration File Verification", async () => { const pakeDir = path.join(config.PROJECT_ROOT, "src-tauri", ".pake"); return new Promise((resolve, reject) => { const testName = "GitHubConfigTest"; const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --debug --width 1200 --height 780`; const child = spawn(command, { shell: true, cwd: config.PROJECT_ROOT, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, PAKE_E2E_TEST: "1", PAKE_CREATE_APP: "1", }, }); const checkConfigFiles = () => { if (fs.existsSync(pakeDir)) { const configFile = path.join(pakeDir, "tauri.conf.json"); const pakeConfigFile = path.join(pakeDir, "pake.json"); if (fs.existsSync(configFile) && fs.existsSync(pakeConfigFile)) { try { const config = JSON.parse( fs.readFileSync(configFile, "utf8"), ); const pakeConfig = JSON.parse( fs.readFileSync(pakeConfigFile, "utf8"), ); if ( config.productName === testName && pakeConfig.windows[0].url === "https://github.com/" ) { child.kill("SIGTERM"); this.trackTempDir(pakeDir); console.log( "āœ“ GitHub.com configuration files verified correctly", ); resolve(true); return true; } } catch (error) { // Continue if config parsing fails } } } return false; }; child.stdout.on("data", (data) => { const output = data.toString(); if ( output.includes("Installing package") || output.includes("Building app") ) { setTimeout(checkConfigFiles, 1000); } }); child.stderr.on("data", (data) => { const output = data.toString(); if ( output.includes("Installing package") || output.includes("Building app") || output.includes("Package installed") ) { setTimeout(checkConfigFiles, 1000); } }); // Timeout after 20 seconds setTimeout(() => { child.kill("SIGTERM"); this.trackTempDir(pakeDir); reject(new Error("GitHub.com configuration verification timeout")); }, 20000); child.on("error", (error) => { reject( new Error( `GitHub.com config verification error: ${error.message}`, ), ); }); child.stdin.end(); }); }, 25000, ); } async runRealBuildTest() { // Real build test that actually creates a complete app await this.runTest( "Complete GitHub.com App Build", async () => { return new Promise((resolve, reject) => { const testName = "GitHubRealBuild"; // Platform-specific output files const outputFiles = { darwin: { app: path.join(config.PROJECT_ROOT, `${testName}.app`), installer: path.join(config.PROJECT_ROOT, `${testName}.dmg`), }, linux: { app: path.join( config.PROJECT_ROOT, `src-tauri/target/release/pake`, ), installer: path.join( config.PROJECT_ROOT, `src-tauri/target/release/bundle/deb/*.deb`, ), }, win32: { app: path.join( config.PROJECT_ROOT, `src-tauri/target/release/pake.exe`, ), installer: path.join( config.PROJECT_ROOT, `src-tauri/target/release/bundle/msi/*.msi`, ), }, }; const platform = process.platform; const expectedFiles = outputFiles[platform] || outputFiles.darwin; console.log(`šŸ”§ Starting real build test for GitHub.com...`); console.log(`šŸ“ Expected output: ${expectedFiles.app}`); const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --width 1200 --height 800 --hide-title-bar`; const child = spawn(command, { shell: true, cwd: config.PROJECT_ROOT, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, PAKE_CREATE_APP: "1", }, }); let buildStarted = false; let compilationStarted = false; // Track progress without too much noise child.stdout.on("data", (data) => { const output = data.toString(); if (output.includes("Installing package")) { console.log(" šŸ“¦ Installing dependencies..."); } if (output.includes("Building app")) { buildStarted = true; console.log(" šŸ—ļø Build started..."); } if (output.includes("Compiling")) { compilationStarted = true; console.log(" āš™ļø Compiling..."); } if (output.includes("Bundling")) { console.log(" šŸ“¦ Bundling..."); } if (output.includes("Built application at:")) { console.log(" āœ… Build completed!"); } }); let errorOutput = ""; child.stderr.on("data", (data) => { const output = data.toString(); if (output.includes("Building app")) buildStarted = true; if (output.includes("Compiling")) compilationStarted = true; if (output.includes("Finished")) console.log(" āœ… Compilation finished!"); // Capture error output for debugging if ( output.includes("error:") || output.includes("Error:") || output.includes("ERROR") ) { errorOutput += output; } }); // Real timeout - 8 minutes for actual build const timeout = setTimeout(() => { const appExists = this.checkFileExists(expectedFiles.app); const installerExists = this.checkFileExists( expectedFiles.installer, ); if (appExists) { console.log( " šŸŽ‰ Build completed successfully (app file exists)!", ); console.log( ` šŸ“± App location: ${this.getActualFilePath(expectedFiles.app)}`, ); if (installerExists) { console.log( ` šŸ’æ Installer location: ${this.getActualFilePath(expectedFiles.installer)}`, ); } console.log(" ✨ Build artifacts preserved for inspection"); child.kill("SIGTERM"); resolve(true); } else { console.log( " āš ļø Build process completed but no app file found", ); console.log(` šŸ“ Expected location: ${expectedFiles.app}`); child.kill("SIGTERM"); reject(new Error("Real build test timeout")); } }, 480000); // 8 minutes child.on("close", (code) => { clearTimeout(timeout); const appExists = this.checkFileExists(expectedFiles.app); const installerExists = this.checkFileExists( expectedFiles.installer, ); const actualAppPath = this.getActualFilePath(expectedFiles.app); const actualInstallerPath = this.getActualFilePath( expectedFiles.installer, ); if (appExists || installerExists) { console.log( " šŸŽ‰ Real build test SUCCESS: Build file(s) generated!", ); if (appExists) { console.log(` šŸ“± App location: ${actualAppPath}`); } if (installerExists) { console.log(` šŸ’æ Installer location: ${actualInstallerPath}`); } else if (appExists && !installerExists) { console.log( " ā„¹ļø Note: Binary created successfully, but installer package not generated", ); } console.log(" ✨ Build artifacts preserved for inspection"); resolve(true); } else if (code === 0 && buildStarted && compilationStarted) { console.log( " āš ļø Build process completed but no build files found", ); console.log(` šŸ“ Expected app location: ${expectedFiles.app}`); console.log( ` šŸ“ Expected installer location: ${expectedFiles.installer}`, ); // Debug: List actual files in target directories to help diagnose const targetDir = path.join( config.PROJECT_ROOT, "src-tauri/target", ); if (fs.existsSync(targetDir)) { console.log( " šŸ” Debug: Listing target directory structure...", ); try { this.listTargetContents(targetDir); } catch (error) { console.log( ` āš ļø Could not list target contents: ${error.message}`, ); } } resolve(false); } else { console.log(` āŒ Build process failed with exit code: ${code}`); if (buildStarted) { console.log( " šŸ“Š Build was started but failed during execution", ); if (errorOutput.trim()) { console.log(" šŸ” Error details:"); errorOutput.split("\n").forEach((line) => { if (line.trim()) console.log(` ${line.trim()}`); }); } // Debug: List actual files in target directories to help diagnose const targetDir = path.join( config.PROJECT_ROOT, "src-tauri/target", ); if (fs.existsSync(targetDir)) { console.log( " šŸ” Debug: Listing target directory structure...", ); try { this.listTargetContents(targetDir); } catch (error) { console.log( ` āš ļø Could not list target contents: ${error.message}`, ); } } } else { console.log(" šŸ“Š Build failed before starting compilation"); if (errorOutput.trim()) { console.log(" šŸ” Error details:"); errorOutput.split("\n").forEach((line) => { if (line.trim()) console.log(` ${line.trim()}`); }); } } reject(new Error(`Real build test failed with code ${code}`)); } }); child.on("error", (error) => { clearTimeout(timeout); reject( new Error(`Real build test process error: ${error.message}`), ); }); child.stdin.end(); }); }, 500000, // 8+ minutes timeout ); } async runMultiArchBuildTest() { // Multi-arch build test specifically for macOS await this.runTest( "Multi-Arch GitHub.com Build (Universal Binary)", async () => { return new Promise((resolve, reject) => { const testName = "GitHubMultiArch"; const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`); const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`); // Also check universal binary locations const universalAppFile = path.join( config.PROJECT_ROOT, `src-tauri/target/universal-apple-darwin/release/bundle/macos/${testName}.app`, ); // Check for DMG file in universal target (fallback if app target not working) const universalDmgFile = path.join( config.PROJECT_ROOT, `src-tauri/target/universal-apple-darwin/release/bundle/dmg/${testName}_*.dmg`, ); console.log(`šŸ”§ Starting multi-arch build test for GitHub.com...`); console.log(`šŸ“ Expected output: ${appFile}`); console.log(`šŸ—ļø Building Universal Binary (Intel + Apple Silicon)`); const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --width 1200 --height 800 --hide-title-bar --multi-arch`; const child = spawn(command, { shell: true, cwd: config.PROJECT_ROOT, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, PAKE_CREATE_APP: "1", HDIUTIL_QUIET: "1", HDIUTIL_NO_AUTOOPEN: "1", }, }); let buildStarted = false; let compilationStarted = false; // Track progress child.stdout.on("data", (data) => { const output = data.toString(); if (output.includes("Installing package")) { console.log(" šŸ“¦ Installing dependencies..."); } if (output.includes("Building app")) { buildStarted = true; console.log(" šŸ—ļø Multi-arch build started..."); } if (output.includes("Compiling")) { compilationStarted = true; console.log(" āš™ļø Compiling for multiple architectures..."); } if ( output.includes("universal-apple-darwin") || output.includes("Universal") ) { console.log(" šŸ”€ Universal binary target detected"); } if (output.includes("Bundling")) { console.log(" šŸ“¦ Bundling universal binary..."); } if (output.includes("Built application at:")) { console.log(" āœ… Multi-arch build completed!"); } }); child.stderr.on("data", (data) => { const output = data.toString(); if (output.includes("Building app")) buildStarted = true; if (output.includes("Compiling")) compilationStarted = true; if (output.includes("Finished")) console.log(" āœ… Multi-arch compilation finished!"); }); // Multi-arch builds take longer - 12 minutes timeout const timeout = setTimeout(() => { const appExists = fs.existsSync(appFile); const universalAppExists = fs.existsSync(universalAppFile); const dmgExists = fs.existsSync(dmgFile); const universalDmgExists = this.checkFileExists(universalDmgFile); if ( appExists || universalAppExists || dmgExists || universalDmgExists ) { console.log(" šŸŽ‰ Multi-arch build completed successfully!"); if (appExists || universalAppExists) { const actualAppFile = appExists ? appFile : universalAppFile; console.log(` šŸ“± App location: ${actualAppFile}`); } if (dmgExists) { console.log(` šŸ’æ DMG location: ${dmgFile}`); } if (universalDmgExists) { const actualDmgFile = this.getActualFilePath(universalDmgFile); console.log(` šŸ’æ Universal DMG location: ${actualDmgFile}`); } console.log(" šŸ”€ Universal binary preserved for inspection"); child.kill("SIGTERM"); resolve(true); } else { console.log( " āŒ Multi-arch build timeout - no output files generated", ); console.log(` šŸ“ Expected: ${appFile}`); console.log(` šŸ“ Or: ${universalAppFile}`); console.log(` šŸ“ Or: ${dmgFile}`); console.log(` šŸ“ Or: ${universalDmgFile}`); child.kill("SIGTERM"); reject(new Error("Multi-arch build test timeout")); } }, 720000); // 12 minutes for multi-arch child.on("close", (code) => { clearTimeout(timeout); const appExists = fs.existsSync(appFile); const universalAppExists = fs.existsSync(universalAppFile); const dmgExists = fs.existsSync(dmgFile); const universalDmgExists = this.checkFileExists(universalDmgFile); if ( appExists || universalAppExists || dmgExists || universalDmgExists ) { console.log( " šŸŽ‰ Multi-arch build test SUCCESS: Universal binary generated!", ); if (appExists || universalAppExists) { const actualAppFile = appExists ? appFile : universalAppFile; console.log(` šŸ“± App location: ${actualAppFile}`); } if (dmgExists) { console.log(` šŸ’æ DMG location: ${dmgFile}`); } if (universalDmgExists) { const actualDmgFile = this.getActualFilePath(universalDmgFile); console.log(` šŸ’æ Universal DMG location: ${actualDmgFile}`); } console.log(" šŸ”€ Universal binary preserved for inspection"); // Verify it's actually a universal binary try { const fileOutput = execSync( `file "${actualAppFile}/Contents/MacOS/pake"`, { encoding: "utf8" }, ); if (fileOutput.includes("universal binary")) { console.log( " āœ… Verified: Universal binary created successfully", ); } else { console.log( " āš ļø Note: Binary architecture:", fileOutput.trim(), ); } } catch (error) { console.log(" āš ļø Could not verify binary architecture"); } resolve(true); } else if (buildStarted && compilationStarted) { // If build started and compilation happened, but no output files found console.log( " āš ļø Multi-arch build process completed but no output files found", ); console.log(` šŸ“ Expected: ${appFile}`); console.log(` šŸ“ Or: ${universalAppFile}`); console.log(` šŸ“ Or: ${dmgFile}`); console.log(` šŸ“ Or: ${universalDmgFile}`); resolve(false); } else { // Only reject if the build never started or failed early reject( new Error(`Multi-arch build test failed with code ${code}`), ); } }); child.on("error", (error) => { clearTimeout(timeout); reject( new Error( `Multi-arch build test process error: ${error.message}`, ), ); }); child.stdin.end(); }); }, 750000, // 12+ minutes timeout ); } // Helper function to check if files exist (handles wildcards) checkFileExists(filePath) { if (filePath.includes("*")) { const dir = path.dirname(filePath); const pattern = path.basename(filePath); try { if (!fs.existsSync(dir)) { return false; } const files = fs.readdirSync(dir); const regex = new RegExp(pattern.replace(/\*/g, ".*")); const matches = files.filter((file) => regex.test(file)); return matches.length > 0; } catch { return false; } } return fs.existsSync(filePath); } getActualFilePath(filePath) { if (filePath.includes("*")) { const dir = path.dirname(filePath); const pattern = path.basename(filePath); try { const files = fs.readdirSync(dir); const regex = new RegExp(pattern.replace(/\*/g, ".*")); const match = files.find((file) => regex.test(file)); return match ? path.join(dir, match) : filePath; } catch { return filePath; } } return filePath; } listTargetContents(targetDir, maxDepth = 3, currentDepth = 0) { if (currentDepth >= maxDepth) return; try { const items = fs.readdirSync(targetDir); items.forEach((item) => { const fullPath = path.join(targetDir, item); const relativePath = path.relative(config.PROJECT_ROOT, fullPath); const indent = " ".repeat(currentDepth + 1); if (fs.statSync(fullPath).isDirectory()) { console.log(`${indent}šŸ“ ${relativePath}/`); if (item === "bundle" || item === "release") { this.listTargetContents(fullPath, maxDepth, currentDepth + 1); } } else { console.log(`${indent}šŸ“„ ${relativePath}`); } }); } catch (error) { console.log(` āš ļø Could not list contents of ${targetDir}`); } } trackTempFile(filepath) { this.tempFiles.push(filepath); } trackTempDir(dirpath) { this.tempDirs.push(dirpath); } cleanupTempIcons() { // Clean up temporary icon files generated during tests const iconsDir = path.join(config.PROJECT_ROOT, "src-tauri/icons"); const testNames = ["urltest", "githubapp", "githubmultiarch"]; testNames.forEach((name) => { const iconPath = path.join(iconsDir, `${name}.icns`); try { if (fs.existsSync(iconPath)) { fs.unlinkSync(iconPath); console.log(` šŸ—‘ļø Cleaned up temporary icon: ${name}.icns`); } } catch (error) { console.warn(`Warning: Could not clean up icon ${iconPath}`); } }); } cleanup() { // Clean up temporary icon files generated during tests this.cleanupTempIcons(); // Clean up temporary files and directories this.tempFiles.forEach((file) => { try { if (fs.existsSync(file)) { if (fs.statSync(file).isDirectory()) { fs.rmSync(file, { recursive: true, force: true }); } else { fs.unlinkSync(file); } } } catch (error) { console.warn(`Warning: Could not clean up file ${file}`); } }); this.tempDirs.forEach((dir) => { try { if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); } } catch (error) { console.warn(`Warning: Could not clean up directory ${dir}`); } }); } displayFinalResults() { const passed = this.results.filter((r) => r.passed).length; const total = this.results.length; console.log("\nšŸŽÆ Overall Test Summary"); console.log("======================="); console.log(`Total: ${passed}/${total} tests passed`); if (passed === total) { console.log("šŸŽ‰ All tests passed! CLI is ready for use.\n"); } else { console.log( `āŒ ${total - passed} test(s) failed. Please check the issues above.\n`, ); // Show failed tests const failed = this.results.filter((r) => !r.passed); if (failed.length > 0) { console.log("Failed tests:"); failed.forEach((result) => { const error = result.error ? ` (${result.error})` : ""; console.log(` āŒ ${result.name}${error}`); }); console.log(); } } } } // Command line interface const args = process.argv.slice(2); // Complete test suite by default - no more smart modes const options = { unit: !args.includes("--no-unit"), integration: !args.includes("--no-integration"), builder: !args.includes("--no-builder"), pakeCliTests: args.includes("--pake-cli"), e2e: args.includes("--e2e"), realBuild: !args.includes("--no-build"), // Always include real build test quick: false, // Remove quick mode }; // Help message if (args.includes("--help") || args.includes("-h")) { console.log(` šŸš€ Pake CLI Test Suite Usage: npm test [-- options] Complete Test Suite (Default): npm test # Run complete test suite with real build (8-12 minutes) Test Components: āœ… Unit Tests # CLI commands, validation, response time āœ… Integration Tests # Process spawning, file permissions, dependencies āœ… Builder Tests # Platform detection, architecture, file naming āœ… Real Build Test # Complete GitHub.com app build with packaging Optional Components: --e2e Add end-to-end configuration tests --pake-cli Add pake-cli GitHub Actions tests Skip Components (if needed): --no-unit Skip unit tests --no-integration Skip integration tests --no-builder Skip builder tests --no-build Skip real build test Examples: npm test # Complete test suite (recommended) npm test -- --e2e # Complete suite + end-to-end tests npm test -- --no-build # Skip real build (faster for development) Environment: CI=1 # Enable CI mode DEBUG=1 # Enable debug output PAKE_CREATE_APP=1 # Allow app creation in tests `); process.exit(0); } // Run tests const runner = new PakeTestRunner(); runner .runAll(options) .then((success) => { process.exit(success ? 0 : 1); }) .catch((error) => { console.error("Test runner failed:", error); process.exit(1); }); export default runner;