apptuner 0.1.0 → 0.1.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/dist/cli.js CHANGED
@@ -82,9 +82,9 @@ async function startCommand(options) {
82
82
  });
83
83
  await new Promise((resolve) => setTimeout(resolve, 1e3));
84
84
  spinner.succeed("File watcher started (port 3030)");
85
- const relayUrl = process.env.APPTUNER_RELAY_URL || "wss://apptuner-relay.falling-bird-3f63.workers.dev";
85
+ const relayUrl = process.env.APPTUNER_RELAY_URL || "wss://relay.apptuner.io";
86
86
  const isLocalTesting = relayUrl.includes("localhost") || relayUrl.includes("127.0.0.1");
87
- const dashboardUrl = isLocalTesting ? `http://localhost:1420/?session=${sessionId}` : `https://apptuner-dashboard.pages.dev/?session=${sessionId}`;
87
+ const dashboardUrl = isLocalTesting ? `http://localhost:1420/?session=${sessionId}` : `https://apptuner.io/?session=${sessionId}`;
88
88
  let isShuttingDown = false;
89
89
  let isFirstConnect = true;
90
90
  let reconnectTimeout = null;
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src-cli/cli.ts", "../src-cli/commands/start.ts", "../src-cli/commands/stop.ts", "../src-cli/commands/status.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { startCommand } from './commands/start.js';\nimport { stopCommand } from './commands/stop.js';\nimport { statusCommand } from './commands/status.js';\n\nconst program = new Command();\n\nprogram\n .name('apptuner')\n .description('Hot reload React Native apps instantly')\n .version('0.1.0');\n\nprogram\n .command('start')\n .description('Start AppTuner development server')\n .option('-p, --project <path>', 'Path to your React Native project', process.cwd())\n .option('--no-qr', 'Disable QR code display')\n .action(startCommand);\n\nprogram\n .command('stop')\n .description('Stop all AppTuner services')\n .action(stopCommand);\n\nprogram\n .command('status')\n .description('Show AppTuner connection status')\n .action(statusCommand);\n\nprogram.parse();\n", "import { spawn } from 'child_process';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs/promises';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { WebSocket } from 'ws';\nimport { exec } from 'child_process';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface StartOptions {\n project: string;\n qr: boolean;\n}\n\n// Generate random 6-character session ID\nfunction generateSessionId(): string {\n const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';\n let result = '';\n for (let i = 0; i < 6; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n// Adapter that wraps the relay WebSocket for sending bundles - supports hot-swapping on reconnect\nclass RelayAdapter {\n private ws: WebSocket | null = null;\n\n updateWebSocket(ws: WebSocket): void {\n this.ws = ws;\n }\n\n sendBundleUpdate(bundleCode: string): void {\n if (!this.ws || this.ws.readyState !== this.ws.OPEN) {\n console.warn(chalk.yellow('\u26A0\uFE0F Cannot send bundle - relay not connected'));\n return;\n }\n this.ws.send(JSON.stringify({\n type: 'bundle_update',\n payload: { code: bundleCode },\n timestamp: Date.now(),\n }));\n }\n}\n\nexport async function startCommand(options: StartOptions) {\n const spinner = ora();\n\n console.log(chalk.blue.bold('\\n\uD83D\uDE80 AppTuner\\n'));\n\n // Step 1: Validate project\n spinner.start('Validating React Native project...');\n const projectPath = path.resolve(options.project);\n const packageJsonPath = path.join(projectPath, 'package.json');\n\n try {\n await fs.access(packageJsonPath);\n } catch {\n spinner.fail('No package.json found');\n console.error(chalk.red(`\\n\u274C Could not find package.json at ${projectPath}`));\n console.log(chalk.gray('\\nMake sure you\\'re in a React Native project directory.\\n'));\n process.exit(1);\n }\n\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));\n\n if (!packageJson.dependencies?.['react-native']) {\n spinner.fail('Not a React Native project');\n console.error(chalk.red('\\n\u274C This is not a React Native project'));\n console.log(chalk.gray('No react-native dependency found in package.json\\n'));\n process.exit(1);\n }\n\n spinner.succeed(`Project validated: ${chalk.cyan(packageJson.name || 'Unnamed Project')}`);\n\n // Step 2: Generate session ID (stays the same across reconnects)\n const sessionId = generateSessionId();\n spinner.succeed(`Session ID: ${chalk.cyan(sessionId)}`);\n\n // Step 3: Start local services\n console.log(chalk.white('\\nStarting local services...\\n'));\n\n // __dirname in compiled dist/cli.js is the dist/ folder, so go up one level to project root\n const rootDir = path.join(__dirname, '..');\n\n // Start Metro bundler\n spinner.start('Starting Metro bundler...');\n const metroProcess = spawn('node', [path.join(rootDir, 'metro-server.cjs')], {\n cwd: projectPath,\n stdio: 'inherit',\n detached: false,\n });\n await new Promise(resolve => setTimeout(resolve, 2000));\n spinner.succeed('Metro bundler started (port 3031)');\n\n // Start file watcher\n spinner.start('Starting file watcher...');\n const watcherProcess = spawn('node', [path.join(rootDir, 'watcher-server.cjs')], {\n cwd: projectPath,\n stdio: 'inherit',\n detached: false,\n });\n await new Promise(resolve => setTimeout(resolve, 1000));\n spinner.succeed('File watcher started (port 3030)');\n\n // Step 4: Connect to relay with auto-reconnect\n const relayUrl = process.env.APPTUNER_RELAY_URL || 'wss://apptuner-relay.falling-bird-3f63.workers.dev';\n const isLocalTesting = relayUrl.includes('localhost') || relayUrl.includes('127.0.0.1');\n const dashboardUrl = isLocalTesting\n ? `http://localhost:1420/?session=${sessionId}`\n : `https://apptuner-dashboard.pages.dev/?session=${sessionId}`;\n\n let isShuttingDown = false;\n let isFirstConnect = true;\n let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n let currentWs: WebSocket | null = null;\n const relayAdapter = new RelayAdapter();\n\n // Step 5: Setup direct connections to local watcher + metro servers\n let autoReloadStarted = false;\n\n async function startAutoReload() {\n if (autoReloadStarted) return;\n autoReloadStarted = true;\n\n let isBundling = false;\n let pendingBundle = false;\n\n async function requestBundle(): Promise<void> {\n if (isBundling) {\n pendingBundle = true;\n return;\n }\n isBundling = true;\n pendingBundle = false;\n\n const startTime = Date.now();\n console.log(chalk.gray('\uD83D\uDCE6 Bundling...'));\n\n try {\n const code = await new Promise<string>((resolve, reject) => {\n const metroWs = new WebSocket('ws://localhost:3031');\n\n metroWs.on('open', () => {\n metroWs.send(JSON.stringify({\n type: 'bundle',\n projectPath,\n entryPoint: 'App.tsx',\n }));\n });\n\n metroWs.on('message', (data) => {\n const msg = JSON.parse(data.toString());\n if (msg.type === 'bundle_ready') {\n metroWs.close();\n if (msg.fromCache) {\n console.log(chalk.gray('\u26A1 No changes \u2014 using cached bundle'));\n }\n resolve(msg.code);\n } else if (msg.type === 'bundle_error') {\n metroWs.close();\n reject(new Error(\n typeof msg.error === 'object' ? msg.error.message : (msg.error || 'Bundle failed')\n ));\n }\n });\n\n metroWs.on('error', (err) => reject(err));\n\n // 60s timeout for large projects\n setTimeout(() => reject(new Error('Bundle timeout (60s)')), 60000);\n });\n\n const sizeKB = Math.round(code.length / 1024);\n const timeMs = Date.now() - startTime;\n relayAdapter.sendBundleUpdate(code);\n console.log(chalk.cyan(`\uD83D\uDCE6 Bundle sent: ${chalk.white(sizeKB + ' KB')} in ${chalk.white(timeMs + 'ms')}`));\n } catch (error: any) {\n console.error(chalk.red('\u274C Bundle error:'), error.message);\n }\n\n isBundling = false;\n\n // If another change came in while bundling, run again\n if (pendingBundle) {\n requestBundle();\n }\n }\n\n // Connect to watcher server\n const watcherWs = new WebSocket('ws://localhost:3030');\n\n watcherWs.on('open', () => {\n // Tell watcher to watch the project directory\n watcherWs.send(JSON.stringify({\n type: 'watch',\n path: projectPath,\n extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],\n }));\n });\n\n watcherWs.on('message', async (data) => {\n const msg = JSON.parse(data.toString());\n\n if (msg.type === 'watcher_ready') {\n console.log(chalk.green('\u2705 Auto-reload active - edit your files to see changes!\\n'));\n // Send initial bundle\n requestBundle();\n } else if (msg.type === 'file_changed') {\n console.log(chalk.yellow(`\uD83D\uDCDD ${msg.relativePath} changed`));\n requestBundle();\n }\n });\n\n watcherWs.on('error', (err) => {\n console.error(chalk.red('Watcher error:'), err.message);\n });\n\n watcherWs.on('close', () => {\n if (!isShuttingDown) {\n console.log(chalk.yellow('Watcher disconnected'));\n }\n });\n }\n\n function connectToRelay() {\n if (isShuttingDown) return;\n\n spinner.start('Connecting to relay server...');\n const ws = new WebSocket(`${relayUrl}/cli/${sessionId}`);\n currentWs = ws;\n\n let pingInterval: ReturnType<typeof setInterval> | null = null;\n\n ws.on('open', async () => {\n relayAdapter.updateWebSocket(ws);\n\n if (isFirstConnect) {\n spinner.succeed('Connected to relay');\n isFirstConnect = false;\n\n console.log(chalk.green.bold('\\n\u2705 AppTuner is running!\\n'));\n console.log(chalk.white(`Dashboard: ${chalk.cyan(dashboardUrl)}`));\n console.log(chalk.gray('\\nOpening browser...\\n'));\n\n const openCommand = process.platform === 'darwin' ? 'open' :\n process.platform === 'win32' ? 'start' : 'xdg-open';\n exec(`${openCommand} \"${dashboardUrl}\"`);\n\n await startAutoReload();\n } else {\n spinner.succeed('Reconnected to relay');\n console.log(chalk.green('\u2705 Relay reconnected\\n'));\n }\n\n // Keepalive pings every 20s (Cloudflare drops idle WS after ~30s)\n pingInterval = setInterval(() => {\n if (ws.readyState === ws.OPEN) {\n ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));\n }\n }, 20000);\n });\n\n ws.on('message', (data) => {\n try {\n const message = JSON.parse(data.toString());\n if (message.type === 'mobile_connected') {\n console.log(chalk.green(`\\n\uD83D\uDCF1 Mobile device connected: ${message.deviceName || 'Unknown'}`));\n }\n if (message.type === 'mobile_disconnected') {\n console.log(chalk.yellow(`\\n\uD83D\uDCF1 Mobile device disconnected`));\n }\n } catch (error) {\n console.error('Error parsing relay message:', error);\n }\n });\n\n ws.on('error', (error) => {\n if (isFirstConnect) {\n spinner.fail('Relay connection failed');\n console.error(chalk.red('\\n\u274C Could not connect to relay server'));\n console.error(chalk.gray(error.message));\n }\n // close event fires after error, triggering reconnect\n });\n\n ws.on('close', (code) => {\n if (pingInterval) {\n clearInterval(pingInterval);\n pingInterval = null;\n }\n\n if (isShuttingDown) return;\n\n if (isFirstConnect) {\n console.error(chalk.red(`\\n\u274C Failed to connect to relay (code ${code})`));\n process.exit(1);\n }\n\n console.log(chalk.yellow(`\\n\u26A0\uFE0F Relay disconnected (code ${code}), reconnecting in 5s...`));\n reconnectTimeout = setTimeout(connectToRelay, 5000);\n });\n }\n\n // Start relay connection\n connectToRelay();\n\n // Save PIDs for cleanup\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n await fs.writeFile(pidFile, JSON.stringify({\n metro: metroProcess.pid,\n watcher: watcherProcess.pid,\n sessionId,\n }, null, 2));\n\n // Graceful shutdown\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\n\u23F9\uFE0F Stopping AppTuner...'));\n\n isShuttingDown = true;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = null;\n }\n\n if (currentWs) {\n currentWs.close();\n }\n\n if (metroProcess.pid) {\n process.kill(metroProcess.pid, 'SIGTERM');\n }\n\n if (watcherProcess.pid) {\n process.kill(watcherProcess.pid, 'SIGTERM');\n }\n\n await fs.unlink(pidFile).catch(() => {});\n\n console.log(chalk.gray('All services stopped.\\n'));\n process.exit(0);\n });\n}\n", "import fs from 'fs/promises';\nimport path from 'path';\nimport chalk from 'chalk';\n\nexport async function stopCommand() {\n console.log(chalk.yellow('\\n\u23F9\uFE0F Stopping AppTuner...\\n'));\n\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n\n try {\n const pidsData = await fs.readFile(pidFile, 'utf-8');\n const pids = JSON.parse(pidsData);\n\n if (pids.metro) {\n try {\n process.kill(-pids.metro, 'SIGTERM');\n console.log(chalk.gray('\u2713 Metro server stopped'));\n } catch (error) {\n console.log(chalk.gray('\u2713 Metro server already stopped'));\n }\n }\n\n if (pids.watcher) {\n try {\n process.kill(-pids.watcher, 'SIGTERM');\n console.log(chalk.gray('\u2713 File watcher stopped'));\n } catch (error) {\n console.log(chalk.gray('\u2713 File watcher already stopped'));\n }\n }\n\n await fs.unlink(pidFile);\n\n console.log(chalk.green('\\n\u2705 All services stopped\\n'));\n } catch (error) {\n console.log(chalk.gray('No running AppTuner services found.\\n'));\n }\n}\n", "import fs from 'fs/promises';\nimport path from 'path';\nimport chalk from 'chalk';\n\nexport async function statusCommand() {\n console.log(chalk.blue.bold('\\n\uD83D\uDCCA AppTuner Status\\n'));\n\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n\n try {\n const pidsData = await fs.readFile(pidFile, 'utf-8');\n const pids = JSON.parse(pidsData);\n\n console.log(chalk.white('Services:'));\n\n // Check if processes are actually running\n let metroRunning = false;\n let watcherRunning = false;\n\n if (pids.metro) {\n try {\n process.kill(pids.metro, 0); // Check if process exists\n metroRunning = true;\n } catch {\n metroRunning = false;\n }\n }\n\n if (pids.watcher) {\n try {\n process.kill(pids.watcher, 0);\n watcherRunning = true;\n } catch {\n watcherRunning = false;\n }\n }\n\n console.log(\n ` ${metroRunning ? chalk.green('\u25CF') : chalk.red('\u25CF')} Metro bundler ${\n metroRunning ? chalk.gray('(port 3031)') : chalk.gray('(stopped)')\n }`\n );\n\n console.log(\n ` ${watcherRunning ? chalk.green('\u25CF') : chalk.red('\u25CF')} File watcher ${\n watcherRunning ? chalk.gray('(port 3030)') : chalk.gray('(stopped)')\n }`\n );\n\n if (pids.sessionId) {\n console.log(chalk.white(`\\nSession: ${chalk.cyan(pids.sessionId)}`));\n }\n\n if (metroRunning && watcherRunning) {\n console.log(chalk.green('\\n\u2705 AppTuner is running\\n'));\n } else {\n console.log(chalk.yellow('\\n\u26A0\uFE0F Some services are not running\\n'));\n }\n } catch (error) {\n console.log(chalk.gray(' No running services\\n'));\n console.log(chalk.gray('Run `apptuner start` to begin.\\n'));\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { startCommand } from './commands/start.js';\nimport { stopCommand } from './commands/stop.js';\nimport { statusCommand } from './commands/status.js';\n\nconst program = new Command();\n\nprogram\n .name('apptuner')\n .description('Hot reload React Native apps instantly')\n .version('0.1.0');\n\nprogram\n .command('start')\n .description('Start AppTuner development server')\n .option('-p, --project <path>', 'Path to your React Native project', process.cwd())\n .option('--no-qr', 'Disable QR code display')\n .action(startCommand);\n\nprogram\n .command('stop')\n .description('Stop all AppTuner services')\n .action(stopCommand);\n\nprogram\n .command('status')\n .description('Show AppTuner connection status')\n .action(statusCommand);\n\nprogram.parse();\n", "import { spawn } from 'child_process';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs/promises';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { WebSocket } from 'ws';\nimport { exec } from 'child_process';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface StartOptions {\n project: string;\n qr: boolean;\n}\n\n// Generate random 6-character session ID\nfunction generateSessionId(): string {\n const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';\n let result = '';\n for (let i = 0; i < 6; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n// Adapter that wraps the relay WebSocket for sending bundles - supports hot-swapping on reconnect\nclass RelayAdapter {\n private ws: WebSocket | null = null;\n\n updateWebSocket(ws: WebSocket): void {\n this.ws = ws;\n }\n\n sendBundleUpdate(bundleCode: string): void {\n if (!this.ws || this.ws.readyState !== this.ws.OPEN) {\n console.warn(chalk.yellow('\u26A0\uFE0F Cannot send bundle - relay not connected'));\n return;\n }\n this.ws.send(JSON.stringify({\n type: 'bundle_update',\n payload: { code: bundleCode },\n timestamp: Date.now(),\n }));\n }\n}\n\nexport async function startCommand(options: StartOptions) {\n const spinner = ora();\n\n console.log(chalk.blue.bold('\\n\uD83D\uDE80 AppTuner\\n'));\n\n // Step 1: Validate project\n spinner.start('Validating React Native project...');\n const projectPath = path.resolve(options.project);\n const packageJsonPath = path.join(projectPath, 'package.json');\n\n try {\n await fs.access(packageJsonPath);\n } catch {\n spinner.fail('No package.json found');\n console.error(chalk.red(`\\n\u274C Could not find package.json at ${projectPath}`));\n console.log(chalk.gray('\\nMake sure you\\'re in a React Native project directory.\\n'));\n process.exit(1);\n }\n\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));\n\n if (!packageJson.dependencies?.['react-native']) {\n spinner.fail('Not a React Native project');\n console.error(chalk.red('\\n\u274C This is not a React Native project'));\n console.log(chalk.gray('No react-native dependency found in package.json\\n'));\n process.exit(1);\n }\n\n spinner.succeed(`Project validated: ${chalk.cyan(packageJson.name || 'Unnamed Project')}`);\n\n // Step 2: Generate session ID (stays the same across reconnects)\n const sessionId = generateSessionId();\n spinner.succeed(`Session ID: ${chalk.cyan(sessionId)}`);\n\n // Step 3: Start local services\n console.log(chalk.white('\\nStarting local services...\\n'));\n\n // __dirname in compiled dist/cli.js is the dist/ folder, so go up one level to project root\n const rootDir = path.join(__dirname, '..');\n\n // Start Metro bundler\n spinner.start('Starting Metro bundler...');\n const metroProcess = spawn('node', [path.join(rootDir, 'metro-server.cjs')], {\n cwd: projectPath,\n stdio: 'inherit',\n detached: false,\n });\n await new Promise(resolve => setTimeout(resolve, 2000));\n spinner.succeed('Metro bundler started (port 3031)');\n\n // Start file watcher\n spinner.start('Starting file watcher...');\n const watcherProcess = spawn('node', [path.join(rootDir, 'watcher-server.cjs')], {\n cwd: projectPath,\n stdio: 'inherit',\n detached: false,\n });\n await new Promise(resolve => setTimeout(resolve, 1000));\n spinner.succeed('File watcher started (port 3030)');\n\n // Step 4: Connect to relay with auto-reconnect\n const relayUrl = process.env.APPTUNER_RELAY_URL || 'wss://relay.apptuner.io';\n const isLocalTesting = relayUrl.includes('localhost') || relayUrl.includes('127.0.0.1');\n const dashboardUrl = isLocalTesting\n ? `http://localhost:1420/?session=${sessionId}`\n : `https://apptuner.io/?session=${sessionId}`;\n\n let isShuttingDown = false;\n let isFirstConnect = true;\n let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n let currentWs: WebSocket | null = null;\n const relayAdapter = new RelayAdapter();\n\n // Step 5: Setup direct connections to local watcher + metro servers\n let autoReloadStarted = false;\n\n async function startAutoReload() {\n if (autoReloadStarted) return;\n autoReloadStarted = true;\n\n let isBundling = false;\n let pendingBundle = false;\n\n async function requestBundle(): Promise<void> {\n if (isBundling) {\n pendingBundle = true;\n return;\n }\n isBundling = true;\n pendingBundle = false;\n\n const startTime = Date.now();\n console.log(chalk.gray('\uD83D\uDCE6 Bundling...'));\n\n try {\n const code = await new Promise<string>((resolve, reject) => {\n const metroWs = new WebSocket('ws://localhost:3031');\n\n metroWs.on('open', () => {\n metroWs.send(JSON.stringify({\n type: 'bundle',\n projectPath,\n entryPoint: 'App.tsx',\n }));\n });\n\n metroWs.on('message', (data) => {\n const msg = JSON.parse(data.toString());\n if (msg.type === 'bundle_ready') {\n metroWs.close();\n if (msg.fromCache) {\n console.log(chalk.gray('\u26A1 No changes \u2014 using cached bundle'));\n }\n resolve(msg.code);\n } else if (msg.type === 'bundle_error') {\n metroWs.close();\n reject(new Error(\n typeof msg.error === 'object' ? msg.error.message : (msg.error || 'Bundle failed')\n ));\n }\n });\n\n metroWs.on('error', (err) => reject(err));\n\n // 60s timeout for large projects\n setTimeout(() => reject(new Error('Bundle timeout (60s)')), 60000);\n });\n\n const sizeKB = Math.round(code.length / 1024);\n const timeMs = Date.now() - startTime;\n relayAdapter.sendBundleUpdate(code);\n console.log(chalk.cyan(`\uD83D\uDCE6 Bundle sent: ${chalk.white(sizeKB + ' KB')} in ${chalk.white(timeMs + 'ms')}`));\n } catch (error: any) {\n console.error(chalk.red('\u274C Bundle error:'), error.message);\n }\n\n isBundling = false;\n\n // If another change came in while bundling, run again\n if (pendingBundle) {\n requestBundle();\n }\n }\n\n // Connect to watcher server\n const watcherWs = new WebSocket('ws://localhost:3030');\n\n watcherWs.on('open', () => {\n // Tell watcher to watch the project directory\n watcherWs.send(JSON.stringify({\n type: 'watch',\n path: projectPath,\n extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],\n }));\n });\n\n watcherWs.on('message', async (data) => {\n const msg = JSON.parse(data.toString());\n\n if (msg.type === 'watcher_ready') {\n console.log(chalk.green('\u2705 Auto-reload active - edit your files to see changes!\\n'));\n // Send initial bundle\n requestBundle();\n } else if (msg.type === 'file_changed') {\n console.log(chalk.yellow(`\uD83D\uDCDD ${msg.relativePath} changed`));\n requestBundle();\n }\n });\n\n watcherWs.on('error', (err) => {\n console.error(chalk.red('Watcher error:'), err.message);\n });\n\n watcherWs.on('close', () => {\n if (!isShuttingDown) {\n console.log(chalk.yellow('Watcher disconnected'));\n }\n });\n }\n\n function connectToRelay() {\n if (isShuttingDown) return;\n\n spinner.start('Connecting to relay server...');\n const ws = new WebSocket(`${relayUrl}/cli/${sessionId}`);\n currentWs = ws;\n\n let pingInterval: ReturnType<typeof setInterval> | null = null;\n\n ws.on('open', async () => {\n relayAdapter.updateWebSocket(ws);\n\n if (isFirstConnect) {\n spinner.succeed('Connected to relay');\n isFirstConnect = false;\n\n console.log(chalk.green.bold('\\n\u2705 AppTuner is running!\\n'));\n console.log(chalk.white(`Dashboard: ${chalk.cyan(dashboardUrl)}`));\n console.log(chalk.gray('\\nOpening browser...\\n'));\n\n const openCommand = process.platform === 'darwin' ? 'open' :\n process.platform === 'win32' ? 'start' : 'xdg-open';\n exec(`${openCommand} \"${dashboardUrl}\"`);\n\n await startAutoReload();\n } else {\n spinner.succeed('Reconnected to relay');\n console.log(chalk.green('\u2705 Relay reconnected\\n'));\n }\n\n // Keepalive pings every 20s (Cloudflare drops idle WS after ~30s)\n pingInterval = setInterval(() => {\n if (ws.readyState === ws.OPEN) {\n ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));\n }\n }, 20000);\n });\n\n ws.on('message', (data) => {\n try {\n const message = JSON.parse(data.toString());\n if (message.type === 'mobile_connected') {\n console.log(chalk.green(`\\n\uD83D\uDCF1 Mobile device connected: ${message.deviceName || 'Unknown'}`));\n }\n if (message.type === 'mobile_disconnected') {\n console.log(chalk.yellow(`\\n\uD83D\uDCF1 Mobile device disconnected`));\n }\n } catch (error) {\n console.error('Error parsing relay message:', error);\n }\n });\n\n ws.on('error', (error) => {\n if (isFirstConnect) {\n spinner.fail('Relay connection failed');\n console.error(chalk.red('\\n\u274C Could not connect to relay server'));\n console.error(chalk.gray(error.message));\n }\n // close event fires after error, triggering reconnect\n });\n\n ws.on('close', (code) => {\n if (pingInterval) {\n clearInterval(pingInterval);\n pingInterval = null;\n }\n\n if (isShuttingDown) return;\n\n if (isFirstConnect) {\n console.error(chalk.red(`\\n\u274C Failed to connect to relay (code ${code})`));\n process.exit(1);\n }\n\n console.log(chalk.yellow(`\\n\u26A0\uFE0F Relay disconnected (code ${code}), reconnecting in 5s...`));\n reconnectTimeout = setTimeout(connectToRelay, 5000);\n });\n }\n\n // Start relay connection\n connectToRelay();\n\n // Save PIDs for cleanup\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n await fs.writeFile(pidFile, JSON.stringify({\n metro: metroProcess.pid,\n watcher: watcherProcess.pid,\n sessionId,\n }, null, 2));\n\n // Graceful shutdown\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\n\u23F9\uFE0F Stopping AppTuner...'));\n\n isShuttingDown = true;\n\n if (reconnectTimeout) {\n clearTimeout(reconnectTimeout);\n reconnectTimeout = null;\n }\n\n if (currentWs) {\n currentWs.close();\n }\n\n if (metroProcess.pid) {\n process.kill(metroProcess.pid, 'SIGTERM');\n }\n\n if (watcherProcess.pid) {\n process.kill(watcherProcess.pid, 'SIGTERM');\n }\n\n await fs.unlink(pidFile).catch(() => {});\n\n console.log(chalk.gray('All services stopped.\\n'));\n process.exit(0);\n });\n}\n", "import fs from 'fs/promises';\nimport path from 'path';\nimport chalk from 'chalk';\n\nexport async function stopCommand() {\n console.log(chalk.yellow('\\n\u23F9\uFE0F Stopping AppTuner...\\n'));\n\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n\n try {\n const pidsData = await fs.readFile(pidFile, 'utf-8');\n const pids = JSON.parse(pidsData);\n\n if (pids.metro) {\n try {\n process.kill(-pids.metro, 'SIGTERM');\n console.log(chalk.gray('\u2713 Metro server stopped'));\n } catch (error) {\n console.log(chalk.gray('\u2713 Metro server already stopped'));\n }\n }\n\n if (pids.watcher) {\n try {\n process.kill(-pids.watcher, 'SIGTERM');\n console.log(chalk.gray('\u2713 File watcher stopped'));\n } catch (error) {\n console.log(chalk.gray('\u2713 File watcher already stopped'));\n }\n }\n\n await fs.unlink(pidFile);\n\n console.log(chalk.green('\\n\u2705 All services stopped\\n'));\n } catch (error) {\n console.log(chalk.gray('No running AppTuner services found.\\n'));\n }\n}\n", "import fs from 'fs/promises';\nimport path from 'path';\nimport chalk from 'chalk';\n\nexport async function statusCommand() {\n console.log(chalk.blue.bold('\\n\uD83D\uDCCA AppTuner Status\\n'));\n\n const pidFile = path.join(process.cwd(), '.apptuner-pids.json');\n\n try {\n const pidsData = await fs.readFile(pidFile, 'utf-8');\n const pids = JSON.parse(pidsData);\n\n console.log(chalk.white('Services:'));\n\n // Check if processes are actually running\n let metroRunning = false;\n let watcherRunning = false;\n\n if (pids.metro) {\n try {\n process.kill(pids.metro, 0); // Check if process exists\n metroRunning = true;\n } catch {\n metroRunning = false;\n }\n }\n\n if (pids.watcher) {\n try {\n process.kill(pids.watcher, 0);\n watcherRunning = true;\n } catch {\n watcherRunning = false;\n }\n }\n\n console.log(\n ` ${metroRunning ? chalk.green('\u25CF') : chalk.red('\u25CF')} Metro bundler ${\n metroRunning ? chalk.gray('(port 3031)') : chalk.gray('(stopped)')\n }`\n );\n\n console.log(\n ` ${watcherRunning ? chalk.green('\u25CF') : chalk.red('\u25CF')} File watcher ${\n watcherRunning ? chalk.gray('(port 3030)') : chalk.gray('(stopped)')\n }`\n );\n\n if (pids.sessionId) {\n console.log(chalk.white(`\\nSession: ${chalk.cyan(pids.sessionId)}`));\n }\n\n if (metroRunning && watcherRunning) {\n console.log(chalk.green('\\n\u2705 AppTuner is running\\n'));\n } else {\n console.log(chalk.yellow('\\n\u26A0\uFE0F Some services are not running\\n'));\n }\n } catch (error) {\n console.log(chalk.gray(' No running services\\n'));\n console.log(chalk.gray('Run `apptuner start` to begin.\\n'));\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fs", "path", "chalk", "fs", "path", "chalk"]
7
7
  }
