apptuner 0.1.1 → 0.1.3
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 +93 -14
- package/dist/cli.js.map +2 -2
- package/metro-server.cjs +18 -2
- package/package.json +4 -4
- package/watcher-server.cjs +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,13 +8,24 @@ import { spawn } from "child_process";
|
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import fs from "fs/promises";
|
|
11
|
+
import net from "net";
|
|
11
12
|
import chalk from "chalk";
|
|
12
13
|
import ora from "ora";
|
|
13
14
|
import { WebSocket } from "ws";
|
|
14
15
|
import { exec } from "child_process";
|
|
15
16
|
var __filename = fileURLToPath(import.meta.url);
|
|
16
17
|
var __dirname = path.dirname(__filename);
|
|
17
|
-
function
|
|
18
|
+
function findFreePort(start) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const server = net.createServer();
|
|
21
|
+
server.listen(start, "127.0.0.1", () => {
|
|
22
|
+
const port = server.address().port;
|
|
23
|
+
server.close(() => resolve(port));
|
|
24
|
+
});
|
|
25
|
+
server.on("error", () => resolve(findFreePort(start + 1)));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function generateId() {
|
|
18
29
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
19
30
|
let result = "";
|
|
20
31
|
for (let i = 0; i < 6; i++) {
|
|
@@ -22,6 +33,18 @@ function generateSessionId() {
|
|
|
22
33
|
}
|
|
23
34
|
return result;
|
|
24
35
|
}
|
|
36
|
+
async function getOrCreateProjectId(projectPath) {
|
|
37
|
+
const configPath = path.join(projectPath, ".apptuner.json");
|
|
38
|
+
try {
|
|
39
|
+
const raw = await fs.readFile(configPath, "utf-8");
|
|
40
|
+
const config = JSON.parse(raw);
|
|
41
|
+
if (config.projectId) return config.projectId;
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
const projectId = generateId();
|
|
45
|
+
await fs.writeFile(configPath, JSON.stringify({ projectId }, null, 2));
|
|
46
|
+
return projectId;
|
|
47
|
+
}
|
|
25
48
|
var RelayAdapter = class {
|
|
26
49
|
ws = null;
|
|
27
50
|
updateWebSocket(ws) {
|
|
@@ -62,38 +85,69 @@ async function startCommand(options) {
|
|
|
62
85
|
process.exit(1);
|
|
63
86
|
}
|
|
64
87
|
spinner.succeed(`Project validated: ${chalk.cyan(packageJson.name || "Unnamed Project")}`);
|
|
65
|
-
const sessionId =
|
|
66
|
-
spinner.succeed(`
|
|
88
|
+
const sessionId = await getOrCreateProjectId(projectPath);
|
|
89
|
+
spinner.succeed(`Project ID: ${chalk.cyan(sessionId)}`);
|
|
67
90
|
console.log(chalk.white("\nStarting local services...\n"));
|
|
68
91
|
const rootDir = path.join(__dirname, "..");
|
|
92
|
+
const metroPort = await findFreePort(3031);
|
|
93
|
+
const watcherPort = await findFreePort(3030);
|
|
69
94
|
spinner.start("Starting Metro bundler...");
|
|
70
95
|
const metroProcess = spawn("node", [path.join(rootDir, "metro-server.cjs")], {
|
|
71
96
|
cwd: projectPath,
|
|
72
97
|
stdio: "inherit",
|
|
73
|
-
detached: false
|
|
98
|
+
detached: false,
|
|
99
|
+
env: { ...process.env, METRO_PORT: String(metroPort) }
|
|
74
100
|
});
|
|
75
101
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
76
|
-
spinner.succeed(
|
|
102
|
+
spinner.succeed(`Metro bundler started (port ${metroPort})`);
|
|
77
103
|
spinner.start("Starting file watcher...");
|
|
78
104
|
const watcherProcess = spawn("node", [path.join(rootDir, "watcher-server.cjs")], {
|
|
79
105
|
cwd: projectPath,
|
|
80
106
|
stdio: "inherit",
|
|
81
|
-
detached: false
|
|
107
|
+
detached: false,
|
|
108
|
+
env: { ...process.env, WATCHER_PORT: String(watcherPort) }
|
|
82
109
|
});
|
|
83
110
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
84
|
-
spinner.succeed(
|
|
85
|
-
const relayUrl = process.env.APPTUNER_RELAY_URL || "wss://
|
|
111
|
+
spinner.succeed(`File watcher started (port ${watcherPort})`);
|
|
112
|
+
const relayUrl = process.env.APPTUNER_RELAY_URL || "wss://relay.apptuner.io";
|
|
86
113
|
const isLocalTesting = relayUrl.includes("localhost") || relayUrl.includes("127.0.0.1");
|
|
87
|
-
const
|
|
114
|
+
const projectName = encodeURIComponent(packageJson.name || "Unnamed Project");
|
|
115
|
+
const baseDashboardUrl = process.env.APPTUNER_DASHBOARD_URL || (isLocalTesting ? "http://localhost:1420" : "https://apptuner.io");
|
|
116
|
+
const dashboardUrl = `${baseDashboardUrl}/?session=${sessionId}&name=${projectName}`;
|
|
88
117
|
let isShuttingDown = false;
|
|
89
118
|
let isFirstConnect = true;
|
|
90
119
|
let reconnectTimeout = null;
|
|
91
120
|
let currentWs = null;
|
|
92
121
|
const relayAdapter = new RelayAdapter();
|
|
93
122
|
let autoReloadStarted = false;
|
|
123
|
+
let bundleRequester = null;
|
|
94
124
|
async function startAutoReload() {
|
|
95
125
|
if (autoReloadStarted) return;
|
|
96
126
|
autoReloadStarted = true;
|
|
127
|
+
console.log(chalk.gray("\u{1F5D1}\uFE0F Clearing Metro cache for fresh session..."));
|
|
128
|
+
try {
|
|
129
|
+
await new Promise((resolve, reject) => {
|
|
130
|
+
const clearWs = new WebSocket(`ws://localhost:${metroPort}`);
|
|
131
|
+
clearWs.on("open", () => {
|
|
132
|
+
clearWs.send(JSON.stringify({
|
|
133
|
+
type: "clear_cache",
|
|
134
|
+
projectPath
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
137
|
+
clearWs.on("message", (data) => {
|
|
138
|
+
const msg = JSON.parse(data.toString());
|
|
139
|
+
if (msg.type === "cache_cleared") {
|
|
140
|
+
console.log(chalk.gray(`\u2713 Cleared ${msg.count} cached bundles`));
|
|
141
|
+
clearWs.close();
|
|
142
|
+
resolve();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
clearWs.on("error", () => reject());
|
|
146
|
+
setTimeout(() => reject(new Error("Cache clear timeout")), 5e3);
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.log(chalk.yellow("\u26A0\uFE0F Could not clear Metro cache, continuing..."));
|
|
150
|
+
}
|
|
97
151
|
let isBundling = false;
|
|
98
152
|
let pendingBundle = false;
|
|
99
153
|
async function requestBundle() {
|
|
@@ -107,7 +161,7 @@ async function startCommand(options) {
|
|
|
107
161
|
console.log(chalk.gray("\u{1F4E6} Bundling..."));
|
|
108
162
|
try {
|
|
109
163
|
const code = await new Promise((resolve, reject) => {
|
|
110
|
-
const metroWs = new WebSocket(
|
|
164
|
+
const metroWs = new WebSocket(`ws://localhost:${metroPort}`);
|
|
111
165
|
metroWs.on("open", () => {
|
|
112
166
|
metroWs.send(JSON.stringify({
|
|
113
167
|
type: "bundle",
|
|
@@ -145,13 +199,20 @@ async function startCommand(options) {
|
|
|
145
199
|
requestBundle();
|
|
146
200
|
}
|
|
147
201
|
}
|
|
148
|
-
|
|
202
|
+
bundleRequester = requestBundle;
|
|
203
|
+
const watcherWs = new WebSocket(`ws://localhost:${watcherPort}`);
|
|
204
|
+
let watcherPingInterval = null;
|
|
149
205
|
watcherWs.on("open", () => {
|
|
150
206
|
watcherWs.send(JSON.stringify({
|
|
151
207
|
type: "watch",
|
|
152
208
|
path: projectPath,
|
|
153
209
|
extensions: [".js", ".jsx", ".ts", ".tsx", ".json"]
|
|
154
210
|
}));
|
|
211
|
+
watcherPingInterval = setInterval(() => {
|
|
212
|
+
if (watcherWs.readyState === watcherWs.OPEN) {
|
|
213
|
+
watcherWs.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
|
|
214
|
+
}
|
|
215
|
+
}, 2e4);
|
|
155
216
|
});
|
|
156
217
|
watcherWs.on("message", async (data) => {
|
|
157
218
|
const msg = JSON.parse(data.toString());
|
|
@@ -167,6 +228,10 @@ async function startCommand(options) {
|
|
|
167
228
|
console.error(chalk.red("Watcher error:"), err.message);
|
|
168
229
|
});
|
|
169
230
|
watcherWs.on("close", () => {
|
|
231
|
+
if (watcherPingInterval) {
|
|
232
|
+
clearInterval(watcherPingInterval);
|
|
233
|
+
watcherPingInterval = null;
|
|
234
|
+
}
|
|
170
235
|
if (!isShuttingDown) {
|
|
171
236
|
console.log(chalk.yellow("Watcher disconnected"));
|
|
172
237
|
}
|
|
@@ -205,6 +270,10 @@ async function startCommand(options) {
|
|
|
205
270
|
if (message.type === "mobile_connected") {
|
|
206
271
|
console.log(chalk.green(`
|
|
207
272
|
\u{1F4F1} Mobile device connected: ${message.deviceName || "Unknown"}`));
|
|
273
|
+
if (bundleRequester) {
|
|
274
|
+
console.log(chalk.gray("\u{1F4E6} Sending bundle to new device..."));
|
|
275
|
+
bundleRequester();
|
|
276
|
+
}
|
|
208
277
|
}
|
|
209
278
|
if (message.type === "mobile_disconnected") {
|
|
210
279
|
console.log(chalk.yellow(`
|
|
@@ -255,11 +324,18 @@ async function startCommand(options) {
|
|
|
255
324
|
currentWs.close();
|
|
256
325
|
}
|
|
257
326
|
if (metroProcess.pid) {
|
|
258
|
-
|
|
327
|
+
try {
|
|
328
|
+
process.kill(metroProcess.pid, "SIGKILL");
|
|
329
|
+
} catch (e) {
|
|
330
|
+
}
|
|
259
331
|
}
|
|
260
332
|
if (watcherProcess.pid) {
|
|
261
|
-
|
|
333
|
+
try {
|
|
334
|
+
process.kill(watcherProcess.pid, "SIGKILL");
|
|
335
|
+
} catch (e) {
|
|
336
|
+
}
|
|
262
337
|
}
|
|
338
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
263
339
|
await fs.unlink(pidFile).catch(() => {
|
|
264
340
|
});
|
|
265
341
|
console.log(chalk.gray("All services stopped.\n"));
|
|
@@ -353,7 +429,10 @@ Session: ${chalk3.cyan(pids.sessionId)}`));
|
|
|
353
429
|
// src-cli/cli.ts
|
|
354
430
|
var program = new Command();
|
|
355
431
|
program.name("apptuner").description("Hot reload React Native apps instantly").version("0.1.0");
|
|
356
|
-
program.command("start").description("Start AppTuner development server").option("-p, --project <path>", "Path to your React Native project", process.cwd()).option("--no-qr", "Disable QR code display").action(
|
|
432
|
+
program.command("start").description("Start AppTuner development server").argument("[path]", "Path to your React Native project (or use -p)").option("-p, --project <path>", "Path to your React Native project", process.cwd()).option("--no-qr", "Disable QR code display").action((argPath, options) => {
|
|
433
|
+
if (argPath) options.project = argPath;
|
|
434
|
+
startCommand(options);
|
|
435
|
+
});
|
|
357
436
|
program.command("stop").description("Stop all AppTuner services").action(stopCommand);
|
|
358
437
|
program.command("status").description("Show AppTuner connection status").action(statusCommand);
|
|
359
438
|
program.parse();
|
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 .argument('[path]', 'Path to your React Native project (or use -p)')\n .option('-p, --project <path>', 'Path to your React Native project', process.cwd())\n .option('--no-qr', 'Disable QR code display')\n .action((argPath, options) => {\n // Positional arg takes priority over -p flag\n if (argPath) options.project = argPath;\n startCommand(options);\n });\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 net from 'net';\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// Find a free port starting from the given port number\nfunction findFreePort(start: number): Promise<number> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.listen(start, '127.0.0.1', () => {\n const port = (server.address() as net.AddressInfo).port;\n server.close(() => resolve(port));\n });\n server.on('error', () => resolve(findFreePort(start + 1)));\n });\n}\n\n// Generate random 6-character ID\nfunction generateId(): 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// Get or create a stable project ID stored in .apptuner.json\nasync function getOrCreateProjectId(projectPath: string): Promise<string> {\n const configPath = path.join(projectPath, '.apptuner.json');\n try {\n const raw = await fs.readFile(configPath, 'utf-8');\n const config = JSON.parse(raw);\n if (config.projectId) return config.projectId;\n } catch {\n // File doesn't exist or is invalid \u2014 create it\n }\n const projectId = generateId();\n await fs.writeFile(configPath, JSON.stringify({ projectId }, null, 2));\n return projectId;\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: Get or create stable project ID (persisted in .apptuner.json)\n const sessionId = await getOrCreateProjectId(projectPath);\n spinner.succeed(`Project 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 // Find free ports for Metro and Watcher\n const metroPort = await findFreePort(3031);\n const watcherPort = await findFreePort(3030);\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 env: { ...process.env, METRO_PORT: String(metroPort) },\n });\n await new Promise(resolve => setTimeout(resolve, 2000));\n spinner.succeed(`Metro bundler started (port ${metroPort})`);\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 env: { ...process.env, WATCHER_PORT: String(watcherPort) },\n });\n await new Promise(resolve => setTimeout(resolve, 1000));\n spinner.succeed(`File watcher started (port ${watcherPort})`);\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 projectName = encodeURIComponent(packageJson.name || 'Unnamed Project');\n // APPTUNER_DASHBOARD_URL lets you point at a local build (e.g. http://localhost:4173)\n // so fixes to BrowserApp take effect before the next apptuner.io deployment.\n const baseDashboardUrl = process.env.APPTUNER_DASHBOARD_URL ||\n (isLocalTesting ? 'http://localhost:1420' : 'https://apptuner.io');\n const dashboardUrl = `${baseDashboardUrl}/?session=${sessionId}&name=${projectName}`;\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 let bundleRequester: (() => void) | null = null; // Set by startAutoReload, used by relay handler\n\n async function startAutoReload() {\n if (autoReloadStarted) return;\n autoReloadStarted = true;\n\n // Clear Metro's cache for this project when starting a new session\n console.log(chalk.gray('\uD83D\uDDD1\uFE0F Clearing Metro cache for fresh session...'));\n try {\n await new Promise<void>((resolve, reject) => {\n const clearWs = new WebSocket(`ws://localhost:${metroPort}`);\n clearWs.on('open', () => {\n clearWs.send(JSON.stringify({\n type: 'clear_cache',\n projectPath,\n }));\n });\n clearWs.on('message', (data) => {\n const msg = JSON.parse(data.toString());\n if (msg.type === 'cache_cleared') {\n console.log(chalk.gray(`\u2713 Cleared ${msg.count} cached bundles`));\n clearWs.close();\n resolve();\n }\n });\n clearWs.on('error', () => reject());\n setTimeout(() => reject(new Error('Cache clear timeout')), 5000);\n });\n } catch (error) {\n console.log(chalk.yellow('\u26A0\uFE0F Could not clear Metro cache, continuing...'));\n }\n\n let isBundling = false;\n let pendingBundle = false;\n\n async function requestBundle(): Promise<void> {\n // also exposed via bundleRequester for external callers (e.g. mobile_connected)\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:${metroPort}`);\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 // Expose requestBundle to relay handler so mobile_connected can trigger a fresh bundle\n bundleRequester = requestBundle;\n\n // Connect to watcher server\n const watcherWs = new WebSocket(`ws://localhost:${watcherPort}`);\n let watcherPingInterval: ReturnType<typeof setInterval> | null = null;\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 // Keepalive pings every 20s to prevent idle timeout\n watcherPingInterval = setInterval(() => {\n if (watcherWs.readyState === watcherWs.OPEN) {\n watcherWs.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));\n }\n }, 20000);\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 (watcherPingInterval) {\n clearInterval(watcherPingInterval);\n watcherPingInterval = null;\n }\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 to maintain idle WebSocket connection\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 // Send bundle to the newly connected device\n if (bundleRequester) {\n console.log(chalk.gray('\uD83D\uDCE6 Sending bundle to new device...'));\n bundleRequester();\n }\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 // Kill Metro and Watcher processes forcefully\n if (metroProcess.pid) {\n try {\n process.kill(metroProcess.pid, 'SIGKILL');\n } catch (e) {\n // Process might already be dead\n }\n }\n\n if (watcherProcess.pid) {\n try {\n process.kill(watcherProcess.pid, 'SIGKILL');\n } catch (e) {\n // Process might already be dead\n }\n }\n\n // Give processes time to die before exiting\n await new Promise(resolve => setTimeout(resolve, 500));\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
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const WebSocket = require('ws');
|
|
6
6
|
|
|
7
|
-
const PORT = 3031;
|
|
7
|
+
const PORT = parseInt(process.env.METRO_PORT || '3031', 10);
|
|
8
8
|
const wss = new WebSocket.Server({ port: PORT });
|
|
9
9
|
|
|
10
10
|
console.log(`📦 Metro bundler server running on ws://localhost:${PORT}`);
|
|
@@ -61,6 +61,21 @@ wss.on('connection', (ws) => {
|
|
|
61
61
|
try {
|
|
62
62
|
const data = JSON.parse(message);
|
|
63
63
|
|
|
64
|
+
// Handle cache clearing for new sessions/projects
|
|
65
|
+
if (data.type === 'clear_cache') {
|
|
66
|
+
const { projectPath } = data;
|
|
67
|
+
const cleared = [];
|
|
68
|
+
for (const [key] of bundleCache.entries()) {
|
|
69
|
+
if (key.startsWith(projectPath + '::')) {
|
|
70
|
+
bundleCache.delete(key);
|
|
71
|
+
cleared.push(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log(`🗑️ Cleared ${cleared.length} cached bundles for ${projectPath}`);
|
|
75
|
+
ws.send(JSON.stringify({ type: 'cache_cleared', count: cleared.length }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
64
79
|
if (data.type === 'bundle') {
|
|
65
80
|
const { projectPath, entryPoint = 'App.tsx' } = data;
|
|
66
81
|
console.log(`📦 Bundle request for: ${projectPath}/${entryPoint}`);
|
|
@@ -215,6 +230,7 @@ function createErrorBundle(error) {
|
|
|
215
230
|
// Create a bundle that renders an error screen
|
|
216
231
|
// This will be executed on the phone just like a normal bundle
|
|
217
232
|
const errorBundle = `
|
|
233
|
+
(function() {
|
|
218
234
|
// Error overlay bundle - match normal bundle structure
|
|
219
235
|
console.log('[ErrorBundle] Starting...');
|
|
220
236
|
|
|
@@ -396,7 +412,7 @@ if (!React || !ReactNative) {
|
|
|
396
412
|
console.log('[ErrorBundle] Set this.App');
|
|
397
413
|
|
|
398
414
|
console.log('[ErrorBundle] Error overlay ready to display');
|
|
399
|
-
}.call(this, (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)))
|
|
415
|
+
}).call(this, (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)));
|
|
400
416
|
`;
|
|
401
417
|
|
|
402
418
|
return errorBundle;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apptuner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Hot reload React Native apps instantly - no Expo needed",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"apptuner": "
|
|
7
|
+
"apptuner": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/cli.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/pepijnvanderknaap/apptuner.git"
|
|
30
|
+
"url": "git+https://github.com/pepijnvanderknaap/apptuner.git"
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://apptuner.io",
|
|
33
33
|
"bugs": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"preview": "vite preview",
|
|
44
44
|
"watcher": "node watcher-server.cjs",
|
|
45
45
|
"metro": "node metro-server.cjs",
|
|
46
|
-
"relay": "
|
|
46
|
+
"relay": "node relay-server.js",
|
|
47
47
|
"mobile": "cd mobile && npm start",
|
|
48
48
|
"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
49
|
"apptuner": "node dist/cli.js",
|
package/watcher-server.cjs
CHANGED
|
@@ -7,7 +7,7 @@ const chokidar = require('chokidar');
|
|
|
7
7
|
const WebSocket = require('ws');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
10
|
-
const PORT = 3030;
|
|
10
|
+
const PORT = parseInt(process.env.WATCHER_PORT || '3030', 10);
|
|
11
11
|
const wss = new WebSocket.Server({ port: PORT });
|
|
12
12
|
|
|
13
13
|
console.log(`🔍 File watcher server running on ws://localhost:${PORT}`);
|