episoda 0.2.20 → 0.2.22
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 +375 -15
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/hooks/post-checkout +3 -2
- package/dist/hooks/pre-commit +6 -5
- package/dist/index.js +405 -485
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -2681,7 +2681,7 @@ var require_package = __commonJS({
|
|
|
2681
2681
|
"package.json"(exports2, module2) {
|
|
2682
2682
|
module2.exports = {
|
|
2683
2683
|
name: "episoda",
|
|
2684
|
-
version: "0.2.
|
|
2684
|
+
version: "0.2.21",
|
|
2685
2685
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2686
2686
|
main: "dist/index.js",
|
|
2687
2687
|
types: "dist/index.d.ts",
|
|
@@ -5141,12 +5141,15 @@ function calculateRestartDelay(restartCount) {
|
|
|
5141
5141
|
const delay = INITIAL_RESTART_DELAY_MS * Math.pow(2, restartCount);
|
|
5142
5142
|
return Math.min(delay, MAX_RESTART_DELAY_MS);
|
|
5143
5143
|
}
|
|
5144
|
-
function spawnDevServerProcess(projectPath, port, moduleUid, logPath) {
|
|
5144
|
+
function spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand) {
|
|
5145
5145
|
rotateLogIfNeeded(logPath);
|
|
5146
5146
|
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
5147
5147
|
const memoryFlag = `--max-old-space-size=${NODE_MEMORY_LIMIT_MB}`;
|
|
5148
5148
|
const enhancedNodeOptions = nodeOptions.includes("max-old-space-size") ? nodeOptions : `${nodeOptions} ${memoryFlag}`.trim();
|
|
5149
|
-
const
|
|
5149
|
+
const command = customCommand || "npm run dev";
|
|
5150
|
+
const [cmd, ...args] = command.split(" ");
|
|
5151
|
+
console.log(`[DevServer] EP959: Starting with command: ${command}`);
|
|
5152
|
+
const devProcess = (0, import_child_process9.spawn)(cmd, args, {
|
|
5150
5153
|
cwd: projectPath,
|
|
5151
5154
|
env: {
|
|
5152
5155
|
...process.env,
|
|
@@ -5154,7 +5157,9 @@ function spawnDevServerProcess(projectPath, port, moduleUid, logPath) {
|
|
|
5154
5157
|
NODE_OPTIONS: enhancedNodeOptions
|
|
5155
5158
|
},
|
|
5156
5159
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5157
|
-
detached: false
|
|
5160
|
+
detached: false,
|
|
5161
|
+
shell: true
|
|
5162
|
+
// EP959: Use shell to handle complex commands
|
|
5158
5163
|
});
|
|
5159
5164
|
devProcess.stdout?.on("data", (data) => {
|
|
5160
5165
|
const line = data.toString().trim();
|
|
@@ -5200,7 +5205,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
5200
5205
|
return;
|
|
5201
5206
|
}
|
|
5202
5207
|
const logPath = serverInfo.logFile || getLogFilePath(moduleUid);
|
|
5203
|
-
const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath);
|
|
5208
|
+
const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath, serverInfo.customCommand);
|
|
5204
5209
|
const updatedInfo = {
|
|
5205
5210
|
...serverInfo,
|
|
5206
5211
|
process: newProcess,
|
|
@@ -5227,6 +5232,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
5227
5232
|
}
|
|
5228
5233
|
async function startDevServer(projectPath, port = 3e3, moduleUid = "default", options = {}) {
|
|
5229
5234
|
const autoRestart = options.autoRestart ?? true;
|
|
5235
|
+
const customCommand = options.customCommand;
|
|
5230
5236
|
if (await isPortInUse(port)) {
|
|
5231
5237
|
console.log(`[DevServer] Server already running on port ${port}`);
|
|
5232
5238
|
return { success: true, alreadyRunning: true };
|
|
@@ -5241,7 +5247,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
5241
5247
|
console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`);
|
|
5242
5248
|
try {
|
|
5243
5249
|
const logPath = getLogFilePath(moduleUid);
|
|
5244
|
-
const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath);
|
|
5250
|
+
const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand);
|
|
5245
5251
|
const serverInfo = {
|
|
5246
5252
|
process: devProcess,
|
|
5247
5253
|
moduleUid,
|
|
@@ -5251,7 +5257,9 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
5251
5257
|
restartCount: 0,
|
|
5252
5258
|
lastRestartAt: null,
|
|
5253
5259
|
autoRestartEnabled: autoRestart,
|
|
5254
|
-
logFile: logPath
|
|
5260
|
+
logFile: logPath,
|
|
5261
|
+
customCommand
|
|
5262
|
+
// EP959-m2: Store for restarts
|
|
5255
5263
|
};
|
|
5256
5264
|
activeServers.set(moduleUid, serverInfo);
|
|
5257
5265
|
writeToLog(logPath, `Starting dev server on port ${port}`, false);
|
|
@@ -5329,11 +5337,11 @@ function getDevServerStatus() {
|
|
|
5329
5337
|
logFile: info.logFile
|
|
5330
5338
|
}));
|
|
5331
5339
|
}
|
|
5332
|
-
async function ensureDevServer(projectPath, port = 3e3, moduleUid = "default") {
|
|
5340
|
+
async function ensureDevServer(projectPath, port = 3e3, moduleUid = "default", customCommand) {
|
|
5333
5341
|
if (await isPortInUse(port)) {
|
|
5334
5342
|
return { success: true };
|
|
5335
5343
|
}
|
|
5336
|
-
return startDevServer(projectPath, port, moduleUid, { autoRestart: true });
|
|
5344
|
+
return startDevServer(projectPath, port, moduleUid, { autoRestart: true, customCommand });
|
|
5337
5345
|
}
|
|
5338
5346
|
|
|
5339
5347
|
// src/utils/port-detect.ts
|
|
@@ -5831,6 +5839,148 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5831
5839
|
this.releaseLock();
|
|
5832
5840
|
}
|
|
5833
5841
|
}
|
|
5842
|
+
// EP959-11: Worktree setup methods
|
|
5843
|
+
/**
|
|
5844
|
+
* Update the setup status of a worktree
|
|
5845
|
+
*/
|
|
5846
|
+
async updateWorktreeStatus(moduleUid, status, error) {
|
|
5847
|
+
return this.updateConfigSafe((config) => {
|
|
5848
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5849
|
+
if (worktree) {
|
|
5850
|
+
worktree.setupStatus = status;
|
|
5851
|
+
if (status === "running") {
|
|
5852
|
+
worktree.setupStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5853
|
+
} else if (status === "ready" || status === "error") {
|
|
5854
|
+
worktree.setupCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5855
|
+
}
|
|
5856
|
+
if (error) {
|
|
5857
|
+
worktree.setupError = error;
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
return config;
|
|
5861
|
+
});
|
|
5862
|
+
}
|
|
5863
|
+
/**
|
|
5864
|
+
* Get worktree info including setup status
|
|
5865
|
+
*/
|
|
5866
|
+
getWorktreeStatus(moduleUid) {
|
|
5867
|
+
const config = this.readConfig();
|
|
5868
|
+
if (!config) return null;
|
|
5869
|
+
return config.worktrees.find((w) => w.moduleUid === moduleUid) || null;
|
|
5870
|
+
}
|
|
5871
|
+
/**
|
|
5872
|
+
* Copy files from main worktree to module worktree
|
|
5873
|
+
*
|
|
5874
|
+
* @deprecated EP964: This function is deprecated. Use worktree_env_vars for
|
|
5875
|
+
* environment variables or worktree_setup_script for other file operations.
|
|
5876
|
+
* This function now returns success (no-op) when main/ worktree doesn't exist.
|
|
5877
|
+
*/
|
|
5878
|
+
async copyFilesFromMain(moduleUid, files) {
|
|
5879
|
+
console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`);
|
|
5880
|
+
console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`);
|
|
5881
|
+
const config = this.readConfig();
|
|
5882
|
+
if (!config) {
|
|
5883
|
+
return { success: false, error: "Config not found" };
|
|
5884
|
+
}
|
|
5885
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5886
|
+
if (!worktree) {
|
|
5887
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5888
|
+
}
|
|
5889
|
+
const mainWorktree = config.worktrees.find((w) => w.moduleUid === "main");
|
|
5890
|
+
if (!mainWorktree) {
|
|
5891
|
+
console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`);
|
|
5892
|
+
return { success: true };
|
|
5893
|
+
}
|
|
5894
|
+
try {
|
|
5895
|
+
for (const file of files) {
|
|
5896
|
+
const srcPath = path12.join(mainWorktree.worktreePath, file);
|
|
5897
|
+
const destPath = path12.join(worktree.worktreePath, file);
|
|
5898
|
+
if (fs11.existsSync(srcPath)) {
|
|
5899
|
+
const destDir = path12.dirname(destPath);
|
|
5900
|
+
if (!fs11.existsSync(destDir)) {
|
|
5901
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
5902
|
+
}
|
|
5903
|
+
fs11.copyFileSync(srcPath, destPath);
|
|
5904
|
+
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
5905
|
+
} else {
|
|
5906
|
+
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
return { success: true };
|
|
5910
|
+
} catch (error) {
|
|
5911
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
/**
|
|
5915
|
+
* Run worktree setup script
|
|
5916
|
+
* EP959-M1: Enhanced logging with working directory, timeout info, and script preview
|
|
5917
|
+
*/
|
|
5918
|
+
async runSetupScript(moduleUid, script) {
|
|
5919
|
+
const config = this.readConfig();
|
|
5920
|
+
if (!config) {
|
|
5921
|
+
return { success: false, error: "Config not found" };
|
|
5922
|
+
}
|
|
5923
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5924
|
+
if (!worktree) {
|
|
5925
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5926
|
+
}
|
|
5927
|
+
const TIMEOUT_MINUTES = 10;
|
|
5928
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
5929
|
+
console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`);
|
|
5930
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
5931
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
5932
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
5933
|
+
try {
|
|
5934
|
+
const { execSync: execSync6 } = require("child_process");
|
|
5935
|
+
execSync6(script, {
|
|
5936
|
+
cwd: worktree.worktreePath,
|
|
5937
|
+
stdio: "inherit",
|
|
5938
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
5939
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
5940
|
+
});
|
|
5941
|
+
console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`);
|
|
5942
|
+
return { success: true };
|
|
5943
|
+
} catch (error) {
|
|
5944
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5945
|
+
console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage);
|
|
5946
|
+
return { success: false, error: errorMessage };
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
/**
|
|
5950
|
+
* Run worktree cleanup script before removal
|
|
5951
|
+
* EP959-m3: Execute cleanup script when worktree is being released
|
|
5952
|
+
*/
|
|
5953
|
+
async runCleanupScript(moduleUid, script) {
|
|
5954
|
+
const config = this.readConfig();
|
|
5955
|
+
if (!config) {
|
|
5956
|
+
return { success: false, error: "Config not found" };
|
|
5957
|
+
}
|
|
5958
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5959
|
+
if (!worktree) {
|
|
5960
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5961
|
+
}
|
|
5962
|
+
const TIMEOUT_MINUTES = 5;
|
|
5963
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
5964
|
+
console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`);
|
|
5965
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
5966
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
5967
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
5968
|
+
try {
|
|
5969
|
+
const { execSync: execSync6 } = require("child_process");
|
|
5970
|
+
execSync6(script, {
|
|
5971
|
+
cwd: worktree.worktreePath,
|
|
5972
|
+
stdio: "inherit",
|
|
5973
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
5974
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
5975
|
+
});
|
|
5976
|
+
console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`);
|
|
5977
|
+
return { success: true };
|
|
5978
|
+
} catch (error) {
|
|
5979
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5980
|
+
console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage);
|
|
5981
|
+
return { success: false, error: errorMessage };
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5834
5984
|
};
|
|
5835
5985
|
function getEpisodaRoot() {
|
|
5836
5986
|
return process.env.EPISODA_ROOT || path12.join(require("os").homedir(), "episoda");
|
|
@@ -5887,6 +6037,17 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
5887
6037
|
}
|
|
5888
6038
|
return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
|
|
5889
6039
|
}
|
|
6040
|
+
async function getProjectRootPath() {
|
|
6041
|
+
const config = await (0, import_core9.loadConfig)();
|
|
6042
|
+
if (!config?.workspace_slug || !config?.project_slug) {
|
|
6043
|
+
return null;
|
|
6044
|
+
}
|
|
6045
|
+
return path13.join(
|
|
6046
|
+
getEpisodaRoot2(),
|
|
6047
|
+
config.workspace_slug,
|
|
6048
|
+
config.project_slug
|
|
6049
|
+
);
|
|
6050
|
+
}
|
|
5890
6051
|
|
|
5891
6052
|
// src/utils/port-allocator.ts
|
|
5892
6053
|
var PORT_RANGE_START = 3100;
|
|
@@ -6694,11 +6855,19 @@ var Daemon = class _Daemon {
|
|
|
6694
6855
|
let result;
|
|
6695
6856
|
if (cmd.action === "start") {
|
|
6696
6857
|
const callbacks = createStreamingCallbacks(cmd.sessionId, message.id);
|
|
6858
|
+
let agentWorkingDir = projectPath;
|
|
6859
|
+
if (cmd.moduleUid) {
|
|
6860
|
+
const worktreeInfo = await getWorktreeInfoForModule(cmd.moduleUid);
|
|
6861
|
+
if (worktreeInfo?.exists) {
|
|
6862
|
+
agentWorkingDir = worktreeInfo.path;
|
|
6863
|
+
console.log(`[Daemon] EP959: Agent for ${cmd.moduleUid} in worktree: ${agentWorkingDir}`);
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6697
6866
|
const startResult = await agentManager.startSession({
|
|
6698
6867
|
sessionId: cmd.sessionId,
|
|
6699
6868
|
moduleId: cmd.moduleId,
|
|
6700
6869
|
moduleUid: cmd.moduleUid,
|
|
6701
|
-
projectPath,
|
|
6870
|
+
projectPath: agentWorkingDir,
|
|
6702
6871
|
message: cmd.message,
|
|
6703
6872
|
credentials: cmd.credentials,
|
|
6704
6873
|
systemPrompt: cmd.systemPrompt,
|
|
@@ -6799,6 +6968,9 @@ var Daemon = class _Daemon {
|
|
|
6799
6968
|
this.flyMachineId = authMessage.flyMachineId;
|
|
6800
6969
|
console.log(`[Daemon] Fly Machine ID: ${this.flyMachineId}`);
|
|
6801
6970
|
}
|
|
6971
|
+
this.syncProjectSettings(projectId).catch((err) => {
|
|
6972
|
+
console.warn("[Daemon] EP964: Settings sync failed:", err.message);
|
|
6973
|
+
});
|
|
6802
6974
|
this.autoStartTunnelsForProject(projectPath, projectId).catch((error) => {
|
|
6803
6975
|
console.error(`[Daemon] EP819: Failed to auto-start tunnels:`, error);
|
|
6804
6976
|
});
|
|
@@ -6837,24 +7009,72 @@ var Daemon = class _Daemon {
|
|
|
6837
7009
|
}
|
|
6838
7010
|
console.log(`[Daemon] EP956: Starting tunnel for ${moduleUid} (${previousState} \u2192 ${state})`);
|
|
6839
7011
|
try {
|
|
6840
|
-
|
|
7012
|
+
let worktree = await getWorktreeInfoForModule(moduleUid);
|
|
6841
7013
|
if (!worktree) {
|
|
6842
7014
|
console.error(`[Daemon] EP956: Cannot resolve worktree path for ${moduleUid} (missing config slugs)`);
|
|
6843
7015
|
return;
|
|
6844
7016
|
}
|
|
6845
|
-
if (!worktree.exists) {
|
|
7017
|
+
if (!worktree.exists && startingWork) {
|
|
7018
|
+
console.log(`[Daemon] EP959: Creating worktree for ${moduleUid} at ${worktree.path}`);
|
|
7019
|
+
const projectRoot = await getProjectRootPath();
|
|
7020
|
+
if (!projectRoot) {
|
|
7021
|
+
console.error(`[Daemon] EP959: Cannot determine project root for worktree creation`);
|
|
7022
|
+
return;
|
|
7023
|
+
}
|
|
7024
|
+
const worktreeManager = new WorktreeManager(projectRoot);
|
|
7025
|
+
const initialized = await worktreeManager.initialize();
|
|
7026
|
+
if (!initialized) {
|
|
7027
|
+
console.error(`[Daemon] EP959: Failed to initialize WorktreeManager at ${projectRoot}`);
|
|
7028
|
+
return;
|
|
7029
|
+
}
|
|
7030
|
+
const moduleBranchName = branchName || moduleUid;
|
|
7031
|
+
const createResult = await worktreeManager.createWorktree(moduleUid, moduleBranchName, true);
|
|
7032
|
+
if (!createResult.success) {
|
|
7033
|
+
console.error(`[Daemon] EP959: Failed to create worktree for ${moduleUid}: ${createResult.error}`);
|
|
7034
|
+
return;
|
|
7035
|
+
}
|
|
7036
|
+
console.log(`[Daemon] EP959: Worktree created for ${moduleUid} at ${createResult.worktreePath}`);
|
|
7037
|
+
worktree = await getWorktreeInfoForModule(moduleUid);
|
|
7038
|
+
if (!worktree || !worktree.exists) {
|
|
7039
|
+
console.error(`[Daemon] EP959: Worktree still not found after creation for ${moduleUid}`);
|
|
7040
|
+
return;
|
|
7041
|
+
}
|
|
7042
|
+
const worktreeConfig = await (0, import_core10.loadConfig)();
|
|
7043
|
+
const setupConfig = worktreeConfig?.project_settings;
|
|
7044
|
+
const hasEnvVars = setupConfig?.worktree_env_vars && Object.keys(setupConfig.worktree_env_vars).length > 0;
|
|
7045
|
+
if (setupConfig?.worktree_copy_files?.length || setupConfig?.worktree_setup_script || hasEnvVars) {
|
|
7046
|
+
console.log(`[Daemon] EP959: Starting async worktree setup for ${moduleUid}`);
|
|
7047
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "pending");
|
|
7048
|
+
this.runWorktreeSetupAsync(
|
|
7049
|
+
moduleUid,
|
|
7050
|
+
worktreeManager,
|
|
7051
|
+
setupConfig.worktree_copy_files || [],
|
|
7052
|
+
setupConfig.worktree_setup_script,
|
|
7053
|
+
worktree.path,
|
|
7054
|
+
setupConfig.worktree_env_vars || {}
|
|
7055
|
+
).then(() => {
|
|
7056
|
+
console.log(`[Daemon] EP959: Setup complete for ${moduleUid}, starting tunnel`);
|
|
7057
|
+
this.startTunnelForModule(moduleUid, worktree.path);
|
|
7058
|
+
}).catch((err) => {
|
|
7059
|
+
console.error(`[Daemon] EP959: Setup failed for ${moduleUid}:`, err);
|
|
7060
|
+
});
|
|
7061
|
+
return;
|
|
7062
|
+
}
|
|
7063
|
+
} else if (!worktree.exists) {
|
|
6846
7064
|
console.log(`[Daemon] EP956: No worktree for ${moduleUid} at ${worktree.path}, skipping tunnel`);
|
|
6847
7065
|
return;
|
|
6848
7066
|
}
|
|
6849
7067
|
const port = allocatePort(moduleUid);
|
|
6850
7068
|
console.log(`[Daemon] EP956: Using worktree ${worktree.path} on port ${port}`);
|
|
6851
|
-
const
|
|
7069
|
+
const devConfig = await (0, import_core10.loadConfig)();
|
|
7070
|
+
const devServerScript = devConfig?.project_settings?.worktree_dev_server_script;
|
|
7071
|
+
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript);
|
|
6852
7072
|
if (!devServerResult.success) {
|
|
6853
7073
|
console.error(`[Daemon] EP956: Dev server failed for ${moduleUid}: ${devServerResult.error}`);
|
|
6854
7074
|
releasePort(moduleUid);
|
|
6855
7075
|
return;
|
|
6856
7076
|
}
|
|
6857
|
-
const config2 =
|
|
7077
|
+
const config2 = devConfig;
|
|
6858
7078
|
const apiUrl = config2?.api_url || "https://episoda.dev";
|
|
6859
7079
|
const startResult = await tunnelManager.startTunnel({
|
|
6860
7080
|
moduleUid,
|
|
@@ -7106,6 +7326,46 @@ var Daemon = class _Daemon {
|
|
|
7106
7326
|
console.warn("[Daemon] Failed to cache device ID:", error instanceof Error ? error.message : error);
|
|
7107
7327
|
}
|
|
7108
7328
|
}
|
|
7329
|
+
/**
|
|
7330
|
+
* EP964: Sync project settings from server on connect/reconnect
|
|
7331
|
+
*
|
|
7332
|
+
* Fetches worktree_env_vars and other settings from the server
|
|
7333
|
+
* and caches them locally for use during worktree checkout.
|
|
7334
|
+
*/
|
|
7335
|
+
async syncProjectSettings(projectId) {
|
|
7336
|
+
try {
|
|
7337
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7338
|
+
if (!config) return;
|
|
7339
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
7340
|
+
const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`);
|
|
7341
|
+
if (!response.ok) {
|
|
7342
|
+
console.warn(`[Daemon] EP964: Failed to sync settings: ${response.status}`);
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
const data = await response.json();
|
|
7346
|
+
const serverSettings = data.settings;
|
|
7347
|
+
if (serverSettings) {
|
|
7348
|
+
const envVars = serverSettings.worktree_env_vars || {};
|
|
7349
|
+
const updatedConfig = {
|
|
7350
|
+
...config,
|
|
7351
|
+
project_settings: {
|
|
7352
|
+
...config.project_settings,
|
|
7353
|
+
worktree_setup_script: serverSettings.worktree_setup_script,
|
|
7354
|
+
worktree_cleanup_script: serverSettings.worktree_cleanup_script,
|
|
7355
|
+
worktree_dev_server_script: serverSettings.worktree_dev_server_script,
|
|
7356
|
+
worktree_env_vars: envVars,
|
|
7357
|
+
// Keep deprecated field for backward compatibility
|
|
7358
|
+
worktree_copy_files: serverSettings.worktree_copy_files,
|
|
7359
|
+
cached_at: Date.now()
|
|
7360
|
+
}
|
|
7361
|
+
};
|
|
7362
|
+
await (0, import_core10.saveConfig)(updatedConfig);
|
|
7363
|
+
console.log(`[Daemon] EP964: Project settings synced (env_vars: ${Object.keys(envVars).length} keys)`);
|
|
7364
|
+
}
|
|
7365
|
+
} catch (error) {
|
|
7366
|
+
console.warn("[Daemon] EP964: Failed to sync project settings:", error instanceof Error ? error.message : error);
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7109
7369
|
/**
|
|
7110
7370
|
* EP956: Cleanup module worktree when module moves to done
|
|
7111
7371
|
*
|
|
@@ -7133,6 +7393,105 @@ var Daemon = class _Daemon {
|
|
|
7133
7393
|
throw error;
|
|
7134
7394
|
}
|
|
7135
7395
|
}
|
|
7396
|
+
/**
|
|
7397
|
+
* EP959-11: Run worktree setup asynchronously
|
|
7398
|
+
* EP964: Added envVars parameter to inject .env file
|
|
7399
|
+
* Writes .env, copies files, and runs setup script after worktree creation
|
|
7400
|
+
*/
|
|
7401
|
+
async runWorktreeSetupAsync(moduleUid, worktreeManager, copyFiles, setupScript, worktreePath, envVars = {}) {
|
|
7402
|
+
console.log(`[Daemon] EP959: Running async worktree setup for ${moduleUid}`);
|
|
7403
|
+
try {
|
|
7404
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "running");
|
|
7405
|
+
if (Object.keys(envVars).length > 0) {
|
|
7406
|
+
console.log(`[Daemon] EP964: Writing .env with ${Object.keys(envVars).length} variables to ${moduleUid}`);
|
|
7407
|
+
const envContent = Object.entries(envVars).map(([key, value]) => {
|
|
7408
|
+
if (/[\s'"#$`\\]/.test(value) || value.includes("\n")) {
|
|
7409
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
7410
|
+
return `${key}="${escaped}"`;
|
|
7411
|
+
}
|
|
7412
|
+
return `${key}=${value}`;
|
|
7413
|
+
}).join("\n") + "\n";
|
|
7414
|
+
const envPath = path14.join(worktreePath, ".env");
|
|
7415
|
+
fs13.writeFileSync(envPath, envContent, { mode: 384 });
|
|
7416
|
+
console.log(`[Daemon] EP964: .env written to ${envPath}`);
|
|
7417
|
+
}
|
|
7418
|
+
if (copyFiles.length > 0) {
|
|
7419
|
+
console.log(`[Daemon] EP964: DEPRECATED - Copying ${copyFiles.length} files to ${moduleUid}`);
|
|
7420
|
+
console.warn(`[Daemon] EP964: worktree_copy_files is deprecated. Use worktree_env_vars or worktree_setup_script instead.`);
|
|
7421
|
+
const copyResult = await worktreeManager.copyFilesFromMain(moduleUid, copyFiles);
|
|
7422
|
+
if (!copyResult.success) {
|
|
7423
|
+
console.warn(`[Daemon] EP964: File copy failed (non-fatal): ${copyResult.error}`);
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
if (setupScript) {
|
|
7427
|
+
console.log(`[Daemon] EP959: Running setup script for ${moduleUid}`);
|
|
7428
|
+
const scriptResult = await worktreeManager.runSetupScript(moduleUid, setupScript);
|
|
7429
|
+
if (!scriptResult.success) {
|
|
7430
|
+
throw new Error(`Setup script failed: ${scriptResult.error}`);
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "ready");
|
|
7434
|
+
console.log(`[Daemon] EP959: Worktree setup complete for ${moduleUid}`);
|
|
7435
|
+
} catch (error) {
|
|
7436
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7437
|
+
console.error(`[Daemon] EP959: Worktree setup failed for ${moduleUid}:`, errorMessage);
|
|
7438
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "error", errorMessage);
|
|
7439
|
+
throw error;
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
/**
|
|
7443
|
+
* EP959-11: Start tunnel for a module after setup completes
|
|
7444
|
+
*/
|
|
7445
|
+
async startTunnelForModule(moduleUid, worktreePath) {
|
|
7446
|
+
const tunnelManager = getTunnelManager();
|
|
7447
|
+
await tunnelManager.initialize();
|
|
7448
|
+
if (tunnelManager.hasTunnel(moduleUid)) {
|
|
7449
|
+
console.log(`[Daemon] EP959: Tunnel already running for ${moduleUid}`);
|
|
7450
|
+
return;
|
|
7451
|
+
}
|
|
7452
|
+
try {
|
|
7453
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7454
|
+
const apiUrl = config?.api_url || "https://episoda.dev";
|
|
7455
|
+
const devServerScript = config?.project_settings?.worktree_dev_server_script;
|
|
7456
|
+
const port = allocatePort(moduleUid);
|
|
7457
|
+
console.log(`[Daemon] EP959: Post-setup tunnel start for ${moduleUid} on port ${port}`);
|
|
7458
|
+
const devServerResult = await ensureDevServer(worktreePath, port, moduleUid, devServerScript);
|
|
7459
|
+
if (!devServerResult.success) {
|
|
7460
|
+
console.error(`[Daemon] EP959: Dev server failed for ${moduleUid}: ${devServerResult.error}`);
|
|
7461
|
+
releasePort(moduleUid);
|
|
7462
|
+
return;
|
|
7463
|
+
}
|
|
7464
|
+
const startResult = await tunnelManager.startTunnel({
|
|
7465
|
+
moduleUid,
|
|
7466
|
+
port,
|
|
7467
|
+
onUrl: async (url) => {
|
|
7468
|
+
console.log(`[Daemon] EP959: Tunnel URL for ${moduleUid}: ${url}`);
|
|
7469
|
+
try {
|
|
7470
|
+
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
|
|
7471
|
+
method: "POST",
|
|
7472
|
+
body: JSON.stringify({ tunnel_url: url })
|
|
7473
|
+
});
|
|
7474
|
+
} catch (err) {
|
|
7475
|
+
console.warn(`[Daemon] EP959: Failed to report tunnel URL:`, err instanceof Error ? err.message : err);
|
|
7476
|
+
}
|
|
7477
|
+
},
|
|
7478
|
+
onStatusChange: (status, error) => {
|
|
7479
|
+
if (status === "error") {
|
|
7480
|
+
console.error(`[Daemon] EP959: Tunnel error for ${moduleUid}: ${error}`);
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
});
|
|
7484
|
+
if (startResult.success) {
|
|
7485
|
+
console.log(`[Daemon] EP959: Tunnel started for ${moduleUid}`);
|
|
7486
|
+
} else {
|
|
7487
|
+
console.error(`[Daemon] EP959: Tunnel failed for ${moduleUid}: ${startResult.error}`);
|
|
7488
|
+
releasePort(moduleUid);
|
|
7489
|
+
}
|
|
7490
|
+
} catch (error) {
|
|
7491
|
+
console.error(`[Daemon] EP959: Error starting tunnel for ${moduleUid}:`, error);
|
|
7492
|
+
releasePort(moduleUid);
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7136
7495
|
/**
|
|
7137
7496
|
* EP819: Auto-start tunnels for active local modules on daemon connect/reconnect
|
|
7138
7497
|
*
|
|
@@ -7235,8 +7594,9 @@ var Daemon = class _Daemon {
|
|
|
7235
7594
|
tunnel_error: null
|
|
7236
7595
|
});
|
|
7237
7596
|
try {
|
|
7597
|
+
const devServerScript = config.project_settings?.worktree_dev_server_script;
|
|
7238
7598
|
console.log(`[Daemon] EP956: Ensuring dev server is running for ${moduleUid} at ${worktree.path}...`);
|
|
7239
|
-
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid);
|
|
7599
|
+
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript);
|
|
7240
7600
|
if (!devServerResult.success) {
|
|
7241
7601
|
const errorMsg2 = `Dev server failed to start: ${devServerResult.error}`;
|
|
7242
7602
|
console.error(`[Daemon] EP956: ${errorMsg2}`);
|