package/metro-server.cjs CHANGED
@@ -124,9 +124,9 @@ wss.on('connection', (ws) => {
124
124
 
125
125
  async function bundleProject(projectPath, entryPoint) {
126
126
  const absoluteProjectPath = path.resolve(projectPath);
127
- // metro-bundle.js lives alongside metro-server.cjs in the AppTuner installation.
127
+ // metro-bundle.cjs lives alongside metro-server.cjs in the AppTuner installation.
128
128
  // This works both in local dev (project root) and when installed globally via npm.
129
- const bundleScript = path.join(__dirname, 'metro-bundle.js');
129
+ const bundleScript = path.join(__dirname, 'metro-bundle.cjs');
130
130
 
131
131
  console.log(`📁 Project path: ${absoluteProjectPath}`);
132
132
  console.log(`📄 Entry point: ${entryPoint}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apptuner",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Hot reload React Native apps instantly - no Expo needed",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "dist/cli.js",
11
11
  "dist/cli.js.map",
12
12
  "metro-server.cjs",
13
- "metro-bundle.js",
13
+ "metro-bundle.cjs",
14
14
  "watcher-server.cjs"
15
15
  ],
16
16
  "keywords": [
@@ -44,6 +44,7 @@
44
44
  "watcher": "node watcher-server.cjs",
45
45
  "metro": "node metro-server.cjs",
46
46
  "relay": "cd relay && npm run dev",
47
+ "relay:vps": "node relay-server.js",
47
48
  "mobile": "cd mobile && npm start",
48
49
  "start:all": "concurrently -n desktop,relay,watcher,metro,mobile -c blue,green,yellow,cyan,magenta \"npm run dev\" \"npm run relay\" \"npm run watcher\" \"npm run metro\" \"npm run mobile\"",
49
50
  "apptuner": "node dist/cli.js",
File without changes