From 1761802105a4f6df17ef1a14a3f81e580136a7a4 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Fri, 22 Aug 2025 12:47:37 +0800 Subject: [PATCH] :art: Simplify test usage and change names --- .github/workflows/pake-cli.yaml | 2 +- dist/cli.js | 2 +- package-lock.json | 4 +- package.json | 2 +- script/app_config.mjs | 188 ------------------------ script/build_with_pake_cli.js | 158 -------------------- script/configure-tauri.mjs | 196 +++++++++++++++++++++++++ script/github-action-build.js | 224 ++++++++++++++++++++++++++++ tests/README.md | 13 +- tests/build.js | 217 --------------------------- tests/complete.js | 253 -------------------------------- tests/github.js | 12 +- tests/index.js | 101 ++++--------- 13 files changed, 472 insertions(+), 900 deletions(-) delete mode 100755 script/app_config.mjs delete mode 100644 script/build_with_pake_cli.js create mode 100755 script/configure-tauri.mjs create mode 100755 script/github-action-build.js delete mode 100755 tests/build.js delete mode 100644 tests/complete.js diff --git a/.github/workflows/pake-cli.yaml b/.github/workflows/pake-cli.yaml index 413fdad..1eec83a 100644 --- a/.github/workflows/pake-cli.yaml +++ b/.github/workflows/pake-cli.yaml @@ -144,7 +144,7 @@ jobs: - name: Build with pake-cli timeout-minutes: 15 run: | - node ./script/build_with_pake_cli.js + node ./script/github-action-build.js env: URL: ${{ inputs.url }} NAME: ${{ inputs.name }} diff --git a/dist/cli.js b/dist/cli.js index d394916..10fd622 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -56,7 +56,7 @@ var scripts = { build: "npm run tauri build --", "build:debug": "npm run tauri build -- --debug", "build:mac": "npm run tauri build -- --target universal-apple-darwin", - "build:config": "chmod +x script/app_config.mjs && node script/app_config.mjs", + "build:config": "chmod +x script/configure-tauri.mjs && node script/configure-tauri.mjs", analyze: "cd src-tauri && cargo bloat --release --crates", tauri: "tauri", cli: "cross-env NODE_ENV=development rollup -c -w", diff --git a/package-lock.json b/package-lock.json index ad62dd8..d2ca146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pake-cli", - "version": "3.2.8", + "version": "3.2.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pake-cli", - "version": "3.2.8", + "version": "3.2.11", "license": "MIT", "dependencies": { "@tauri-apps/api": "^2.8.0", diff --git a/package.json b/package.json index f161252..c7c5e44 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "npm run tauri build --", "build:debug": "npm run tauri build -- --debug", "build:mac": "npm run tauri build -- --target universal-apple-darwin", - "build:config": "chmod +x script/app_config.mjs && node script/app_config.mjs", + "build:config": "chmod +x script/configure-tauri.mjs && node script/configure-tauri.mjs", "analyze": "cd src-tauri && cargo bloat --release --crates", "tauri": "tauri", "cli": "cross-env NODE_ENV=development rollup -c -w", diff --git a/script/app_config.mjs b/script/app_config.mjs deleted file mode 100755 index 56f93e8..0000000 --- a/script/app_config.mjs +++ /dev/null @@ -1,188 +0,0 @@ -import pakeJson from "../src-tauri/pake.json" assert { type: "json" }; -import tauriJson from "../src-tauri/tauri.conf.json" assert { type: "json" }; -import windowsJson from "../src-tauri/tauri.windows.conf.json" assert { type: "json" }; -import macosJson from "../src-tauri/tauri.macos.conf.json" assert { type: "json" }; -import linuxJson from "../src-tauri/tauri.linux.conf.json" assert { type: "json" }; - -import { writeFileSync, existsSync, copyFileSync } from "fs"; -import os from "os"; - -const desktopEntry = `[Desktop Entry] -Encoding=UTF-8 -Categories=Office -Exec=com-pake-${process.env.NAME} -Icon=com-pake-${process.env.NAME} -Name=com-pake-${process.env.NAME} -Name[zh_CN]=${process.env.NAME_ZH} -StartupNotify=true -Terminal=false -Type=Application -`; - -const variables = { - url: process.env.URL, - name: process.env.NAME, - title: process.env.TITLE, - nameZh: process.env.NAME_ZH, - - pakeConfigPath: "src-tauri/pake.json", - tauriConfigPath: "src-tauri/tauri.conf.json", - identifier: `com.pake.${process.env.NAME}`, - - linux: { - configFilePath: "src-tauri/tauri.linux.conf.json", - iconPath: `src-tauri/png/${process.env.NAME}_512.png`, - productName: `com-pake-${process.env.NAME}`, - defaultIconPath: "src-tauri/png/icon_512.png", - icon: [`png/${process.env.NAME}_512.png`], - desktopEntry, - desktopEntryPath: `src-tauri/assets/com-pake-${process.env.NAME}.desktop`, - desktopEntryConfig: { - configKey: `/usr/share/applications/com-pake-${process.env.NAME}.desktop`, - configValue: `assets/com-pake-${process.env.NAME}.desktop`, - }, - }, - macos: { - configFilePath: "src-tauri/tauri.macos.conf.json", - iconPath: `src-tauri/icons/${process.env.NAME}.icns`, - defaultPath: "src-tauri/icons/icon.icns", - icon: [`icons/${process.env.NAME}.icns`], - }, - windows: { - configFilePath: "src-tauri/tauri.windows.conf.json", - iconPath: `src-tauri/png/${process.env.NAME}_32.ico`, - defaultPath: "src-tauri/png/icon_32.ico", - hdIconPath: `src-tauri/png/${process.env.NAME}_256.ico`, - hdDefaultPath: "src-tauri/png/icon_256.ico", - icon: [`png/${process.env.NAME}_256.ico`, `png/${process.env.NAME}_32.ico`], - resources: [`png/${process.env.NAME}_32.ico`], - }, -}; - -validate(); - -updatePakeJson(); - -updateTauriJson(); - -let platformVariables; -let platformConfig; - -switch (os.platform()) { - case "linux": - platformVariables = variables.linux; - platformConfig = linuxJson; - updateDesktopEntry(); - break; - case "darwin": - platformVariables = variables.macos; - platformConfig = macosJson; - break; - case "win32": - platformConfig = windowsJson; - platformVariables = variables.windows; - updateResources(); - updateIconFile( - platformVariables.hdIconPath, - platformVariables.hdDefaultPath, - ); - break; -} - -updateIconFile(platformVariables.iconPath, platformVariables.defaultIconPath); - -updatePlatformConfig(platformConfig, platformVariables); - -save(); - -function validate() { - if (!("URL" in process.env)) { - console.log("URL is not set"); - process.exit(1); - } - - console.log(`URL: ${process.env.URL}`); - - if (!("NAME" in process.env)) { - console.log("NAME is not set"); - process.exit(1); - } - - console.log(`NAME: ${process.env.NAME}`); - - if (!("TITLE" in process.env)) { - console.log("TITLE is not set"); - process.exit(1); - } - - console.log(`TITLE: ${process.env.TITLE}`); - - if (!("NAME_ZH" in process.env)) { - console.log("NAME_ZH is not set"); - process.exit(1); - } - - console.log(`NAME_ZH: ${process.env.NAME_ZH}`); -} - -function updatePakeJson() { - pakeJson.windows[0].url = variables.url; -} - -function updateTauriJson() { - tauriJson.productName = variables.title; - writeFileSync( - "src-tauri/tauri.conf.json", - JSON.stringify(tauriJson, null, 2), - ); -} - -function updateIconFile(iconPath, defaultIconPath) { - if (!existsSync(iconPath)) { - console.warn( - `Icon for ${process.env.NAME} not found, will use default icon`, - ); - copyFileSync(defaultIconPath, iconPath); - } -} - -function updatePlatformConfig(platformConfig, platformVariables) { - platformConfig.bundle["icon"] = platformVariables.icon; - platformConfig.identifier = variables.identifier; -} - -function save() { - writeFileSync(variables.pakeConfigPath, JSON.stringify(pakeJson, null, 2)); - writeFileSync(variables.tauriConfigPath, JSON.stringify(tauriJson, null, 2)); - - writeFileSync( - variables.linux.configFilePath, - JSON.stringify(linuxJson, null, 2), - ); - writeFileSync( - platformVariables.configFilePath, - JSON.stringify(platformConfig, null, 2), - ); - - writeFileSync( - variables.macos.configFilePath, - JSON.stringify(macosJson, null, 2), - ); - - writeFileSync( - variables.windows.configFilePath, - JSON.stringify(windowsJson, null, 2), - ); -} - -function updateDesktopEntry() { - linuxJson.bundle.linux.deb.files = {}; - linuxJson.bundle.linux.deb.files[ - variables.linux.desktopEntryConfig.configKey - ] = variables.linux.desktopEntryConfig.configValue; - writeFileSync(variables.linux.desktopEntryPath, variables.linux.desktopEntry); -} - -function updateResources() { - windowsJson.bundle.resources = variables.windows.resources; -} diff --git a/script/build_with_pake_cli.js b/script/build_with_pake_cli.js deleted file mode 100644 index ad2deb0..0000000 --- a/script/build_with_pake_cli.js +++ /dev/null @@ -1,158 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { execa } from "execa"; - -// Configuration logging -const logConfiguration = () => { - console.log("Welcome to use pake-cli to build app"); - console.log("Node.js info in your localhost:", process.version); - console.log("\n=======================\n"); - console.log("Pake parameters:"); - console.log("url:", process.env.URL); - console.log("name:", process.env.NAME); - console.log("icon:", process.env.ICON); - console.log("height:", process.env.HEIGHT); - console.log("width:", process.env.WIDTH); - console.log("fullscreen:", process.env.FULLSCREEN); - console.log("hide-title-bar:", process.env.HIDE_TITLE_BAR); - console.log("is multi arch? only for Mac:", process.env.MULTI_ARCH); - console.log("targets type? only for Linux:", process.env.TARGETS); - console.log("===========================\n"); -}; - -// Main execution -const main = async () => { - try { - logConfiguration(); - - const cliPath = path.join(process.cwd(), "node_modules/pake-cli"); - - // Check if pake-cli directory exists - if (!fs.existsSync(cliPath)) { - console.error("Error: pake-cli not found at", cliPath); - console.error( - "Please make sure pake-cli is installed: npm install pake-cli", - ); - process.exit(1); - } - - process.chdir(cliPath); - - // Clean up any previous configuration to ensure fresh build - const pakeDirPath = path.join("src-tauri", ".pake"); - - // Remove .pake directory to force fresh config generation - if (fs.existsSync(pakeDirPath)) { - fs.rmSync(pakeDirPath, { recursive: true, force: true }); - console.log("Cleaned previous .pake directory for fresh build"); - } - - // Also clean any potential target directories that might contain cached configs - const targetDirs = [ - "src-tauri/target", - "src-tauri/target/debug", - "src-tauri/target/release", - "src-tauri/target/universal-apple-darwin", - ]; - - targetDirs.forEach((dir) => { - if (fs.existsSync(dir)) { - // Only remove .pake subdirectories, not the entire target directory - const targetPakeDir = path.join(dir, ".pake"); - if (fs.existsSync(targetPakeDir)) { - fs.rmSync(targetPakeDir, { recursive: true, force: true }); - console.log(`Cleaned .pake directory in ${dir}`); - } - } - }); - - // Build CLI parameters - let params = [ - "dist/cli.js", - process.env.URL, - "--name", - process.env.NAME, - "--height", - process.env.HEIGHT, - "--width", - process.env.WIDTH, - ]; - - if ( - process.env.HIDE_TITLE_BAR === "true" && - process.platform === "darwin" - ) { - params.push("--hide-title-bar"); - } - - if (process.env.FULLSCREEN === "true") { - params.push("--fullscreen"); - } - - if (process.env.MULTI_ARCH === "true" && process.platform === "darwin") { - params.push("--multi-arch"); - } - - if (process.env.TARGETS && process.platform === "linux") { - params.push("--targets", process.env.TARGETS); - } - - if (process.platform === "win32" || process.platform === "linux") { - params.push("--show-system-tray"); - } - - // Add icon parameter if provided - CLI will handle download and conversion - if (process.env.ICON && process.env.ICON !== "") { - params.push("--icon", process.env.ICON); - } else { - console.log( - "No icon provided, pake-cli will attempt to fetch favicon or use default", - ); - } - - console.log("Pake parameters:", params.join(" ")); - console.log("Expected app name:", process.env.NAME); - console.log("Compiling...."); - - // Execute the CLI command - await execa("node", params, { stdio: "inherit" }); - - // Create output directory and move built files - if (!fs.existsSync("output")) { - fs.mkdirSync("output"); - } - - // pake-cli outputs files to current directory with various naming patterns - const files = fs.readdirSync("."); - const appName = process.env.NAME; - const escapedAppName = appName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // Escape regex special chars - - // Create comprehensive pattern for app files - const appFilePattern = new RegExp( - `^(${escapedAppName}|${escapedAppName.toLowerCase()})(_.*|\\..*)$`, - "i", - ); - let foundFiles = false; - - for (const file of files) { - if (appFilePattern.test(file)) { - const destPath = path.join("output", file); - fs.renameSync(file, destPath); - console.log(`Moved: ${file} -> output/${file}`); - foundFiles = true; - } - } - - if (!foundFiles) { - console.log("Warning: No output files found matching pattern"); - } - - console.log("Build Success"); - process.chdir("../.."); - } catch (error) { - console.error("Build failed:", error); - process.exit(1); - } -}; - -main(); diff --git a/script/configure-tauri.mjs b/script/configure-tauri.mjs new file mode 100755 index 0000000..86f5118 --- /dev/null +++ b/script/configure-tauri.mjs @@ -0,0 +1,196 @@ +import pakeJson from "../src-tauri/pake.json" with { type: "json" }; +import tauriJson from "../src-tauri/tauri.conf.json" with { type: "json" }; +import windowsJson from "../src-tauri/tauri.windows.conf.json" with { type: "json" }; +import macosJson from "../src-tauri/tauri.macos.conf.json" with { type: "json" }; +import linuxJson from "../src-tauri/tauri.linux.conf.json" with { type: "json" }; + +import { writeFileSync, existsSync, copyFileSync } from "fs"; +import os from "os"; + +/** + * Configuration script for Tauri app generation + * Sets up platform-specific configurations, icons, and desktop entries + */ + +// Environment validation +const requiredEnvVars = ["URL", "NAME", "TITLE", "NAME_ZH"]; + +function validateEnvironment() { + const missing = requiredEnvVars.filter((key) => !(key in process.env)); + + if (missing.length > 0) { + console.error( + `Missing required environment variables: ${missing.join(", ")}`, + ); + process.exit(1); + } + + console.log("Environment variables:"); + requiredEnvVars.forEach((key) => { + console.log(` ${key}: ${process.env[key]}`); + }); +} + +// Configuration constants +const CONFIG = { + get identifier() { + return `com.pake.${process.env.NAME}`; + }, + get productName() { + return `com-pake-${process.env.NAME}`; + }, + + paths: { + pakeConfig: "src-tauri/pake.json", + tauriConfig: "src-tauri/tauri.conf.json", + }, + + platforms: { + linux: { + configFile: "src-tauri/tauri.linux.conf.json", + iconPath: `src-tauri/png/${process.env.NAME}_512.png`, + defaultIcon: "src-tauri/png/icon_512.png", + icons: [`png/${process.env.NAME}_512.png`], + get desktopEntry() { + return `[Desktop Entry] +Encoding=UTF-8 +Categories=Office +Exec=${CONFIG.productName} +Icon=${CONFIG.productName} +Name=${CONFIG.productName} +Name[zh_CN]=${process.env.NAME_ZH} +StartupNotify=true +Terminal=false +Type=Application +`; + }, + get desktopEntryPath() { + return `src-tauri/assets/${CONFIG.productName}.desktop`; + }, + get desktopConfig() { + return { + key: `/usr/share/applications/${CONFIG.productName}.desktop`, + value: `assets/${CONFIG.productName}.desktop`, + }; + }, + }, + + darwin: { + configFile: "src-tauri/tauri.macos.conf.json", + iconPath: `src-tauri/icons/${process.env.NAME}.icns`, + defaultIcon: "src-tauri/icons/icon.icns", + icons: [`icons/${process.env.NAME}.icns`], + }, + + windows: { + configFile: "src-tauri/tauri.windows.conf.json", + iconPath: `src-tauri/png/${process.env.NAME}_32.ico`, + hdIconPath: `src-tauri/png/${process.env.NAME}_256.ico`, + defaultIcon: "src-tauri/png/icon_32.ico", + hdDefaultIcon: "src-tauri/png/icon_256.ico", + icons: [ + `png/${process.env.NAME}_256.ico`, + `png/${process.env.NAME}_32.ico`, + ], + resources: [`png/${process.env.NAME}_32.ico`], + }, + }, +}; + +// Core configuration functions +function updateBaseConfigs() { + // Update pake.json + pakeJson.windows[0].url = process.env.URL; + + // Update tauri.conf.json + tauriJson.productName = process.env.TITLE; + tauriJson.identifier = CONFIG.identifier; +} + +function ensureIconExists(iconPath, defaultPath, description = "icon") { + if (!existsSync(iconPath)) { + console.warn( + `${description} for ${process.env.NAME} not found, using default`, + ); + copyFileSync(defaultPath, iconPath); + } +} + +function updatePlatformConfig(platformConfig, platformVars) { + platformConfig.bundle.icon = platformVars.icons; + platformConfig.identifier = CONFIG.identifier; +} + +// Platform-specific handlers +const platformHandlers = { + linux: (config) => { + ensureIconExists(config.iconPath, config.defaultIcon, "Linux icon"); + + // Update desktop entry + linuxJson.bundle.linux.deb.files = { + [config.desktopConfig.key]: config.desktopConfig.value, + }; + writeFileSync(config.desktopEntryPath, config.desktopEntry); + + updatePlatformConfig(linuxJson, config); + }, + + darwin: (config) => { + ensureIconExists(config.iconPath, config.defaultIcon, "macOS icon"); + updatePlatformConfig(macosJson, config); + }, + + win32: (config) => { + ensureIconExists(config.iconPath, config.defaultIcon, "Windows icon"); + ensureIconExists( + config.hdIconPath, + config.hdDefaultIcon, + "Windows HD icon", + ); + + windowsJson.bundle.resources = config.resources; + updatePlatformConfig(windowsJson, config); + }, +}; + +function saveConfigurations() { + const configs = [ + { path: CONFIG.paths.pakeConfig, data: pakeJson }, + { path: CONFIG.paths.tauriConfig, data: tauriJson }, + { path: CONFIG.platforms.linux.configFile, data: linuxJson }, + { path: CONFIG.platforms.darwin.configFile, data: macosJson }, + { path: CONFIG.platforms.windows.configFile, data: windowsJson }, + ]; + + configs.forEach(({ path, data }) => { + writeFileSync(path, JSON.stringify(data, null, 2)); + }); +} + +// Main execution +function main() { + try { + validateEnvironment(); + updateBaseConfigs(); + + const platform = os.platform(); + const platformConfig = CONFIG.platforms[platform]; + + if (!platformConfig) { + throw new Error(`Unsupported platform: ${platform}`); + } + + const handler = platformHandlers[platform]; + if (handler) { + handler(platformConfig); + } + + saveConfigurations(); + console.log(`โœ… Tauri configuration complete for ${platform}`); + } catch (error) { + console.error("โŒ Configuration failed:", error.message); + process.exit(1); + } +} + +main(); diff --git a/script/github-action-build.js b/script/github-action-build.js new file mode 100755 index 0000000..4b5c4c9 --- /dev/null +++ b/script/github-action-build.js @@ -0,0 +1,224 @@ +import fs from "fs"; +import path from "path"; +import { execa } from "execa"; + +/** + * GitHub Actions build script for Pake CLI + * Handles environment setup, parameter building, and output management + */ + +// Environment variables expected from GitHub Actions +const ENV_VARS = { + required: ["URL", "NAME", "HEIGHT", "WIDTH"], + optional: ["ICON", "FULLSCREEN", "HIDE_TITLE_BAR", "MULTI_ARCH", "TARGETS"], +}; + +// Platform-specific configurations +const PLATFORM_CONFIG = { + darwin: { + supportsHideTitleBar: true, + supportsMultiArch: true, + needsSystemTray: false, + }, + linux: { + supportsTargets: true, + needsSystemTray: true, + }, + win32: { + needsSystemTray: true, + }, +}; + +class PakeBuildManager { + constructor() { + this.platform = process.platform; + this.config = PLATFORM_CONFIG[this.platform] || {}; + } + + logConfiguration() { + console.log("๐Ÿš€ Pake CLI Build Started"); + console.log(`๐Ÿ“‹ Node.js version: ${process.version}`); + console.log(`๐Ÿ–ฅ๏ธ Platform: ${this.platform}`); + console.log("\n" + "=".repeat(50)); + console.log("๐Ÿ“ Build Parameters:"); + + ENV_VARS.required.forEach((key) => { + console.log(` ${key}: ${process.env[key]}`); + }); + + ENV_VARS.optional.forEach((key) => { + if (process.env[key]) { + console.log(` ${key}: ${process.env[key]}`); + } + }); + console.log("=".repeat(50) + "\n"); + } + + validateEnvironment() { + const missing = ENV_VARS.required.filter((key) => !process.env[key]); + + if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(", ")}`, + ); + } + } + + setupWorkspace() { + const cliPath = path.join(process.cwd(), "node_modules/pake-cli"); + + if (!fs.existsSync(cliPath)) { + throw new Error( + `pake-cli not found at ${cliPath}. Run: npm install pake-cli`, + ); + } + + process.chdir(cliPath); + this.cleanPreviousBuilds(); + + return cliPath; + } + + cleanPreviousBuilds() { + const cleanupPaths = [ + "src-tauri/.pake", + "src-tauri/target/.pake", + "src-tauri/target/debug/.pake", + "src-tauri/target/release/.pake", + "src-tauri/target/universal-apple-darwin/.pake", + ]; + + cleanupPaths.forEach((dirPath) => { + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true, force: true }); + console.log(`๐Ÿงน Cleaned: ${dirPath}`); + } + }); + } + + buildCliParameters() { + const params = [ + "dist/cli.js", + process.env.URL, + "--name", + process.env.NAME, + "--height", + process.env.HEIGHT, + "--width", + process.env.WIDTH, + ]; + + // Platform-specific parameters + if ( + this.config.supportsHideTitleBar && + process.env.HIDE_TITLE_BAR === "true" + ) { + params.push("--hide-title-bar"); + } + + if (process.env.FULLSCREEN === "true") { + params.push("--fullscreen"); + } + + if (this.config.supportsMultiArch && process.env.MULTI_ARCH === "true") { + params.push("--multi-arch"); + } + + if (this.config.supportsTargets && process.env.TARGETS) { + params.push("--targets", process.env.TARGETS); + } + + if (this.config.needsSystemTray) { + params.push("--show-system-tray"); + } + + // Icon handling + if (process.env.ICON?.trim()) { + params.push("--icon", process.env.ICON); + } else { + console.log( + "โ„น๏ธ No icon provided, will attempt to fetch favicon or use default", + ); + } + + return params; + } + + async executeBuild(params) { + console.log(`๐Ÿ”ง Command: node ${params.join(" ")}`); + console.log(`๐Ÿ“ฑ Building app: ${process.env.NAME}`); + console.log("โณ Compiling...\n"); + + await execa("node", params, { stdio: "inherit" }); + } + + organizeOutput() { + const outputDir = "output"; + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); + } + + const appName = process.env.NAME; + const filePattern = new RegExp( + `^(${this.escapeRegex(appName)}|${this.escapeRegex(appName.toLowerCase())})(_.*|\\..*)$`, + "i", + ); + + const files = fs.readdirSync("."); + let movedFiles = 0; + + files.forEach((file) => { + if (filePattern.test(file)) { + const destPath = path.join(outputDir, file); + fs.renameSync(file, destPath); + console.log(`๐Ÿ“ฆ Packaged: ${file}`); + movedFiles++; + } + }); + + if (movedFiles === 0) { + console.warn( + "โš ๏ธ Warning: No output files found matching expected pattern", + ); + } + + return movedFiles; + } + + escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + + async run() { + try { + this.logConfiguration(); + this.validateEnvironment(); + + this.setupWorkspace(); + const params = this.buildCliParameters(); + + await this.executeBuild(params); + + const fileCount = this.organizeOutput(); + + console.log(`\nโœ… Build completed successfully!`); + console.log(`๐Ÿ“ฆ Generated ${fileCount} output file(s)`); + + // Return to original directory + process.chdir("../.."); + } catch (error) { + console.error("\nโŒ Build failed:", error.message); + + if (error.stderr) { + console.error("Error details:", error.stderr); + } + + process.exit(1); + } + } +} + +// Execute build +const buildManager = new PakeBuildManager(); +buildManager.run(); diff --git a/tests/README.md b/tests/README.md index 8af3235..1f408c4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,9 +5,20 @@ ## ่ฟ่กŒๆต‹่ฏ• ```bash -npm test +# ๅฎŒๆ•ดๆต‹่ฏ•ๅฅ—ไปถ๏ผˆๆŽจ่๏ผ‰ +npm test # ่ฟ่กŒๅฎŒๆ•ดๆต‹่ฏ•ๅฅ—ไปถ๏ผŒๅŒ…ๅซ็œŸๅฎžๆž„ๅปบๆต‹่ฏ•๏ผˆ8-12ๅˆ†้’Ÿ๏ผ‰ + +# ๅผ€ๅ‘ๆ—ถๅฟซ้€Ÿๆต‹่ฏ• +npm test -- --no-build # ่ทณ่ฟ‡ๆž„ๅปบๆต‹่ฏ•๏ผŒไป…้ชŒ่ฏๆ ธๅฟƒๅŠŸ่ƒฝ๏ผˆ30็ง’๏ผ‰ ``` +### ๐Ÿš€ ๅฎŒๆ•ดๆต‹่ฏ•ๅฅ—ไปถๅŒ…ๅซ๏ผš + +- โœ… **ๅ•ๅ…ƒๆต‹่ฏ•**๏ผšCLIๅ‘ฝไปคใ€ๅ‚ๆ•ฐ้ชŒ่ฏใ€ๅ“ๅบ”ๆ—ถ้—ด +- โœ… **้›†ๆˆๆต‹่ฏ•**๏ผš่ฟ›็จ‹็ฎก็†ใ€ๆ–‡ไปถๆƒ้™ใ€ไพ่ต–่งฃๆž +- โœ… **ๆž„ๅปบๅ™จๆต‹่ฏ•**๏ผšๅนณๅฐๆฃ€ๆต‹ใ€ๆžถๆž„ๆฃ€ๆต‹ใ€ๆ–‡ไปถๅ‘ฝๅ +- โœ… **็œŸๅฎžๆž„ๅปบๆต‹่ฏ•**๏ผšๅฎŒๆ•ด็š„GitHub.comๅบ”็”จๆ‰“ๅŒ…้ชŒ่ฏ + ## ๆต‹่ฏ•ๅ†…ๅฎน ### ๅ•ๅ…ƒๆต‹่ฏ•๏ผˆ6ไธช๏ผ‰ diff --git a/tests/build.js b/tests/build.js deleted file mode 100755 index 9c08435..0000000 --- a/tests/build.js +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env node - -/** - * GitHub.com Real Build Test - * - * This is a standalone test for actual GitHub.com app packaging - * to validate that both CLI and GitHub Actions scenarios work correctly. - */ - -import { spawn, execSync } from "child_process"; -import fs from "fs"; -import path from "path"; -import config from "./config.js"; - -console.log("๐Ÿ™ GitHub.com Real Build Test"); -console.log("==============================\n"); - -const testName = "GitHubRealTest"; -const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`); -const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`); - -// Cleanup function -const cleanup = () => { - try { - if (fs.existsSync(appFile)) { - if (fs.statSync(appFile).isDirectory()) { - fs.rmSync(appFile, { recursive: true, force: true }); - } else { - fs.unlinkSync(appFile); - } - console.log("โœ… Cleaned up .app file"); - } - if (fs.existsSync(dmgFile)) { - fs.unlinkSync(dmgFile); - console.log("โœ… Cleaned up .dmg file"); - } - - // Clean .pake directory - const pakeDir = path.join(config.PROJECT_ROOT, "src-tauri", ".pake"); - if (fs.existsSync(pakeDir)) { - fs.rmSync(pakeDir, { recursive: true, force: true }); - console.log("โœ… Cleaned up .pake directory"); - } - } catch (error) { - console.warn("โš ๏ธ Cleanup warning:", error.message); - } -}; - -// Handle cleanup on exit -process.on("exit", cleanup); -process.on("SIGINT", () => { - console.log("\n๐Ÿ›‘ Build interrupted by user"); - cleanup(); - process.exit(1); -}); -process.on("SIGTERM", cleanup); - -console.log("๐Ÿ”ง Testing GitHub.com packaging with CLI..."); -console.log( - `Command: node ${config.CLI_PATH} https://github.com --name ${testName} --debug --width 1200 --height 780\n`, -); - -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_CREATE_APP: "1", - }, -}); - -let buildStarted = false; -let configGenerated = false; -let compilationStarted = false; - -console.log("๐Ÿ“‹ Build Progress:"); -console.log("------------------"); - -child.stdout.on("data", (data) => { - const output = data.toString(); - - // Track build progress - if (output.includes("Installing package")) { - console.log("๐Ÿ“ฆ Installing pake dependencies..."); - } - if (output.includes("Package installed")) { - console.log("โœ… Package installation completed"); - } - if (output.includes("Building app")) { - buildStarted = true; - console.log("๐Ÿ—๏ธ Build process started..."); - } - if (output.includes("Compiling")) { - compilationStarted = true; - console.log("โš™๏ธ Rust compilation started..."); - } - if (output.includes("Bundling")) { - console.log("๐Ÿ“ฆ App bundling started..."); - } - if (output.includes("Built application at:")) { - console.log("โœ… Application built successfully!"); - } -}); - -child.stderr.on("data", (data) => { - const output = data.toString(); - - // Track stderr progress (Tauri outputs build info to stderr) - if (output.includes("Installing package")) { - console.log("๐Ÿ“ฆ Installing pake dependencies..."); - } - if (output.includes("Building app")) { - buildStarted = true; - console.log("๐Ÿ—๏ธ Build process started..."); - } - if (output.includes("Compiling")) { - compilationStarted = true; - console.log("โš™๏ธ Rust compilation started..."); - } - if (output.includes("Finished")) { - console.log("โœ… Rust compilation finished!"); - } - if (output.includes("Bundling")) { - console.log("๐Ÿ“ฆ App bundling started..."); - } - if (output.includes("Built application at:")) { - console.log("โœ… Application built successfully!"); - } - - // Only show actual errors, filter out build progress - if ( - !output.includes("warning:") && - !output.includes("verbose") && - !output.includes("npm info") && - !output.includes("Installing package") && - !output.includes("Package installed") && - !output.includes("Building app") && - !output.includes("Compiling") && - !output.includes("Finished") && - !output.includes("Built application at:") && - !output.includes("Bundling") && - !output.includes("npm http") && - output.trim().length > 0 - ) { - console.log("โŒ Build error:", output.trim()); - } -}); - -// Set a 3-minute timeout for the test -const timeout = setTimeout(() => { - console.log("\nโฑ๏ธ Build timeout reached (3 minutes)"); - child.kill("SIGTERM"); - - if (buildStarted && compilationStarted) { - console.log("โœ… SUCCESS: GitHub.com CLI build started successfully!"); - console.log(" - Build process initiated โœ“"); - console.log(" - Rust compilation started โœ“"); - console.log(" - Configuration generated for GitHub.com โœ“"); - console.log("\n๐ŸŽฏ Test Result: PASS"); - console.log(" The GitHub.com app build is working correctly."); - console.log( - " Build was terminated early to save time, but core functionality verified.", - ); - process.exit(0); - } else if (buildStarted) { - console.log("โš ๏ธ PARTIAL: Build started but compilation not detected"); - console.log("๐ŸŽฏ Test Result: PARTIAL PASS"); - process.exit(0); - } else { - console.log("โŒ FAIL: Build did not start within timeout"); - console.log("๐ŸŽฏ Test Result: FAIL"); - process.exit(1); - } -}, 180000); // 3 minutes - -child.on("close", (code) => { - clearTimeout(timeout); - - console.log(`\n๐Ÿ“Š Build Process Summary:`); - console.log("========================"); - console.log(`Exit Code: ${code}`); - console.log(`Build Started: ${buildStarted ? "โœ…" : "โŒ"}`); - console.log(`Compilation Started: ${compilationStarted ? "โœ…" : "โŒ"}`); - - // Check for output files - const appExists = fs.existsSync(appFile); - const dmgExists = fs.existsSync(dmgFile); - console.log(`App File (.app): ${appExists ? "โœ…" : "โŒ"}`); - console.log(`DMG File: ${dmgExists ? "โœ…" : "โŒ"}`); - - if (buildStarted && compilationStarted) { - console.log("\n๐ŸŽ‰ SUCCESS: GitHub.com CLI build verification completed!"); - console.log(" All critical build stages detected."); - process.exit(0); - } else if (buildStarted) { - console.log( - "\nโš ๏ธ PARTIAL SUCCESS: Build started but may not have completed", - ); - process.exit(0); - } else { - console.log("\nโŒ FAILED: Build did not start properly"); - process.exit(1); - } -}); - -child.on("error", (error) => { - clearTimeout(timeout); - console.log(`\nโŒ Process Error: ${error.message}`); - console.log("๐ŸŽฏ Test Result: FAIL"); - process.exit(1); -}); - -// Send empty input to handle any prompts -child.stdin.end(); diff --git a/tests/complete.js b/tests/complete.js deleted file mode 100644 index be126c5..0000000 --- a/tests/complete.js +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env node - -/** - * GitHub.com Complete Build Test - * - * This test performs a complete build of github.com to verify - * that the entire packaging pipeline works correctly end-to-end. - */ - -import { spawn } from "child_process"; -import fs from "fs"; -import path from "path"; -import config from "./config.js"; - -console.log("๐Ÿ™ GitHub.com Complete Build Test"); -console.log("==================================\n"); - -const testName = "GitHub"; -const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`); -const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`); - -// Cleanup function -const cleanup = () => { - try { - if (fs.existsSync(appFile)) { - if (fs.statSync(appFile).isDirectory()) { - fs.rmSync(appFile, { recursive: true, force: true }); - } else { - fs.unlinkSync(appFile); - } - console.log("โœ… Cleaned up .app file"); - } - if (fs.existsSync(dmgFile)) { - fs.unlinkSync(dmgFile); - console.log("โœ… Cleaned up .dmg file"); - } - - // Clean .pake directory - const pakeDir = path.join(config.PROJECT_ROOT, "src-tauri", ".pake"); - if (fs.existsSync(pakeDir)) { - fs.rmSync(pakeDir, { recursive: true, force: true }); - console.log("โœ… Cleaned up .pake directory"); - } - } catch (error) { - console.warn("โš ๏ธ Cleanup warning:", error.message); - } -}; - -// Handle cleanup on exit -process.on("exit", cleanup); -process.on("SIGINT", () => { - console.log("\n๐Ÿ›‘ Build interrupted by user"); - cleanup(); - process.exit(1); -}); -process.on("SIGTERM", cleanup); - -console.log("๐Ÿ”ง Testing GitHub app packaging with optimal settings..."); -console.log( - `Command: pake https://github.com --name ${testName} --width 1200 --height 800 --hide-title-bar\n`, -); - -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; -let bundlingStarted = false; -let buildCompleted = false; - -console.log("๐Ÿ“‹ Build Progress:"); -console.log("------------------"); - -child.stdout.on("data", (data) => { - const output = data.toString(); - - // Track build progress - if (output.includes("Installing package")) { - console.log("๐Ÿ“ฆ Installing pake dependencies..."); - } - if (output.includes("Package installed")) { - console.log("โœ… Package installation completed"); - } - if (output.includes("Building app")) { - buildStarted = true; - console.log("๐Ÿ—๏ธ Build process started..."); - } - if (output.includes("Compiling")) { - compilationStarted = true; - console.log("โš™๏ธ Rust compilation started..."); - } - if (output.includes("Bundling")) { - bundlingStarted = true; - console.log("๐Ÿ“ฆ App bundling started..."); - } - if (output.includes("Built application at:")) { - buildCompleted = true; - console.log("โœ… Application built successfully!"); - } - if (output.includes("GitHub")) { - console.log("๐Ÿ™ GitHub app configuration detected"); - } -}); - -child.stderr.on("data", (data) => { - const output = data.toString(); - - // Track stderr progress (Tauri outputs build info to stderr) - if (output.includes("Installing package")) { - console.log("๐Ÿ“ฆ Installing pake dependencies..."); - } - if (output.includes("Building app")) { - buildStarted = true; - console.log("๐Ÿ—๏ธ Build process started..."); - } - if (output.includes("Compiling")) { - compilationStarted = true; - console.log("โš™๏ธ Rust compilation started..."); - } - if (output.includes("Finished")) { - console.log("โœ… Rust compilation finished!"); - } - if (output.includes("Bundling")) { - bundlingStarted = true; - console.log("๐Ÿ“ฆ App bundling started..."); - } - if (output.includes("Built application at:")) { - buildCompleted = true; - console.log("โœ… Application built successfully!"); - } - - // Only show actual errors, filter out build progress - if ( - !output.includes("warning:") && - !output.includes("verbose") && - !output.includes("npm info") && - !output.includes("Installing package") && - !output.includes("Package installed") && - !output.includes("Building app") && - !output.includes("Compiling") && - !output.includes("Finished") && - !output.includes("Built application at:") && - !output.includes("Bundling") && - !output.includes("npm http") && - !output.includes("Info Looking up installed") && - output.trim().length > 0 - ) { - console.log("โŒ Build error:", output.trim()); - } -}); - -// Set a 10-minute timeout for the complete build (real packaging takes time) -// DON'T kill the process early - let it complete naturally -const timeout = setTimeout(() => { - console.log("\nโฑ๏ธ Build timeout reached (10 minutes)"); - - // Check if we actually have output files even if process is still running - const appExists = fs.existsSync(appFile); - const dmgExists = fs.existsSync(dmgFile); - - if (appExists || buildCompleted) { - console.log("๐ŸŽ‰ SUCCESS: GitHub app was built successfully!"); - console.log(" App file exists, build completed despite long duration"); - child.kill("SIGTERM"); - process.exit(0); - } else { - console.log("โŒ TIMEOUT: Build did not complete within 10 minutes"); - child.kill("SIGTERM"); - process.exit(1); - } -}, 600000); // 10 minutes - -child.on("close", (code) => { - clearTimeout(timeout); - - console.log(`\n๐Ÿ“Š GitHub App Build Summary:`); - console.log("============================="); - console.log(`Exit Code: ${code}`); - console.log(`Build Started: ${buildStarted ? "โœ…" : "โŒ"}`); - console.log(`Compilation Started: ${compilationStarted ? "โœ…" : "โŒ"}`); - console.log(`Bundling Started: ${bundlingStarted ? "โœ…" : "โŒ"}`); - console.log(`Build Completed: ${buildCompleted ? "โœ…" : "โŒ"}`); - - // Check for output files - const appExists = fs.existsSync(appFile); - const dmgExists = fs.existsSync(dmgFile); - console.log(`App File (.app): ${appExists ? "โœ…" : "โŒ"}`); - console.log(`DMG File: ${dmgExists ? "โœ…" : "โŒ"}`); - - // Check .app bundle structure if it exists - if (appExists) { - try { - const contentsPath = path.join(appFile, "Contents"); - const macOSPath = path.join(contentsPath, "MacOS"); - const resourcesPath = path.join(contentsPath, "Resources"); - - console.log(`App Bundle Structure:`); - console.log(` Contents/: ${fs.existsSync(contentsPath) ? "โœ…" : "โŒ"}`); - console.log( - ` Contents/MacOS/: ${fs.existsSync(macOSPath) ? "โœ…" : "โŒ"}`, - ); - console.log( - ` Contents/Resources/: ${fs.existsSync(resourcesPath) ? "โœ…" : "โŒ"}`, - ); - } catch (error) { - console.log(`App Bundle Check: โŒ (${error.message})`); - } - } - - // Real success check: app file must exist and build must have completed - if (appExists && (buildCompleted || code === 0)) { - console.log("\n๐ŸŽ‰ COMPLETE SUCCESS: GitHub app build fully completed!"); - console.log(" ๐Ÿ™ GitHub.com successfully packaged as desktop app"); - console.log(" ๐ŸŽฏ Build completed with app file generated"); - console.log(" ๐Ÿ“ฑ App bundle created with proper structure"); - process.exit(0); - } else if (appExists) { - console.log("\nโœ… SUCCESS: GitHub app was built successfully!"); - console.log(" ๐Ÿ™ GitHub.com packaging completed with app file"); - console.log(" ๐ŸŽฏ Build process successful"); - process.exit(0); - } else if (code === 0 && buildStarted && compilationStarted) { - console.log( - "\nโš ๏ธ PARTIAL SUCCESS: Build process completed but no app file found", - ); - console.log(" ๐Ÿ™ GitHub.com build process executed successfully"); - console.log(" โš ๏ธ App file may be in a different location"); - process.exit(0); - } else { - console.log("\nโŒ FAILED: GitHub app build did not complete successfully"); - console.log(" โŒ No app file generated or build process failed"); - process.exit(1); - } -}); - -child.on("error", (error) => { - clearTimeout(timeout); - console.log(`\nโŒ Process Error: ${error.message}`); - console.log("๐ŸŽฏ Test Result: FAIL"); - process.exit(1); -}); - -// Send empty input to handle any prompts -child.stdin.end(); diff --git a/tests/github.js b/tests/github.js index cd68a79..247b20e 100644 --- a/tests/github.js +++ b/tests/github.js @@ -234,7 +234,7 @@ runner.addTest( "GitHub Actions Environment Simulation", async () => { try { - // Create a temporary test script that simulates build_with_pake_cli.js + // Create a temporary test script that simulates github-action-build.js const testScript = ` const { execSync } = require('child_process'); @@ -298,7 +298,7 @@ runner.addTest( runner.trackTempDir(tempDir); - // Test cleanup script logic (from build_with_pake_cli.js) + // Test cleanup script logic (from github-action-build.js) const cleanupScript = ` const fs = require('fs'); const path = require('path'); @@ -340,7 +340,7 @@ process.exit(cleanedDirs > 0 ? 0 : 1); } }, TIMEOUTS.MEDIUM, - "Tests configuration cleanup logic from build_with_pake_cli.js", + "Tests configuration cleanup logic from github-action-build.js", ); // Test 5: Icon fetching simulation @@ -539,7 +539,7 @@ runner.addTest( const requiredElements = [ "npm install pake-cli@latest --no-package-lock", // Latest version installation "timeout-minutes: 15", // Sufficient timeout - "node ./script/build_with_pake_cli.js", // Build script execution + "node ./script/github-action-build.js", // Build script execution "ubuntu-24.04", // Linux support "macos-latest", // macOS support "windows-latest", // Windows support @@ -738,7 +738,7 @@ console.log('URL:', process.env.URL); console.log('NAME:', process.env.NAME); console.log('WIDTH x HEIGHT:', process.env.WIDTH + 'x' + process.env.HEIGHT); -// Simulate the build script execution (script/build_with_pake_cli.js equivalent) +// Simulate the build script execution (script/github-action-build.js equivalent) const fs = require('fs'); // Simulate pake-cli installation check @@ -800,7 +800,7 @@ const path = require('path'); const { execSync } = require('child_process'); // Check if build script exists -const buildScript = path.join(process.cwd(), 'script', 'build_with_pake_cli.js'); +const buildScript = path.join(process.cwd(), 'script', 'github-action-build.js'); const fs = require('fs'); if (!fs.existsSync(buildScript)) { diff --git a/tests/index.js b/tests/index.js index 1f47de3..2f71227 100644 --- a/tests/index.js +++ b/tests/index.js @@ -81,11 +81,6 @@ class PakeTestRunner { } } - if (quick) { - console.log("โšก Running Quick Tests..."); - await this.runQuickTests(); - } - this.cleanup(); this.displayFinalResults(); @@ -1047,49 +1042,6 @@ class PakeTestRunner { ); } - async runQuickTests() { - // Only run essential tests for quick mode - await this.runTest( - "Quick Version Check", - () => { - const output = execSync(`node "${config.CLI_PATH}" --version`, { - encoding: "utf8", - timeout: 3000, - }); - return /^\d+\.\d+\.\d+/.test(output.trim()); - }, - TIMEOUTS.QUICK, - ); - - await this.runTest( - "Quick Help Check", - () => { - const output = execSync(`node "${config.CLI_PATH}" --help`, { - encoding: "utf8", - timeout: 3000, - }); - return output.includes("Usage: cli [url] [options]"); - }, - TIMEOUTS.QUICK, - ); - - await this.runTest( - "Quick Environment Check", - () => { - const platform = process.platform; - const arch = process.arch; - const nodeVersion = process.version; - - return ( - typeof platform === "string" && - typeof arch === "string" && - nodeVersion.startsWith("v") - ); - }, - TIMEOUTS.QUICK, - ); - } - // Helper function to check if files exist (handles wildcards) checkFileExists(filePath) { if (filePath.includes("*")) { @@ -1238,15 +1190,15 @@ class PakeTestRunner { // Command line interface const args = process.argv.slice(2); -// Parse command line arguments +// Complete test suite by default - no more smart modes const options = { - unit: args.includes("--unit") || args.length === 0, - integration: args.includes("--integration") || args.length === 0, - builder: args.includes("--builder") || args.length === 0, + unit: !args.includes("--no-unit"), + integration: !args.includes("--no-integration"), + builder: !args.includes("--no-builder"), pakeCliTests: args.includes("--pake-cli"), - e2e: args.includes("--e2e") || args.includes("--full"), - realBuild: args.includes("--real-build") || args.length === 0, // Include real build in default tests - quick: args.includes("--quick"), + e2e: args.includes("--e2e"), + realBuild: !args.includes("--no-build"), // Always include real build test + quick: false, // Remove quick mode }; // Help message @@ -1254,26 +1206,31 @@ if (args.includes("--help") || args.includes("-h")) { console.log(` ๐Ÿš€ Pake CLI Test Suite -Usage: node tests/index.js [options] +Usage: npm test [-- options] -Options: - --unit Run unit tests (default) - --integration Run integration tests (default) - --builder Run builder tests (default) - --pake-cli Run pake-cli GitHub Actions tests - --e2e, --full Run end-to-end tests - --real-build Run complete real build test (8+ minutes on non-macOS, 12+ minutes multi-arch on macOS) - --quick Run only essential tests (fast) - --help, -h Show this help message +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 # Run all default tests (multi-arch on macOS) - node tests/index.js # Run all default tests (multi-arch on macOS) - node tests/index.js --quick # Quick test (30 seconds) - node tests/index.js --real-build # Complete build test (multi-arch on macOS, single-arch elsewhere) - node tests/index.js --pake-cli # GitHub Actions tests - node tests/index.js --e2e # Full end-to-end tests - node tests/index.js --unit --integration # Specific tests only + 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