@episoda/cli 0.2.151 → 0.2.153
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/daemon/daemon-process.js +125 -36
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -2604,7 +2604,7 @@ var require_auth = __commonJS({
|
|
|
2604
2604
|
};
|
|
2605
2605
|
})();
|
|
2606
2606
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2607
|
-
exports2.getConfigDir =
|
|
2607
|
+
exports2.getConfigDir = getConfigDir9;
|
|
2608
2608
|
exports2.getConfigPath = getConfigPath;
|
|
2609
2609
|
exports2.loadConfig = loadConfig9;
|
|
2610
2610
|
exports2.saveConfig = saveConfig2;
|
|
@@ -2616,14 +2616,14 @@ var require_auth = __commonJS({
|
|
|
2616
2616
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2617
2617
|
var hasWarnedMissingProjectId = false;
|
|
2618
2618
|
var hasWarnedMissingRequiredFields = false;
|
|
2619
|
-
function
|
|
2619
|
+
function getConfigDir9() {
|
|
2620
2620
|
return process.env.EPISODA_CONFIG_DIR || path25.join(os10.homedir(), ".episoda");
|
|
2621
2621
|
}
|
|
2622
2622
|
function getConfigPath(configPath) {
|
|
2623
2623
|
if (configPath) {
|
|
2624
2624
|
return configPath;
|
|
2625
2625
|
}
|
|
2626
|
-
return path25.join(
|
|
2626
|
+
return path25.join(getConfigDir9(), DEFAULT_CONFIG_FILE);
|
|
2627
2627
|
}
|
|
2628
2628
|
function ensureConfigDir(configPath) {
|
|
2629
2629
|
const dir = path25.dirname(configPath);
|
|
@@ -2913,7 +2913,7 @@ var require_package = __commonJS({
|
|
|
2913
2913
|
"package.json"(exports2, module2) {
|
|
2914
2914
|
module2.exports = {
|
|
2915
2915
|
name: "@episoda/cli",
|
|
2916
|
-
version: "0.2.
|
|
2916
|
+
version: "0.2.153",
|
|
2917
2917
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2918
2918
|
main: "dist/index.js",
|
|
2919
2919
|
types: "dist/index.d.ts",
|
|
@@ -3178,6 +3178,7 @@ var import_core3 = __toESM(require_dist());
|
|
|
3178
3178
|
function getPidFilePath() {
|
|
3179
3179
|
return path3.join((0, import_core3.getConfigDir)(), "daemon.pid");
|
|
3180
3180
|
}
|
|
3181
|
+
var MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
|
|
3181
3182
|
|
|
3182
3183
|
// src/ipc/ipc-server.ts
|
|
3183
3184
|
var net = __toESM(require("net"));
|
|
@@ -7753,8 +7754,9 @@ async function handleWorktreeCreate(request2) {
|
|
|
7753
7754
|
createBranch,
|
|
7754
7755
|
repoUrl,
|
|
7755
7756
|
envVars,
|
|
7756
|
-
setupScript
|
|
7757
|
+
setupScript,
|
|
7757
7758
|
// EP1229: detachedHead removed - planning worktrees no longer exist
|
|
7759
|
+
moduleType
|
|
7758
7760
|
} = request2;
|
|
7759
7761
|
console.log(`[Worktree] K1273: Creating worktree for ${moduleUid}`);
|
|
7760
7762
|
console.log(`[Worktree] Project: ${projectSlug}`);
|
|
@@ -7877,7 +7879,9 @@ ${buildCmd}` : buildCmd;
|
|
|
7877
7879
|
}
|
|
7878
7880
|
let previewUrl;
|
|
7879
7881
|
let port;
|
|
7880
|
-
if (
|
|
7882
|
+
if (moduleType === "ops") {
|
|
7883
|
+
console.log(`[Worktree] EP1363: Skipping preview for ops module ${moduleUid}`);
|
|
7884
|
+
} else if (finalStatus === "ready" && !isCloud) {
|
|
7881
7885
|
port = allocatePort(moduleUid);
|
|
7882
7886
|
console.log(`[Worktree] EP1143: Allocated port ${port} for ${moduleUid}`);
|
|
7883
7887
|
const previewManager = getPreviewManager();
|
|
@@ -10406,7 +10410,7 @@ var path21 = __toESM(require("path"));
|
|
|
10406
10410
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
10407
10411
|
var INITIAL_RESTART_DELAY_MS = 2e3;
|
|
10408
10412
|
var MAX_RESTART_DELAY_MS = 3e4;
|
|
10409
|
-
var
|
|
10413
|
+
var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
|
|
10410
10414
|
var NODE_MEMORY_LIMIT_MB = 2048;
|
|
10411
10415
|
var activeServers = /* @__PURE__ */ new Map();
|
|
10412
10416
|
function getLogsDir() {
|
|
@@ -10423,7 +10427,7 @@ function rotateLogIfNeeded(logPath) {
|
|
|
10423
10427
|
try {
|
|
10424
10428
|
if (fs20.existsSync(logPath)) {
|
|
10425
10429
|
const stats = fs20.statSync(logPath);
|
|
10426
|
-
if (stats.size >
|
|
10430
|
+
if (stats.size > MAX_LOG_SIZE_BYTES2) {
|
|
10427
10431
|
const backupPath = `${logPath}.1`;
|
|
10428
10432
|
if (fs20.existsSync(backupPath)) {
|
|
10429
10433
|
fs20.unlinkSync(backupPath);
|
|
@@ -11203,8 +11207,8 @@ var Daemon = class _Daemon {
|
|
|
11203
11207
|
}
|
|
11204
11208
|
console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
|
|
11205
11209
|
await this.shutdown();
|
|
11206
|
-
const { getConfigDir:
|
|
11207
|
-
const configDir =
|
|
11210
|
+
const { getConfigDir: getConfigDir9 } = require_dist();
|
|
11211
|
+
const configDir = getConfigDir9();
|
|
11208
11212
|
const logPath = path24.join(configDir, "daemon.log");
|
|
11209
11213
|
const logFd = fs23.openSync(logPath, "a");
|
|
11210
11214
|
const child = (0, import_child_process14.spawn)("node", [__filename], {
|
|
@@ -11499,16 +11503,29 @@ var Daemon = class _Daemon {
|
|
|
11499
11503
|
"Content-Type": "application/json",
|
|
11500
11504
|
"X-Project-Id": projectId
|
|
11501
11505
|
};
|
|
11502
|
-
const
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11506
|
+
const controller = new AbortController();
|
|
11507
|
+
const timeoutId = setTimeout(() => controller.abort(), 8e3);
|
|
11508
|
+
try {
|
|
11509
|
+
const response = await fetch(`${config.api_url}/api/cli/status`, {
|
|
11510
|
+
headers,
|
|
11511
|
+
signal: controller.signal
|
|
11512
|
+
});
|
|
11513
|
+
if (response.ok) {
|
|
11514
|
+
const data = await response.json();
|
|
11515
|
+
serverConnected = data.connected === true;
|
|
11516
|
+
serverMachineId = data.machine_id || null;
|
|
11517
|
+
} else {
|
|
11518
|
+
serverError = `Server returned ${response.status}`;
|
|
11519
|
+
}
|
|
11520
|
+
} finally {
|
|
11521
|
+
clearTimeout(timeoutId);
|
|
11509
11522
|
}
|
|
11510
11523
|
} catch (err) {
|
|
11511
|
-
|
|
11524
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
11525
|
+
serverError = "Server verification timed out (8s)";
|
|
11526
|
+
} else {
|
|
11527
|
+
serverError = err instanceof Error ? err.message : "Network error";
|
|
11528
|
+
}
|
|
11512
11529
|
}
|
|
11513
11530
|
const machineMatch = serverMachineId === this.machineId;
|
|
11514
11531
|
return {
|
|
@@ -12412,23 +12429,6 @@ var Daemon = class _Daemon {
|
|
|
12412
12429
|
} catch (pidError) {
|
|
12413
12430
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
12414
12431
|
}
|
|
12415
|
-
const authSuccessPromise = new Promise((resolve4, reject) => {
|
|
12416
|
-
const AUTH_TIMEOUT = 3e4;
|
|
12417
|
-
const timeout = setTimeout(() => {
|
|
12418
|
-
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
12419
|
-
}, AUTH_TIMEOUT);
|
|
12420
|
-
const authHandler = () => {
|
|
12421
|
-
clearTimeout(timeout);
|
|
12422
|
-
resolve4();
|
|
12423
|
-
};
|
|
12424
|
-
client.once("auth_success", authHandler);
|
|
12425
|
-
const errorHandler = (message) => {
|
|
12426
|
-
clearTimeout(timeout);
|
|
12427
|
-
const errorMsg = message;
|
|
12428
|
-
reject(new Error(errorMsg.message || "Authentication failed"));
|
|
12429
|
-
};
|
|
12430
|
-
client.once("auth_error", errorHandler);
|
|
12431
|
-
});
|
|
12432
12432
|
const modeConfig = getDaemonModeConfig();
|
|
12433
12433
|
const environment = modeConfig.mode;
|
|
12434
12434
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
@@ -12441,7 +12441,36 @@ var Daemon = class _Daemon {
|
|
|
12441
12441
|
containerId
|
|
12442
12442
|
});
|
|
12443
12443
|
console.log(`[Daemon] Successfully connected to project ${projectId}`);
|
|
12444
|
-
|
|
12444
|
+
const AUTH_TIMEOUT = 3e4;
|
|
12445
|
+
await new Promise((resolve4, reject) => {
|
|
12446
|
+
let settled = false;
|
|
12447
|
+
const cleanup = () => {
|
|
12448
|
+
clearTimeout(timeout);
|
|
12449
|
+
client.off("auth_success", authHandler);
|
|
12450
|
+
client.off("auth_error", errorHandler);
|
|
12451
|
+
};
|
|
12452
|
+
const timeout = setTimeout(() => {
|
|
12453
|
+
if (settled) return;
|
|
12454
|
+
settled = true;
|
|
12455
|
+
cleanup();
|
|
12456
|
+
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
12457
|
+
}, AUTH_TIMEOUT);
|
|
12458
|
+
const authHandler = () => {
|
|
12459
|
+
if (settled) return;
|
|
12460
|
+
settled = true;
|
|
12461
|
+
cleanup();
|
|
12462
|
+
resolve4();
|
|
12463
|
+
};
|
|
12464
|
+
const errorHandler = (message) => {
|
|
12465
|
+
if (settled) return;
|
|
12466
|
+
settled = true;
|
|
12467
|
+
cleanup();
|
|
12468
|
+
const errorMsg = message;
|
|
12469
|
+
reject(new Error(errorMsg.message || "Authentication failed"));
|
|
12470
|
+
};
|
|
12471
|
+
client.on("auth_success", authHandler);
|
|
12472
|
+
client.on("auth_error", errorHandler);
|
|
12473
|
+
});
|
|
12445
12474
|
console.log(`[Daemon] Authentication complete for project ${projectId}`);
|
|
12446
12475
|
} catch (error) {
|
|
12447
12476
|
console.error(`[Daemon] Failed to connect to ${projectId}:`, error);
|
|
@@ -13234,6 +13263,7 @@ var Daemon = class _Daemon {
|
|
|
13234
13263
|
this.healthCheckCounter = 0;
|
|
13235
13264
|
await this.auditWorktreesOnStartup();
|
|
13236
13265
|
}
|
|
13266
|
+
this.checkAndRotateLog();
|
|
13237
13267
|
} catch (error) {
|
|
13238
13268
|
console.error("[Daemon] EP929: Health check error:", error instanceof Error ? error.message : error);
|
|
13239
13269
|
} finally {
|
|
@@ -13251,6 +13281,50 @@ var Daemon = class _Daemon {
|
|
|
13251
13281
|
console.log("[Daemon] EP929: Health check polling stopped");
|
|
13252
13282
|
}
|
|
13253
13283
|
}
|
|
13284
|
+
static {
|
|
13285
|
+
/**
|
|
13286
|
+
* EP1351: Check daemon.log size and rotate if it exceeds the limit.
|
|
13287
|
+
*
|
|
13288
|
+
* Since the daemon's stdout/stderr are piped to daemon.log via an open FD,
|
|
13289
|
+
* we cannot simply rename the file (the FD follows the inode). Instead:
|
|
13290
|
+
* 1. Copy the current log to daemon.log.1 (shift existing rotated files)
|
|
13291
|
+
* 2. Truncate daemon.log in-place (the open FD continues writing to offset 0)
|
|
13292
|
+
*
|
|
13293
|
+
* This means a small window of duplicate data between .1 and the truncated file,
|
|
13294
|
+
* but that's acceptable for diagnostics logs.
|
|
13295
|
+
*/
|
|
13296
|
+
this.MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
|
|
13297
|
+
}
|
|
13298
|
+
static {
|
|
13299
|
+
// 10 MB
|
|
13300
|
+
this.MAX_LOG_FILES = 3;
|
|
13301
|
+
}
|
|
13302
|
+
checkAndRotateLog() {
|
|
13303
|
+
try {
|
|
13304
|
+
const configDir = (0, import_core14.getConfigDir)();
|
|
13305
|
+
const logPath = path24.join(configDir, "daemon.log");
|
|
13306
|
+
if (!fs23.existsSync(logPath)) return;
|
|
13307
|
+
const stats = fs23.statSync(logPath);
|
|
13308
|
+
if (stats.size < _Daemon.MAX_LOG_SIZE_BYTES) return;
|
|
13309
|
+
console.log(`[Daemon] EP1351: Log rotation triggered (size: ${Math.round(stats.size / 1024 / 1024)}MB)`);
|
|
13310
|
+
const oldestPath = `${logPath}.${_Daemon.MAX_LOG_FILES}`;
|
|
13311
|
+
if (fs23.existsSync(oldestPath)) {
|
|
13312
|
+
fs23.unlinkSync(oldestPath);
|
|
13313
|
+
}
|
|
13314
|
+
for (let i = _Daemon.MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
13315
|
+
const src = `${logPath}.${i}`;
|
|
13316
|
+
const dst = `${logPath}.${i + 1}`;
|
|
13317
|
+
if (fs23.existsSync(src)) {
|
|
13318
|
+
fs23.renameSync(src, dst);
|
|
13319
|
+
}
|
|
13320
|
+
}
|
|
13321
|
+
fs23.copyFileSync(logPath, `${logPath}.1`);
|
|
13322
|
+
fs23.truncateSync(logPath, 0);
|
|
13323
|
+
console.log("[Daemon] EP1351: Log rotated successfully");
|
|
13324
|
+
} catch (error) {
|
|
13325
|
+
console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
|
|
13326
|
+
}
|
|
13327
|
+
}
|
|
13254
13328
|
/**
|
|
13255
13329
|
* EP822: Clean up orphaned tunnels from previous daemon runs
|
|
13256
13330
|
* EP904: Enhanced to aggressively clean ALL cloudflared processes then restart
|
|
@@ -13773,6 +13847,21 @@ var Daemon = class _Daemon {
|
|
|
13773
13847
|
process.exit(0);
|
|
13774
13848
|
}
|
|
13775
13849
|
};
|
|
13850
|
+
var daemonStartedAt = Date.now();
|
|
13851
|
+
function logCrashDiagnostics(label, error) {
|
|
13852
|
+
const uptimeMs = Date.now() - daemonStartedAt;
|
|
13853
|
+
const mem = process.memoryUsage();
|
|
13854
|
+
console.error(`[Daemon] ${label}:`, error);
|
|
13855
|
+
console.error(`[Daemon] Crash diagnostics: uptime=${Math.round(uptimeMs / 1e3)}s, rss=${Math.round(mem.rss / 1024 / 1024)}MB, heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
13856
|
+
}
|
|
13857
|
+
process.on("unhandledRejection", (reason) => {
|
|
13858
|
+
logCrashDiagnostics("Unhandled rejection", reason);
|
|
13859
|
+
process.exit(1);
|
|
13860
|
+
});
|
|
13861
|
+
process.on("uncaughtException", (error) => {
|
|
13862
|
+
logCrashDiagnostics("Uncaught exception", error);
|
|
13863
|
+
process.exit(1);
|
|
13864
|
+
});
|
|
13776
13865
|
async function main() {
|
|
13777
13866
|
if (!process.env.EPISODA_DAEMON_MODE) {
|
|
13778
13867
|
console.error("This script should only be run by daemon-manager");
|