episoda 0.2.21 → 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 +407 -552
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -295,7 +295,7 @@ var require_git_executor = __commonJS({
|
|
|
295
295
|
var git_validator_1 = require_git_validator();
|
|
296
296
|
var git_parser_1 = require_git_parser();
|
|
297
297
|
var execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
298
|
-
var
|
|
298
|
+
var GitExecutor4 = class {
|
|
299
299
|
/**
|
|
300
300
|
* Execute a git command
|
|
301
301
|
* @param command - The git command to execute
|
|
@@ -1531,15 +1531,15 @@ var require_git_executor = __commonJS({
|
|
|
1531
1531
|
try {
|
|
1532
1532
|
const { stdout: gitDir } = await execAsync("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1533
1533
|
const gitDirPath = gitDir.trim();
|
|
1534
|
-
const
|
|
1534
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1535
1535
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1536
1536
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1537
1537
|
try {
|
|
1538
|
-
await
|
|
1538
|
+
await fs11.access(rebaseMergePath);
|
|
1539
1539
|
inRebase = true;
|
|
1540
1540
|
} catch {
|
|
1541
1541
|
try {
|
|
1542
|
-
await
|
|
1542
|
+
await fs11.access(rebaseApplyPath);
|
|
1543
1543
|
inRebase = true;
|
|
1544
1544
|
} catch {
|
|
1545
1545
|
inRebase = false;
|
|
@@ -1593,9 +1593,9 @@ var require_git_executor = __commonJS({
|
|
|
1593
1593
|
error: validation.error || "UNKNOWN_ERROR"
|
|
1594
1594
|
};
|
|
1595
1595
|
}
|
|
1596
|
-
const
|
|
1596
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1597
1597
|
try {
|
|
1598
|
-
await
|
|
1598
|
+
await fs11.access(command.path);
|
|
1599
1599
|
return {
|
|
1600
1600
|
success: false,
|
|
1601
1601
|
error: "WORKTREE_EXISTS",
|
|
@@ -1646,9 +1646,9 @@ var require_git_executor = __commonJS({
|
|
|
1646
1646
|
*/
|
|
1647
1647
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1648
1648
|
try {
|
|
1649
|
-
const
|
|
1649
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1650
1650
|
try {
|
|
1651
|
-
await
|
|
1651
|
+
await fs11.access(command.path);
|
|
1652
1652
|
} catch {
|
|
1653
1653
|
return {
|
|
1654
1654
|
success: false,
|
|
@@ -1801,10 +1801,10 @@ var require_git_executor = __commonJS({
|
|
|
1801
1801
|
*/
|
|
1802
1802
|
async executeCloneBare(command, options) {
|
|
1803
1803
|
try {
|
|
1804
|
-
const
|
|
1805
|
-
const
|
|
1804
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1805
|
+
const path13 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1806
1806
|
try {
|
|
1807
|
-
await
|
|
1807
|
+
await fs11.access(command.path);
|
|
1808
1808
|
return {
|
|
1809
1809
|
success: false,
|
|
1810
1810
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1813,9 +1813,9 @@ var require_git_executor = __commonJS({
|
|
|
1813
1813
|
};
|
|
1814
1814
|
} catch {
|
|
1815
1815
|
}
|
|
1816
|
-
const parentDir =
|
|
1816
|
+
const parentDir = path13.dirname(command.path);
|
|
1817
1817
|
try {
|
|
1818
|
-
await
|
|
1818
|
+
await fs11.mkdir(parentDir, { recursive: true });
|
|
1819
1819
|
} catch {
|
|
1820
1820
|
}
|
|
1821
1821
|
const { stdout, stderr } = await execAsync(
|
|
@@ -1858,24 +1858,24 @@ var require_git_executor = __commonJS({
|
|
|
1858
1858
|
*/
|
|
1859
1859
|
async executeProjectInfo(cwd, options) {
|
|
1860
1860
|
try {
|
|
1861
|
-
const
|
|
1862
|
-
const
|
|
1861
|
+
const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1862
|
+
const path13 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1863
1863
|
let currentPath = cwd;
|
|
1864
1864
|
let worktreeMode = false;
|
|
1865
1865
|
let projectPath = cwd;
|
|
1866
1866
|
let bareRepoPath;
|
|
1867
1867
|
for (let i = 0; i < 10; i++) {
|
|
1868
|
-
const bareDir =
|
|
1869
|
-
const episodaDir =
|
|
1868
|
+
const bareDir = path13.join(currentPath, ".bare");
|
|
1869
|
+
const episodaDir = path13.join(currentPath, ".episoda");
|
|
1870
1870
|
try {
|
|
1871
|
-
await
|
|
1872
|
-
await
|
|
1871
|
+
await fs11.access(bareDir);
|
|
1872
|
+
await fs11.access(episodaDir);
|
|
1873
1873
|
worktreeMode = true;
|
|
1874
1874
|
projectPath = currentPath;
|
|
1875
1875
|
bareRepoPath = bareDir;
|
|
1876
1876
|
break;
|
|
1877
1877
|
} catch {
|
|
1878
|
-
const parentPath =
|
|
1878
|
+
const parentPath = path13.dirname(currentPath);
|
|
1879
1879
|
if (parentPath === currentPath) {
|
|
1880
1880
|
break;
|
|
1881
1881
|
}
|
|
@@ -1998,7 +1998,7 @@ var require_git_executor = __commonJS({
|
|
|
1998
1998
|
}
|
|
1999
1999
|
}
|
|
2000
2000
|
};
|
|
2001
|
-
exports2.GitExecutor =
|
|
2001
|
+
exports2.GitExecutor = GitExecutor4;
|
|
2002
2002
|
}
|
|
2003
2003
|
});
|
|
2004
2004
|
|
|
@@ -2467,34 +2467,34 @@ var require_auth = __commonJS({
|
|
|
2467
2467
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2468
2468
|
exports2.getConfigDir = getConfigDir5;
|
|
2469
2469
|
exports2.getConfigPath = getConfigPath4;
|
|
2470
|
-
exports2.loadConfig =
|
|
2471
|
-
exports2.saveConfig =
|
|
2470
|
+
exports2.loadConfig = loadConfig6;
|
|
2471
|
+
exports2.saveConfig = saveConfig4;
|
|
2472
2472
|
exports2.validateToken = validateToken;
|
|
2473
|
-
var
|
|
2474
|
-
var
|
|
2473
|
+
var fs11 = __importStar(require("fs"));
|
|
2474
|
+
var path13 = __importStar(require("path"));
|
|
2475
2475
|
var os3 = __importStar(require("os"));
|
|
2476
2476
|
var child_process_1 = require("child_process");
|
|
2477
2477
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2478
2478
|
function getConfigDir5() {
|
|
2479
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2479
|
+
return process.env.EPISODA_CONFIG_DIR || path13.join(os3.homedir(), ".episoda");
|
|
2480
2480
|
}
|
|
2481
2481
|
function getConfigPath4(configPath) {
|
|
2482
2482
|
if (configPath) {
|
|
2483
2483
|
return configPath;
|
|
2484
2484
|
}
|
|
2485
|
-
return
|
|
2485
|
+
return path13.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
|
|
2486
2486
|
}
|
|
2487
2487
|
function ensureConfigDir(configPath) {
|
|
2488
|
-
const dir =
|
|
2489
|
-
const isNew = !
|
|
2488
|
+
const dir = path13.dirname(configPath);
|
|
2489
|
+
const isNew = !fs11.existsSync(dir);
|
|
2490
2490
|
if (isNew) {
|
|
2491
|
-
|
|
2491
|
+
fs11.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2492
2492
|
}
|
|
2493
2493
|
if (process.platform === "darwin") {
|
|
2494
|
-
const nosyncPath =
|
|
2495
|
-
if (isNew || !
|
|
2494
|
+
const nosyncPath = path13.join(dir, ".nosync");
|
|
2495
|
+
if (isNew || !fs11.existsSync(nosyncPath)) {
|
|
2496
2496
|
try {
|
|
2497
|
-
|
|
2497
|
+
fs11.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2498
2498
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2499
2499
|
stdio: "ignore",
|
|
2500
2500
|
timeout: 5e3
|
|
@@ -2504,13 +2504,13 @@ var require_auth = __commonJS({
|
|
|
2504
2504
|
}
|
|
2505
2505
|
}
|
|
2506
2506
|
}
|
|
2507
|
-
async function
|
|
2507
|
+
async function loadConfig6(configPath) {
|
|
2508
2508
|
const fullPath = getConfigPath4(configPath);
|
|
2509
|
-
if (!
|
|
2509
|
+
if (!fs11.existsSync(fullPath)) {
|
|
2510
2510
|
return null;
|
|
2511
2511
|
}
|
|
2512
2512
|
try {
|
|
2513
|
-
const content =
|
|
2513
|
+
const content = fs11.readFileSync(fullPath, "utf8");
|
|
2514
2514
|
const config = JSON.parse(content);
|
|
2515
2515
|
return config;
|
|
2516
2516
|
} catch (error) {
|
|
@@ -2518,12 +2518,12 @@ var require_auth = __commonJS({
|
|
|
2518
2518
|
return null;
|
|
2519
2519
|
}
|
|
2520
2520
|
}
|
|
2521
|
-
async function
|
|
2521
|
+
async function saveConfig4(config, configPath) {
|
|
2522
2522
|
const fullPath = getConfigPath4(configPath);
|
|
2523
2523
|
ensureConfigDir(fullPath);
|
|
2524
2524
|
try {
|
|
2525
2525
|
const content = JSON.stringify(config, null, 2);
|
|
2526
|
-
|
|
2526
|
+
fs11.writeFileSync(fullPath, content, { mode: 384 });
|
|
2527
2527
|
} catch (error) {
|
|
2528
2528
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2529
2529
|
}
|
|
@@ -2634,10 +2634,10 @@ var require_dist = __commonJS({
|
|
|
2634
2634
|
|
|
2635
2635
|
// src/index.ts
|
|
2636
2636
|
var import_commander = require("commander");
|
|
2637
|
-
var
|
|
2637
|
+
var import_core14 = __toESM(require_dist());
|
|
2638
2638
|
|
|
2639
2639
|
// src/commands/dev.ts
|
|
2640
|
-
var
|
|
2640
|
+
var import_core4 = __toESM(require_dist());
|
|
2641
2641
|
|
|
2642
2642
|
// src/framework-detector.ts
|
|
2643
2643
|
var fs = __toESM(require("fs"));
|
|
@@ -3110,9 +3110,9 @@ async function getDevServerStatus() {
|
|
|
3110
3110
|
}
|
|
3111
3111
|
|
|
3112
3112
|
// src/commands/dev.ts
|
|
3113
|
-
var
|
|
3114
|
-
var
|
|
3115
|
-
var
|
|
3113
|
+
var import_child_process2 = require("child_process");
|
|
3114
|
+
var path5 = __toESM(require("path"));
|
|
3115
|
+
var fs4 = __toESM(require("fs"));
|
|
3116
3116
|
|
|
3117
3117
|
// src/utils/port-check.ts
|
|
3118
3118
|
var net2 = __toESM(require("net"));
|
|
@@ -3564,6 +3564,148 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3564
3564
|
this.releaseLock();
|
|
3565
3565
|
}
|
|
3566
3566
|
}
|
|
3567
|
+
// EP959-11: Worktree setup methods
|
|
3568
|
+
/**
|
|
3569
|
+
* Update the setup status of a worktree
|
|
3570
|
+
*/
|
|
3571
|
+
async updateWorktreeStatus(moduleUid, status2, error) {
|
|
3572
|
+
return this.updateConfigSafe((config) => {
|
|
3573
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3574
|
+
if (worktree) {
|
|
3575
|
+
worktree.setupStatus = status2;
|
|
3576
|
+
if (status2 === "running") {
|
|
3577
|
+
worktree.setupStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3578
|
+
} else if (status2 === "ready" || status2 === "error") {
|
|
3579
|
+
worktree.setupCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3580
|
+
}
|
|
3581
|
+
if (error) {
|
|
3582
|
+
worktree.setupError = error;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
return config;
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
/**
|
|
3589
|
+
* Get worktree info including setup status
|
|
3590
|
+
*/
|
|
3591
|
+
getWorktreeStatus(moduleUid) {
|
|
3592
|
+
const config = this.readConfig();
|
|
3593
|
+
if (!config) return null;
|
|
3594
|
+
return config.worktrees.find((w) => w.moduleUid === moduleUid) || null;
|
|
3595
|
+
}
|
|
3596
|
+
/**
|
|
3597
|
+
* Copy files from main worktree to module worktree
|
|
3598
|
+
*
|
|
3599
|
+
* @deprecated EP964: This function is deprecated. Use worktree_env_vars for
|
|
3600
|
+
* environment variables or worktree_setup_script for other file operations.
|
|
3601
|
+
* This function now returns success (no-op) when main/ worktree doesn't exist.
|
|
3602
|
+
*/
|
|
3603
|
+
async copyFilesFromMain(moduleUid, files) {
|
|
3604
|
+
console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`);
|
|
3605
|
+
console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`);
|
|
3606
|
+
const config = this.readConfig();
|
|
3607
|
+
if (!config) {
|
|
3608
|
+
return { success: false, error: "Config not found" };
|
|
3609
|
+
}
|
|
3610
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3611
|
+
if (!worktree) {
|
|
3612
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3613
|
+
}
|
|
3614
|
+
const mainWorktree = config.worktrees.find((w) => w.moduleUid === "main");
|
|
3615
|
+
if (!mainWorktree) {
|
|
3616
|
+
console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`);
|
|
3617
|
+
return { success: true };
|
|
3618
|
+
}
|
|
3619
|
+
try {
|
|
3620
|
+
for (const file of files) {
|
|
3621
|
+
const srcPath = path4.join(mainWorktree.worktreePath, file);
|
|
3622
|
+
const destPath = path4.join(worktree.worktreePath, file);
|
|
3623
|
+
if (fs3.existsSync(srcPath)) {
|
|
3624
|
+
const destDir = path4.dirname(destPath);
|
|
3625
|
+
if (!fs3.existsSync(destDir)) {
|
|
3626
|
+
fs3.mkdirSync(destDir, { recursive: true });
|
|
3627
|
+
}
|
|
3628
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
3629
|
+
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
3630
|
+
} else {
|
|
3631
|
+
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
return { success: true };
|
|
3635
|
+
} catch (error) {
|
|
3636
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
/**
|
|
3640
|
+
* Run worktree setup script
|
|
3641
|
+
* EP959-M1: Enhanced logging with working directory, timeout info, and script preview
|
|
3642
|
+
*/
|
|
3643
|
+
async runSetupScript(moduleUid, script) {
|
|
3644
|
+
const config = this.readConfig();
|
|
3645
|
+
if (!config) {
|
|
3646
|
+
return { success: false, error: "Config not found" };
|
|
3647
|
+
}
|
|
3648
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3649
|
+
if (!worktree) {
|
|
3650
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3651
|
+
}
|
|
3652
|
+
const TIMEOUT_MINUTES = 10;
|
|
3653
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
3654
|
+
console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`);
|
|
3655
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
3656
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
3657
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
3658
|
+
try {
|
|
3659
|
+
const { execSync: execSync7 } = require("child_process");
|
|
3660
|
+
execSync7(script, {
|
|
3661
|
+
cwd: worktree.worktreePath,
|
|
3662
|
+
stdio: "inherit",
|
|
3663
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
3664
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
3665
|
+
});
|
|
3666
|
+
console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`);
|
|
3667
|
+
return { success: true };
|
|
3668
|
+
} catch (error) {
|
|
3669
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3670
|
+
console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage);
|
|
3671
|
+
return { success: false, error: errorMessage };
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Run worktree cleanup script before removal
|
|
3676
|
+
* EP959-m3: Execute cleanup script when worktree is being released
|
|
3677
|
+
*/
|
|
3678
|
+
async runCleanupScript(moduleUid, script) {
|
|
3679
|
+
const config = this.readConfig();
|
|
3680
|
+
if (!config) {
|
|
3681
|
+
return { success: false, error: "Config not found" };
|
|
3682
|
+
}
|
|
3683
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
3684
|
+
if (!worktree) {
|
|
3685
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
3686
|
+
}
|
|
3687
|
+
const TIMEOUT_MINUTES = 5;
|
|
3688
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
3689
|
+
console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`);
|
|
3690
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
3691
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
3692
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
3693
|
+
try {
|
|
3694
|
+
const { execSync: execSync7 } = require("child_process");
|
|
3695
|
+
execSync7(script, {
|
|
3696
|
+
cwd: worktree.worktreePath,
|
|
3697
|
+
stdio: "inherit",
|
|
3698
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
3699
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
3700
|
+
});
|
|
3701
|
+
console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`);
|
|
3702
|
+
return { success: true };
|
|
3703
|
+
} catch (error) {
|
|
3704
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3705
|
+
console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage);
|
|
3706
|
+
return { success: false, error: errorMessage };
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3567
3709
|
};
|
|
3568
3710
|
function getEpisodaRoot() {
|
|
3569
3711
|
return process.env.EPISODA_ROOT || path4.join(require("os").homedir(), "episoda");
|
|
@@ -3598,401 +3740,11 @@ async function findProjectRoot(startPath) {
|
|
|
3598
3740
|
return null;
|
|
3599
3741
|
}
|
|
3600
3742
|
|
|
3601
|
-
// src/commands/migrate.ts
|
|
3602
|
-
var fs5 = __toESM(require("fs"));
|
|
3603
|
-
var path6 = __toESM(require("path"));
|
|
3604
|
-
var import_child_process2 = require("child_process");
|
|
3605
|
-
var import_core5 = __toESM(require_dist());
|
|
3606
|
-
|
|
3607
|
-
// src/daemon/project-tracker.ts
|
|
3608
|
-
var fs4 = __toESM(require("fs"));
|
|
3609
|
-
var path5 = __toESM(require("path"));
|
|
3610
|
-
var import_core4 = __toESM(require_dist());
|
|
3611
|
-
function getProjectsFilePath() {
|
|
3612
|
-
return path5.join((0, import_core4.getConfigDir)(), "projects.json");
|
|
3613
|
-
}
|
|
3614
|
-
function readProjects() {
|
|
3615
|
-
const projectsPath = getProjectsFilePath();
|
|
3616
|
-
try {
|
|
3617
|
-
if (!fs4.existsSync(projectsPath)) {
|
|
3618
|
-
return { projects: [] };
|
|
3619
|
-
}
|
|
3620
|
-
const content = fs4.readFileSync(projectsPath, "utf-8");
|
|
3621
|
-
const data = JSON.parse(content);
|
|
3622
|
-
if (!data.projects || !Array.isArray(data.projects)) {
|
|
3623
|
-
console.warn("Invalid projects.json structure, resetting");
|
|
3624
|
-
return { projects: [] };
|
|
3625
|
-
}
|
|
3626
|
-
return data;
|
|
3627
|
-
} catch (error) {
|
|
3628
|
-
console.error("Error reading projects.json:", error);
|
|
3629
|
-
return { projects: [] };
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
function writeProjects(data) {
|
|
3633
|
-
const projectsPath = getProjectsFilePath();
|
|
3634
|
-
try {
|
|
3635
|
-
const dir = path5.dirname(projectsPath);
|
|
3636
|
-
if (!fs4.existsSync(dir)) {
|
|
3637
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
3638
|
-
}
|
|
3639
|
-
fs4.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
3640
|
-
} catch (error) {
|
|
3641
|
-
throw new Error(`Failed to write projects.json: ${error}`);
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
function addProject2(projectId, projectPath, options) {
|
|
3645
|
-
const data = readProjects();
|
|
3646
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3647
|
-
const existingByPath = data.projects.find((p) => p.path === projectPath);
|
|
3648
|
-
if (existingByPath) {
|
|
3649
|
-
existingByPath.id = projectId;
|
|
3650
|
-
existingByPath.last_active = now;
|
|
3651
|
-
if (options?.worktreeMode !== void 0) {
|
|
3652
|
-
existingByPath.worktreeMode = options.worktreeMode;
|
|
3653
|
-
}
|
|
3654
|
-
if (options?.bareRepoPath) {
|
|
3655
|
-
existingByPath.bareRepoPath = options.bareRepoPath;
|
|
3656
|
-
}
|
|
3657
|
-
writeProjects(data);
|
|
3658
|
-
return existingByPath;
|
|
3659
|
-
}
|
|
3660
|
-
const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
|
|
3661
|
-
if (existingByIdIndex !== -1) {
|
|
3662
|
-
const existingById = data.projects[existingByIdIndex];
|
|
3663
|
-
console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
|
|
3664
|
-
data.projects.splice(existingByIdIndex, 1);
|
|
3665
|
-
}
|
|
3666
|
-
const projectName = path5.basename(projectPath);
|
|
3667
|
-
const newProject = {
|
|
3668
|
-
id: projectId,
|
|
3669
|
-
path: projectPath,
|
|
3670
|
-
name: projectName,
|
|
3671
|
-
added_at: now,
|
|
3672
|
-
last_active: now,
|
|
3673
|
-
// EP944: Worktree mode fields
|
|
3674
|
-
worktreeMode: options?.worktreeMode,
|
|
3675
|
-
bareRepoPath: options?.bareRepoPath
|
|
3676
|
-
};
|
|
3677
|
-
data.projects.push(newProject);
|
|
3678
|
-
writeProjects(data);
|
|
3679
|
-
return newProject;
|
|
3680
|
-
}
|
|
3681
|
-
function getProject(projectPath) {
|
|
3682
|
-
const data = readProjects();
|
|
3683
|
-
return data.projects.find((p) => p.path === projectPath) || null;
|
|
3684
|
-
}
|
|
3685
|
-
|
|
3686
|
-
// src/commands/migrate.ts
|
|
3687
|
-
async function migrateCommand(options = {}) {
|
|
3688
|
-
const cwd = options.cwd || process.cwd();
|
|
3689
|
-
status.info("Checking current project...");
|
|
3690
|
-
status.info("");
|
|
3691
|
-
if (!fs5.existsSync(path6.join(cwd, ".git"))) {
|
|
3692
|
-
throw new Error(
|
|
3693
|
-
"Not a git repository.\nRun this command from the root of an existing git project."
|
|
3694
|
-
);
|
|
3695
|
-
}
|
|
3696
|
-
const bareDir = path6.join(cwd, ".bare");
|
|
3697
|
-
if (fs5.existsSync(bareDir)) {
|
|
3698
|
-
throw new Error(
|
|
3699
|
-
"This project is already in worktree mode.\nUse `episoda checkout {module}` to create worktrees."
|
|
3700
|
-
);
|
|
3701
|
-
}
|
|
3702
|
-
const config = await (0, import_core5.loadConfig)();
|
|
3703
|
-
if (!config || !config.access_token) {
|
|
3704
|
-
throw new Error(
|
|
3705
|
-
"Not authenticated. Please run `episoda auth` first."
|
|
3706
|
-
);
|
|
3707
|
-
}
|
|
3708
|
-
const episodaConfigPath = path6.join(cwd, ".episoda", "config.json");
|
|
3709
|
-
if (!fs5.existsSync(episodaConfigPath)) {
|
|
3710
|
-
throw new Error(
|
|
3711
|
-
"No .episoda/config.json found.\nThis project is not connected to episoda.dev.\nUse `episoda clone {workspace}/{project}` to clone a fresh copy."
|
|
3712
|
-
);
|
|
3713
|
-
}
|
|
3714
|
-
let existingConfig;
|
|
3715
|
-
try {
|
|
3716
|
-
existingConfig = JSON.parse(fs5.readFileSync(episodaConfigPath, "utf-8"));
|
|
3717
|
-
} catch (error) {
|
|
3718
|
-
throw new Error("Failed to read .episoda/config.json");
|
|
3719
|
-
}
|
|
3720
|
-
const trackedProject = getProject(cwd);
|
|
3721
|
-
const projectId = trackedProject?.id || existingConfig.project_id;
|
|
3722
|
-
if (!projectId) {
|
|
3723
|
-
throw new Error(
|
|
3724
|
-
"Cannot determine project ID.\nUse `episoda clone {workspace}/{project}` for a fresh clone."
|
|
3725
|
-
);
|
|
3726
|
-
}
|
|
3727
|
-
const gitExecutor = new import_core5.GitExecutor();
|
|
3728
|
-
let currentBranch;
|
|
3729
|
-
try {
|
|
3730
|
-
currentBranch = (0, import_child_process2.execSync)("git branch --show-current", {
|
|
3731
|
-
cwd,
|
|
3732
|
-
encoding: "utf-8"
|
|
3733
|
-
}).trim();
|
|
3734
|
-
} catch {
|
|
3735
|
-
throw new Error("Failed to determine current branch");
|
|
3736
|
-
}
|
|
3737
|
-
const statusResult = await gitExecutor.execute({ action: "status" }, { cwd });
|
|
3738
|
-
const hasUncommitted = (statusResult.details?.uncommittedFiles?.length || 0) > 0;
|
|
3739
|
-
if (hasUncommitted && !options.force) {
|
|
3740
|
-
status.warning("You have uncommitted changes:");
|
|
3741
|
-
for (const file of statusResult.details?.uncommittedFiles || []) {
|
|
3742
|
-
status.info(` - ${file}`);
|
|
3743
|
-
}
|
|
3744
|
-
status.info("");
|
|
3745
|
-
throw new Error(
|
|
3746
|
-
"Please commit or stash your changes first.\nOr use --force to stash changes automatically."
|
|
3747
|
-
);
|
|
3748
|
-
}
|
|
3749
|
-
let remoteUrl;
|
|
3750
|
-
try {
|
|
3751
|
-
remoteUrl = (0, import_child_process2.execSync)("git remote get-url origin", {
|
|
3752
|
-
cwd,
|
|
3753
|
-
encoding: "utf-8"
|
|
3754
|
-
}).trim();
|
|
3755
|
-
} catch {
|
|
3756
|
-
throw new Error(
|
|
3757
|
-
'No git remote "origin" found.\nPlease configure a remote before migrating.'
|
|
3758
|
-
);
|
|
3759
|
-
}
|
|
3760
|
-
let workspaceSlug = existingConfig.workspace_slug || "default";
|
|
3761
|
-
let projectSlug = existingConfig.project_slug || path6.basename(cwd);
|
|
3762
|
-
try {
|
|
3763
|
-
const apiUrl = config.api_url || "https://episoda.dev";
|
|
3764
|
-
const response = await fetch(`${apiUrl}/api/projects/${projectId}`, {
|
|
3765
|
-
headers: {
|
|
3766
|
-
"Authorization": `Bearer ${config.access_token}`,
|
|
3767
|
-
"Content-Type": "application/json"
|
|
3768
|
-
}
|
|
3769
|
-
});
|
|
3770
|
-
if (response.ok) {
|
|
3771
|
-
const responseData = await response.json();
|
|
3772
|
-
const projectData = responseData.data?.project;
|
|
3773
|
-
if (projectData) {
|
|
3774
|
-
workspaceSlug = projectData.workspace_slug || workspaceSlug;
|
|
3775
|
-
projectSlug = projectData.slug || projectSlug;
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
} catch {
|
|
3779
|
-
}
|
|
3780
|
-
const targetPath = getProjectPath(workspaceSlug, projectSlug);
|
|
3781
|
-
status.info("Migration Plan:");
|
|
3782
|
-
status.info(` Current path: ${cwd}`);
|
|
3783
|
-
status.info(` Target path: ${targetPath}`);
|
|
3784
|
-
status.info(` Remote URL: ${remoteUrl}`);
|
|
3785
|
-
status.info(` Current branch: ${currentBranch}`);
|
|
3786
|
-
if (hasUncommitted) {
|
|
3787
|
-
status.warning(` Uncommitted changes will be stashed`);
|
|
3788
|
-
}
|
|
3789
|
-
status.info("");
|
|
3790
|
-
if (options.dryRun) {
|
|
3791
|
-
status.info("Dry run complete. No changes made.");
|
|
3792
|
-
status.info("Remove --dry-run to perform the migration.");
|
|
3793
|
-
return;
|
|
3794
|
-
}
|
|
3795
|
-
const uncommittedFiles = statusResult.details?.uncommittedFiles || [];
|
|
3796
|
-
if (hasUncommitted) {
|
|
3797
|
-
status.info(`Found ${uncommittedFiles.length} uncommitted file(s) to preserve...`);
|
|
3798
|
-
}
|
|
3799
|
-
const targetExistedBefore = fs5.existsSync(targetPath);
|
|
3800
|
-
try {
|
|
3801
|
-
status.info("Creating target directory...");
|
|
3802
|
-
const episodaRoot = getEpisodaRoot();
|
|
3803
|
-
fs5.mkdirSync(targetPath, { recursive: true });
|
|
3804
|
-
status.info("Cloning as bare repository...");
|
|
3805
|
-
const bareRepoPath = path6.join(targetPath, ".bare");
|
|
3806
|
-
const cloneResult = await gitExecutor.execute({
|
|
3807
|
-
action: "clone_bare",
|
|
3808
|
-
url: remoteUrl,
|
|
3809
|
-
path: bareRepoPath
|
|
3810
|
-
});
|
|
3811
|
-
if (!cloneResult.success) {
|
|
3812
|
-
throw new Error(`Failed to clone: ${cloneResult.output}`);
|
|
3813
|
-
}
|
|
3814
|
-
status.success("\u2713 Bare repository cloned");
|
|
3815
|
-
status.info("Creating worktree configuration...");
|
|
3816
|
-
const episodaDir = path6.join(targetPath, ".episoda");
|
|
3817
|
-
fs5.mkdirSync(episodaDir, { recursive: true });
|
|
3818
|
-
const worktreeConfig = {
|
|
3819
|
-
projectId,
|
|
3820
|
-
workspaceSlug,
|
|
3821
|
-
projectSlug,
|
|
3822
|
-
bareRepoPath,
|
|
3823
|
-
worktreeMode: true,
|
|
3824
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3825
|
-
worktrees: []
|
|
3826
|
-
};
|
|
3827
|
-
fs5.writeFileSync(
|
|
3828
|
-
path6.join(episodaDir, "config.json"),
|
|
3829
|
-
JSON.stringify(worktreeConfig, null, 2),
|
|
3830
|
-
"utf-8"
|
|
3831
|
-
);
|
|
3832
|
-
status.info(`Creating worktree for branch "${currentBranch}"...`);
|
|
3833
|
-
const moduleUid = extractModuleUid(currentBranch) || "main";
|
|
3834
|
-
const worktreePath = path6.join(targetPath, moduleUid);
|
|
3835
|
-
const worktreeResult = await gitExecutor.execute({
|
|
3836
|
-
action: "worktree_add",
|
|
3837
|
-
path: worktreePath,
|
|
3838
|
-
branch: currentBranch,
|
|
3839
|
-
create: false
|
|
3840
|
-
}, { cwd: bareRepoPath });
|
|
3841
|
-
if (!worktreeResult.success) {
|
|
3842
|
-
throw new Error(`Failed to create worktree: ${worktreeResult.output}`);
|
|
3843
|
-
}
|
|
3844
|
-
status.success(`\u2713 Worktree created at ${worktreePath}`);
|
|
3845
|
-
worktreeConfig.worktrees.push({
|
|
3846
|
-
moduleUid,
|
|
3847
|
-
branchName: currentBranch,
|
|
3848
|
-
worktreePath,
|
|
3849
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3850
|
-
lastAccessed: (/* @__PURE__ */ new Date()).toISOString()
|
|
3851
|
-
});
|
|
3852
|
-
fs5.writeFileSync(
|
|
3853
|
-
path6.join(episodaDir, "config.json"),
|
|
3854
|
-
JSON.stringify(worktreeConfig, null, 2),
|
|
3855
|
-
"utf-8"
|
|
3856
|
-
);
|
|
3857
|
-
if (hasUncommitted && uncommittedFiles.length > 0) {
|
|
3858
|
-
status.info("Copying uncommitted changes to new worktree...");
|
|
3859
|
-
let copiedCount = 0;
|
|
3860
|
-
let failedCount = 0;
|
|
3861
|
-
for (const file of uncommittedFiles) {
|
|
3862
|
-
const sourcePath = path6.join(cwd, file);
|
|
3863
|
-
const destPath = path6.join(worktreePath, file);
|
|
3864
|
-
try {
|
|
3865
|
-
if (fs5.existsSync(sourcePath)) {
|
|
3866
|
-
const destDir = path6.dirname(destPath);
|
|
3867
|
-
if (!fs5.existsSync(destDir)) {
|
|
3868
|
-
fs5.mkdirSync(destDir, { recursive: true });
|
|
3869
|
-
}
|
|
3870
|
-
fs5.copyFileSync(sourcePath, destPath);
|
|
3871
|
-
copiedCount++;
|
|
3872
|
-
}
|
|
3873
|
-
} catch (copyError) {
|
|
3874
|
-
failedCount++;
|
|
3875
|
-
status.warning(`Could not copy: ${file}`);
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
if (copiedCount > 0) {
|
|
3879
|
-
status.success(`\u2713 Copied ${copiedCount} uncommitted file(s)`);
|
|
3880
|
-
}
|
|
3881
|
-
if (failedCount > 0) {
|
|
3882
|
-
status.warning(`${failedCount} file(s) could not be copied`);
|
|
3883
|
-
status.info("You may need to copy some files manually from the original directory");
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
addProject2(projectId, targetPath, {
|
|
3887
|
-
worktreeMode: true,
|
|
3888
|
-
bareRepoPath
|
|
3889
|
-
});
|
|
3890
|
-
status.success("\u2713 Project registered with daemon");
|
|
3891
|
-
try {
|
|
3892
|
-
const apiUrl = config.api_url || "https://episoda.dev";
|
|
3893
|
-
const settingsResponse = await fetch(`${apiUrl}/api/projects/${projectId}/settings`, {
|
|
3894
|
-
method: "PATCH",
|
|
3895
|
-
headers: {
|
|
3896
|
-
"Authorization": `Bearer ${config.access_token}`,
|
|
3897
|
-
"Content-Type": "application/json"
|
|
3898
|
-
},
|
|
3899
|
-
body: JSON.stringify({
|
|
3900
|
-
local_project_path: worktreePath
|
|
3901
|
-
})
|
|
3902
|
-
});
|
|
3903
|
-
if (settingsResponse.ok) {
|
|
3904
|
-
status.success("\u2713 Project settings updated");
|
|
3905
|
-
} else {
|
|
3906
|
-
status.warning("Could not update project settings in database");
|
|
3907
|
-
}
|
|
3908
|
-
} catch {
|
|
3909
|
-
status.warning("Could not update project settings in database");
|
|
3910
|
-
}
|
|
3911
|
-
try {
|
|
3912
|
-
const updatedConfig = {
|
|
3913
|
-
...config,
|
|
3914
|
-
project_settings: {
|
|
3915
|
-
...config.project_settings,
|
|
3916
|
-
local_project_path: worktreePath,
|
|
3917
|
-
cached_at: Date.now()
|
|
3918
|
-
}
|
|
3919
|
-
};
|
|
3920
|
-
await (0, import_core5.saveConfig)(updatedConfig);
|
|
3921
|
-
status.success("\u2713 Local config updated");
|
|
3922
|
-
} catch {
|
|
3923
|
-
status.warning("Could not update local config cache");
|
|
3924
|
-
}
|
|
3925
|
-
status.info("");
|
|
3926
|
-
status.success("Migration complete!");
|
|
3927
|
-
status.info("");
|
|
3928
|
-
status.info("New project structure:");
|
|
3929
|
-
status.info(` ${targetPath}/`);
|
|
3930
|
-
status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
|
|
3931
|
-
status.info(" \u251C\u2500\u2500 .episoda/");
|
|
3932
|
-
status.info(" \u2502 \u2514\u2500\u2500 config.json");
|
|
3933
|
-
status.info(` \u2514\u2500\u2500 ${moduleUid}/ # Your current worktree`);
|
|
3934
|
-
status.info("");
|
|
3935
|
-
status.info("Next steps:");
|
|
3936
|
-
status.info(` 1. cd ${worktreePath}`);
|
|
3937
|
-
status.info(" 2. Continue working on your current branch");
|
|
3938
|
-
status.info(" 3. Use `episoda checkout EP###` for other modules");
|
|
3939
|
-
status.info("");
|
|
3940
|
-
status.info("Your original repository at:");
|
|
3941
|
-
status.info(` ${cwd}`);
|
|
3942
|
-
status.info("can be safely removed after verifying the migration.");
|
|
3943
|
-
status.info("");
|
|
3944
|
-
} catch (error) {
|
|
3945
|
-
status.error("Migration failed, cleaning up...");
|
|
3946
|
-
if (!targetExistedBefore && fs5.existsSync(targetPath)) {
|
|
3947
|
-
try {
|
|
3948
|
-
fs5.rmSync(targetPath, { recursive: true, force: true });
|
|
3949
|
-
status.info(`Removed ${targetPath}`);
|
|
3950
|
-
} catch {
|
|
3951
|
-
status.warning(`Could not remove ${targetPath}`);
|
|
3952
|
-
}
|
|
3953
|
-
} else if (targetExistedBefore) {
|
|
3954
|
-
status.info("Target directory existed before migration, only removing migration artifacts...");
|
|
3955
|
-
const bareRepoPath = path6.join(targetPath, ".bare");
|
|
3956
|
-
const episodaDir = path6.join(targetPath, ".episoda");
|
|
3957
|
-
if (fs5.existsSync(bareRepoPath)) {
|
|
3958
|
-
try {
|
|
3959
|
-
fs5.rmSync(bareRepoPath, { recursive: true, force: true });
|
|
3960
|
-
status.info("Removed .bare directory");
|
|
3961
|
-
} catch {
|
|
3962
|
-
status.warning("Could not remove .bare directory");
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
if (fs5.existsSync(episodaDir)) {
|
|
3966
|
-
try {
|
|
3967
|
-
const configPath = path6.join(episodaDir, "config.json");
|
|
3968
|
-
if (fs5.existsSync(configPath)) {
|
|
3969
|
-
const config2 = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
|
|
3970
|
-
if (config2.worktreeMode) {
|
|
3971
|
-
fs5.rmSync(episodaDir, { recursive: true, force: true });
|
|
3972
|
-
status.info("Removed .episoda directory");
|
|
3973
|
-
}
|
|
3974
|
-
}
|
|
3975
|
-
} catch {
|
|
3976
|
-
status.warning("Could not remove .episoda directory");
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
}
|
|
3980
|
-
throw error;
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
function extractModuleUid(branchName) {
|
|
3984
|
-
const match = branchName.match(/ep(\d+)/i);
|
|
3985
|
-
if (match) {
|
|
3986
|
-
return `EP${match[1]}`;
|
|
3987
|
-
}
|
|
3988
|
-
return null;
|
|
3989
|
-
}
|
|
3990
|
-
|
|
3991
3743
|
// src/commands/dev.ts
|
|
3992
3744
|
var CONNECTION_MAX_RETRIES = 3;
|
|
3993
3745
|
function findGitRoot(startDir) {
|
|
3994
3746
|
try {
|
|
3995
|
-
const result = (0,
|
|
3747
|
+
const result = (0, import_child_process2.execSync)("git rev-parse --show-toplevel", {
|
|
3996
3748
|
cwd: startDir,
|
|
3997
3749
|
encoding: "utf-8",
|
|
3998
3750
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4004,7 +3756,7 @@ function findGitRoot(startDir) {
|
|
|
4004
3756
|
}
|
|
4005
3757
|
async function devCommand(options = {}) {
|
|
4006
3758
|
try {
|
|
4007
|
-
const config = await (0,
|
|
3759
|
+
const config = await (0, import_core4.loadConfig)();
|
|
4008
3760
|
if (!config || !config.access_token || !config.project_id) {
|
|
4009
3761
|
status.error("No authentication found. Please run:");
|
|
4010
3762
|
status.info("");
|
|
@@ -4056,7 +3808,7 @@ async function devCommand(options = {}) {
|
|
|
4056
3808
|
status.debug(`Using cached project path: ${projectPath}`);
|
|
4057
3809
|
} else {
|
|
4058
3810
|
const detectedRoot = findGitRoot(options.cwd || process.cwd());
|
|
4059
|
-
projectPath = detectedRoot ||
|
|
3811
|
+
projectPath = detectedRoot || path5.resolve(options.cwd || process.cwd());
|
|
4060
3812
|
if (detectedRoot) {
|
|
4061
3813
|
status.debug(`Detected project root: ${projectPath}`);
|
|
4062
3814
|
} else {
|
|
@@ -4070,7 +3822,7 @@ async function devCommand(options = {}) {
|
|
|
4070
3822
|
cached_at: Date.now()
|
|
4071
3823
|
}
|
|
4072
3824
|
};
|
|
4073
|
-
await (0,
|
|
3825
|
+
await (0, import_core4.saveConfig)(updatedConfig);
|
|
4074
3826
|
status.debug("Cached project settings locally");
|
|
4075
3827
|
const settingsUrl = `${serverUrl}/api/projects/${config.project_id}/settings`;
|
|
4076
3828
|
fetch(settingsUrl, {
|
|
@@ -4083,22 +3835,24 @@ async function devCommand(options = {}) {
|
|
|
4083
3835
|
}).catch(() => {
|
|
4084
3836
|
});
|
|
4085
3837
|
}
|
|
4086
|
-
const hasGitDir = fs6.existsSync(path7.join(projectPath, ".git"));
|
|
4087
3838
|
const isWorktree = await isWorktreeProject(projectPath);
|
|
4088
|
-
if (
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
status.info("");
|
|
4093
|
-
try {
|
|
4094
|
-
await migrateCommand({ cwd: projectPath, silent: false });
|
|
4095
|
-
status.success("Project migrated to worktree mode!");
|
|
3839
|
+
if (!isWorktree) {
|
|
3840
|
+
const hasGitDir = fs4.existsSync(path5.join(projectPath, ".git"));
|
|
3841
|
+
if (hasGitDir) {
|
|
3842
|
+
status.error("This project is not in worktree mode.");
|
|
4096
3843
|
status.info("");
|
|
4097
|
-
|
|
4098
|
-
status.
|
|
4099
|
-
status.info("
|
|
3844
|
+
status.info("Episoda requires projects to be cloned in worktree mode.");
|
|
3845
|
+
status.info("To set up this project:");
|
|
3846
|
+
status.info(" 1. Get the workspace/project slug from episoda.dev");
|
|
3847
|
+
status.info(" 2. Run: episoda clone {workspace}/{project}");
|
|
3848
|
+
status.info(" 3. Then: cd ~/episoda/{workspace}/{project}");
|
|
3849
|
+
status.info("");
|
|
3850
|
+
} else {
|
|
3851
|
+
status.error("Not a git repository or worktree project.");
|
|
3852
|
+
status.info("Run this command from within a project cloned with `episoda clone`.");
|
|
4100
3853
|
status.info("");
|
|
4101
3854
|
}
|
|
3855
|
+
process.exit(1);
|
|
4102
3856
|
}
|
|
4103
3857
|
let daemonPid = isDaemonRunning();
|
|
4104
3858
|
if (!daemonPid) {
|
|
@@ -4192,7 +3946,7 @@ async function runDevServer(command, cwd, autoRestart) {
|
|
|
4192
3946
|
let shuttingDown = false;
|
|
4193
3947
|
const startServer = () => {
|
|
4194
3948
|
status.info(`Starting dev server: ${command.join(" ")}`);
|
|
4195
|
-
devProcess = (0,
|
|
3949
|
+
devProcess = (0, import_child_process2.spawn)(command[0], command.slice(1), {
|
|
4196
3950
|
cwd,
|
|
4197
3951
|
stdio: ["inherit", "inherit", "inherit"],
|
|
4198
3952
|
shell: true
|
|
@@ -4245,33 +3999,33 @@ Received ${signal}, shutting down...`);
|
|
|
4245
3999
|
|
|
4246
4000
|
// src/commands/auth.ts
|
|
4247
4001
|
var os = __toESM(require("os"));
|
|
4248
|
-
var
|
|
4249
|
-
var
|
|
4250
|
-
var
|
|
4251
|
-
var
|
|
4002
|
+
var fs6 = __toESM(require("fs"));
|
|
4003
|
+
var path7 = __toESM(require("path"));
|
|
4004
|
+
var import_child_process4 = require("child_process");
|
|
4005
|
+
var import_core6 = __toESM(require_dist());
|
|
4252
4006
|
|
|
4253
4007
|
// src/daemon/machine-id.ts
|
|
4254
|
-
var
|
|
4255
|
-
var
|
|
4008
|
+
var fs5 = __toESM(require("fs"));
|
|
4009
|
+
var path6 = __toESM(require("path"));
|
|
4256
4010
|
var crypto2 = __toESM(require("crypto"));
|
|
4257
|
-
var
|
|
4258
|
-
var
|
|
4011
|
+
var import_child_process3 = require("child_process");
|
|
4012
|
+
var import_core5 = __toESM(require_dist());
|
|
4259
4013
|
function isValidUUID(str) {
|
|
4260
4014
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4261
4015
|
return uuidRegex.test(str);
|
|
4262
4016
|
}
|
|
4263
4017
|
async function getMachineId() {
|
|
4264
|
-
const machineIdPath =
|
|
4018
|
+
const machineIdPath = path6.join((0, import_core5.getConfigDir)(), "machine-id");
|
|
4265
4019
|
try {
|
|
4266
|
-
if (
|
|
4267
|
-
const existingId =
|
|
4020
|
+
if (fs5.existsSync(machineIdPath)) {
|
|
4021
|
+
const existingId = fs5.readFileSync(machineIdPath, "utf-8").trim();
|
|
4268
4022
|
if (existingId) {
|
|
4269
4023
|
if (isValidUUID(existingId)) {
|
|
4270
4024
|
return existingId;
|
|
4271
4025
|
}
|
|
4272
4026
|
console.log("[MachineId] Migrating legacy machine ID to UUID format...");
|
|
4273
4027
|
const newUUID = generateMachineId();
|
|
4274
|
-
|
|
4028
|
+
fs5.writeFileSync(machineIdPath, newUUID, "utf-8");
|
|
4275
4029
|
console.log(`[MachineId] Migrated: ${existingId} \u2192 ${newUUID}`);
|
|
4276
4030
|
return newUUID;
|
|
4277
4031
|
}
|
|
@@ -4280,11 +4034,11 @@ async function getMachineId() {
|
|
|
4280
4034
|
}
|
|
4281
4035
|
const machineId = generateMachineId();
|
|
4282
4036
|
try {
|
|
4283
|
-
const dir =
|
|
4284
|
-
if (!
|
|
4285
|
-
|
|
4037
|
+
const dir = path6.dirname(machineIdPath);
|
|
4038
|
+
if (!fs5.existsSync(dir)) {
|
|
4039
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4286
4040
|
}
|
|
4287
|
-
|
|
4041
|
+
fs5.writeFileSync(machineIdPath, machineId, "utf-8");
|
|
4288
4042
|
} catch (error) {
|
|
4289
4043
|
console.error("Warning: Could not save machine ID to disk:", error);
|
|
4290
4044
|
}
|
|
@@ -4293,7 +4047,7 @@ async function getMachineId() {
|
|
|
4293
4047
|
function getHardwareUUID() {
|
|
4294
4048
|
try {
|
|
4295
4049
|
if (process.platform === "darwin") {
|
|
4296
|
-
const output = (0,
|
|
4050
|
+
const output = (0, import_child_process3.execSync)(
|
|
4297
4051
|
`ioreg -d2 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/{print $(NF-1)}'`,
|
|
4298
4052
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
4299
4053
|
).trim();
|
|
@@ -4301,20 +4055,20 @@ function getHardwareUUID() {
|
|
|
4301
4055
|
return output;
|
|
4302
4056
|
}
|
|
4303
4057
|
} else if (process.platform === "linux") {
|
|
4304
|
-
if (
|
|
4305
|
-
const machineId =
|
|
4058
|
+
if (fs5.existsSync("/etc/machine-id")) {
|
|
4059
|
+
const machineId = fs5.readFileSync("/etc/machine-id", "utf-8").trim();
|
|
4306
4060
|
if (machineId && machineId.length > 0) {
|
|
4307
4061
|
return machineId;
|
|
4308
4062
|
}
|
|
4309
4063
|
}
|
|
4310
|
-
if (
|
|
4311
|
-
const dbusId =
|
|
4064
|
+
if (fs5.existsSync("/var/lib/dbus/machine-id")) {
|
|
4065
|
+
const dbusId = fs5.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
|
|
4312
4066
|
if (dbusId && dbusId.length > 0) {
|
|
4313
4067
|
return dbusId;
|
|
4314
4068
|
}
|
|
4315
4069
|
}
|
|
4316
4070
|
} else if (process.platform === "win32") {
|
|
4317
|
-
const output = (0,
|
|
4071
|
+
const output = (0, import_child_process3.execSync)("wmic csproduct get uuid", {
|
|
4318
4072
|
encoding: "utf-8",
|
|
4319
4073
|
timeout: 5e3
|
|
4320
4074
|
});
|
|
@@ -4695,7 +4449,7 @@ async function authCommand(options = {}) {
|
|
|
4695
4449
|
status.info(` Project: ${tokenResponse.project_uid}`);
|
|
4696
4450
|
status.info(` Workspace: ${tokenResponse.workspace_uid}`);
|
|
4697
4451
|
status.info("");
|
|
4698
|
-
await (0,
|
|
4452
|
+
await (0, import_core6.saveConfig)({
|
|
4699
4453
|
project_id: tokenResponse.project_id,
|
|
4700
4454
|
user_id: tokenResponse.user_id,
|
|
4701
4455
|
workspace_id: tokenResponse.workspace_id,
|
|
@@ -4709,7 +4463,7 @@ async function authCommand(options = {}) {
|
|
|
4709
4463
|
// Convert to Unix timestamp
|
|
4710
4464
|
api_url: apiUrl
|
|
4711
4465
|
});
|
|
4712
|
-
status.success(`\u2713 Configuration saved to ${(0,
|
|
4466
|
+
status.success(`\u2713 Configuration saved to ${(0, import_core6.getConfigPath)()}`);
|
|
4713
4467
|
status.info("");
|
|
4714
4468
|
status.info("Installing git credential helper...");
|
|
4715
4469
|
const credentialHelperInstalled = await installGitCredentialHelper(apiUrl);
|
|
@@ -4753,7 +4507,7 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
|
|
|
4753
4507
|
resolve4(false);
|
|
4754
4508
|
}, expiresIn * 1e3);
|
|
4755
4509
|
const url = `${apiUrl}/api/oauth/authorize-stream?device_code=${deviceCode}`;
|
|
4756
|
-
const curlProcess = (0,
|
|
4510
|
+
const curlProcess = (0, import_child_process4.spawn)("curl", ["-N", url]);
|
|
4757
4511
|
let buffer = "";
|
|
4758
4512
|
curlProcess.stdout.on("data", (chunk) => {
|
|
4759
4513
|
buffer += chunk.toString();
|
|
@@ -4857,7 +4611,7 @@ function openBrowser(url) {
|
|
|
4857
4611
|
break;
|
|
4858
4612
|
}
|
|
4859
4613
|
try {
|
|
4860
|
-
(0,
|
|
4614
|
+
(0, import_child_process4.spawn)(command, args, {
|
|
4861
4615
|
detached: true,
|
|
4862
4616
|
stdio: "ignore"
|
|
4863
4617
|
}).unref();
|
|
@@ -4868,17 +4622,17 @@ function openBrowser(url) {
|
|
|
4868
4622
|
async function installGitCredentialHelper(apiUrl) {
|
|
4869
4623
|
try {
|
|
4870
4624
|
const homeDir = os.homedir();
|
|
4871
|
-
const episodaBinDir =
|
|
4872
|
-
const helperPath =
|
|
4873
|
-
|
|
4625
|
+
const episodaBinDir = path7.join(homeDir, ".episoda", "bin");
|
|
4626
|
+
const helperPath = path7.join(episodaBinDir, "git-credential-episoda");
|
|
4627
|
+
fs6.mkdirSync(episodaBinDir, { recursive: true });
|
|
4874
4628
|
const scriptContent = generateCredentialHelperScript(apiUrl);
|
|
4875
|
-
|
|
4629
|
+
fs6.writeFileSync(helperPath, scriptContent, { mode: 493 });
|
|
4876
4630
|
try {
|
|
4877
|
-
|
|
4631
|
+
fs6.accessSync(helperPath, fs6.constants.X_OK);
|
|
4878
4632
|
} catch {
|
|
4879
4633
|
}
|
|
4880
4634
|
try {
|
|
4881
|
-
const allHelpers = (0,
|
|
4635
|
+
const allHelpers = (0, import_child_process4.execSync)("git config --global --get-all credential.helper", {
|
|
4882
4636
|
encoding: "utf8",
|
|
4883
4637
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4884
4638
|
}).trim().split("\n");
|
|
@@ -4887,7 +4641,7 @@ async function installGitCredentialHelper(apiUrl) {
|
|
|
4887
4641
|
}
|
|
4888
4642
|
} catch {
|
|
4889
4643
|
}
|
|
4890
|
-
(0,
|
|
4644
|
+
(0, import_child_process4.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
|
|
4891
4645
|
encoding: "utf8",
|
|
4892
4646
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4893
4647
|
});
|
|
@@ -4905,19 +4659,19 @@ function updateShellProfile(binDir) {
|
|
|
4905
4659
|
}
|
|
4906
4660
|
const homeDir = os.homedir();
|
|
4907
4661
|
const profiles = [
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4662
|
+
path7.join(homeDir, ".bashrc"),
|
|
4663
|
+
path7.join(homeDir, ".zshrc"),
|
|
4664
|
+
path7.join(homeDir, ".profile")
|
|
4911
4665
|
];
|
|
4912
4666
|
const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda auth`;
|
|
4913
4667
|
for (const profile of profiles) {
|
|
4914
4668
|
try {
|
|
4915
|
-
if (
|
|
4916
|
-
const content =
|
|
4669
|
+
if (fs6.existsSync(profile)) {
|
|
4670
|
+
const content = fs6.readFileSync(profile, "utf8");
|
|
4917
4671
|
if (content.includes(".episoda/bin")) {
|
|
4918
4672
|
continue;
|
|
4919
4673
|
}
|
|
4920
|
-
|
|
4674
|
+
fs6.appendFileSync(profile, `
|
|
4921
4675
|
# Episoda CLI
|
|
4922
4676
|
${exportLine}
|
|
4923
4677
|
`);
|
|
@@ -4929,10 +4683,10 @@ ${exportLine}
|
|
|
4929
4683
|
|
|
4930
4684
|
// src/commands/connect.ts
|
|
4931
4685
|
var os2 = __toESM(require("os"));
|
|
4932
|
-
var
|
|
4933
|
-
var
|
|
4934
|
-
var
|
|
4935
|
-
var
|
|
4686
|
+
var fs7 = __toESM(require("fs"));
|
|
4687
|
+
var path8 = __toESM(require("path"));
|
|
4688
|
+
var import_child_process5 = require("child_process");
|
|
4689
|
+
var import_core7 = __toESM(require_dist());
|
|
4936
4690
|
async function connectCommand(options) {
|
|
4937
4691
|
const { code } = options;
|
|
4938
4692
|
const apiUrl = options.apiUrl || process.env.EPISODA_API_URL || "https://episoda.dev";
|
|
@@ -4948,14 +4702,14 @@ async function connectCommand(options) {
|
|
|
4948
4702
|
status.info(` Project: ${tokenResponse.project_uid}`);
|
|
4949
4703
|
status.info(` Workspace: ${tokenResponse.workspace_uid}`);
|
|
4950
4704
|
status.info("");
|
|
4951
|
-
await (0,
|
|
4705
|
+
await (0, import_core7.saveConfig)({
|
|
4952
4706
|
project_id: tokenResponse.project_id,
|
|
4953
4707
|
user_id: tokenResponse.user_id,
|
|
4954
4708
|
workspace_id: tokenResponse.workspace_id,
|
|
4955
4709
|
access_token: tokenResponse.access_token,
|
|
4956
4710
|
api_url: apiUrl
|
|
4957
4711
|
});
|
|
4958
|
-
status.success(`\u2713 Configuration saved to ${(0,
|
|
4712
|
+
status.success(`\u2713 Configuration saved to ${(0, import_core7.getConfigPath)()}`);
|
|
4959
4713
|
status.info("");
|
|
4960
4714
|
status.info("Installing git credential helper...");
|
|
4961
4715
|
const credentialHelperInstalled = await installGitCredentialHelper2(apiUrl);
|
|
@@ -5005,13 +4759,13 @@ async function exchangeUserCode(apiUrl, userCode, machineId) {
|
|
|
5005
4759
|
async function installGitCredentialHelper2(apiUrl) {
|
|
5006
4760
|
try {
|
|
5007
4761
|
const homeDir = os2.homedir();
|
|
5008
|
-
const episodaBinDir =
|
|
5009
|
-
const helperPath =
|
|
5010
|
-
|
|
4762
|
+
const episodaBinDir = path8.join(homeDir, ".episoda", "bin");
|
|
4763
|
+
const helperPath = path8.join(episodaBinDir, "git-credential-episoda");
|
|
4764
|
+
fs7.mkdirSync(episodaBinDir, { recursive: true });
|
|
5011
4765
|
const scriptContent = generateCredentialHelperScript(apiUrl);
|
|
5012
|
-
|
|
4766
|
+
fs7.writeFileSync(helperPath, scriptContent, { mode: 493 });
|
|
5013
4767
|
try {
|
|
5014
|
-
const allHelpers = (0,
|
|
4768
|
+
const allHelpers = (0, import_child_process5.execSync)("git config --global --get-all credential.helper", {
|
|
5015
4769
|
encoding: "utf8",
|
|
5016
4770
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5017
4771
|
}).trim().split("\n");
|
|
@@ -5020,7 +4774,7 @@ async function installGitCredentialHelper2(apiUrl) {
|
|
|
5020
4774
|
}
|
|
5021
4775
|
} catch {
|
|
5022
4776
|
}
|
|
5023
|
-
(0,
|
|
4777
|
+
(0, import_child_process5.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
|
|
5024
4778
|
encoding: "utf8",
|
|
5025
4779
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5026
4780
|
});
|
|
@@ -5038,19 +4792,19 @@ function updateShellProfile2(binDir) {
|
|
|
5038
4792
|
}
|
|
5039
4793
|
const homeDir = os2.homedir();
|
|
5040
4794
|
const profiles = [
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
4795
|
+
path8.join(homeDir, ".bashrc"),
|
|
4796
|
+
path8.join(homeDir, ".zshrc"),
|
|
4797
|
+
path8.join(homeDir, ".profile")
|
|
5044
4798
|
];
|
|
5045
4799
|
const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda`;
|
|
5046
4800
|
for (const profile of profiles) {
|
|
5047
4801
|
try {
|
|
5048
|
-
if (
|
|
5049
|
-
const content =
|
|
4802
|
+
if (fs7.existsSync(profile)) {
|
|
4803
|
+
const content = fs7.readFileSync(profile, "utf8");
|
|
5050
4804
|
if (content.includes(".episoda/bin")) {
|
|
5051
4805
|
continue;
|
|
5052
4806
|
}
|
|
5053
|
-
|
|
4807
|
+
fs7.appendFileSync(profile, `
|
|
5054
4808
|
# Episoda CLI
|
|
5055
4809
|
${exportLine}
|
|
5056
4810
|
`);
|
|
@@ -5061,11 +4815,11 @@ ${exportLine}
|
|
|
5061
4815
|
}
|
|
5062
4816
|
|
|
5063
4817
|
// src/commands/status.ts
|
|
5064
|
-
var
|
|
4818
|
+
var import_core8 = __toESM(require_dist());
|
|
5065
4819
|
async function statusCommand(options = {}) {
|
|
5066
4820
|
status.info("Checking CLI status...");
|
|
5067
4821
|
status.info("");
|
|
5068
|
-
const config = await (0,
|
|
4822
|
+
const config = await (0, import_core8.loadConfig)();
|
|
5069
4823
|
if (!config) {
|
|
5070
4824
|
status.error("\u2717 CLI not initialized");
|
|
5071
4825
|
status.info("");
|
|
@@ -5080,8 +4834,8 @@ async function statusCommand(options = {}) {
|
|
|
5080
4834
|
status.info("Configuration:");
|
|
5081
4835
|
status.info(` Project ID: ${config.project_id}`);
|
|
5082
4836
|
status.info(` API URL: ${config.api_url}`);
|
|
5083
|
-
status.info(` CLI Version: ${
|
|
5084
|
-
status.info(` Config file: ${(0,
|
|
4837
|
+
status.info(` CLI Version: ${import_core8.VERSION}`);
|
|
4838
|
+
status.info(` Config file: ${(0, import_core8.getConfigPath)()}`);
|
|
5085
4839
|
status.info("");
|
|
5086
4840
|
if (!config.access_token || config.access_token === "") {
|
|
5087
4841
|
status.warning("\u26A0 Not authenticated");
|
|
@@ -5241,9 +4995,87 @@ async function stopCommand(options = {}) {
|
|
|
5241
4995
|
}
|
|
5242
4996
|
|
|
5243
4997
|
// src/commands/clone.ts
|
|
5244
|
-
var
|
|
5245
|
-
var
|
|
5246
|
-
var
|
|
4998
|
+
var fs9 = __toESM(require("fs"));
|
|
4999
|
+
var path10 = __toESM(require("path"));
|
|
5000
|
+
var import_child_process6 = require("child_process");
|
|
5001
|
+
var import_core10 = __toESM(require_dist());
|
|
5002
|
+
|
|
5003
|
+
// src/daemon/project-tracker.ts
|
|
5004
|
+
var fs8 = __toESM(require("fs"));
|
|
5005
|
+
var path9 = __toESM(require("path"));
|
|
5006
|
+
var import_core9 = __toESM(require_dist());
|
|
5007
|
+
function getProjectsFilePath() {
|
|
5008
|
+
return path9.join((0, import_core9.getConfigDir)(), "projects.json");
|
|
5009
|
+
}
|
|
5010
|
+
function readProjects() {
|
|
5011
|
+
const projectsPath = getProjectsFilePath();
|
|
5012
|
+
try {
|
|
5013
|
+
if (!fs8.existsSync(projectsPath)) {
|
|
5014
|
+
return { projects: [] };
|
|
5015
|
+
}
|
|
5016
|
+
const content = fs8.readFileSync(projectsPath, "utf-8");
|
|
5017
|
+
const data = JSON.parse(content);
|
|
5018
|
+
if (!data.projects || !Array.isArray(data.projects)) {
|
|
5019
|
+
console.warn("Invalid projects.json structure, resetting");
|
|
5020
|
+
return { projects: [] };
|
|
5021
|
+
}
|
|
5022
|
+
return data;
|
|
5023
|
+
} catch (error) {
|
|
5024
|
+
console.error("Error reading projects.json:", error);
|
|
5025
|
+
return { projects: [] };
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
function writeProjects(data) {
|
|
5029
|
+
const projectsPath = getProjectsFilePath();
|
|
5030
|
+
try {
|
|
5031
|
+
const dir = path9.dirname(projectsPath);
|
|
5032
|
+
if (!fs8.existsSync(dir)) {
|
|
5033
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
5034
|
+
}
|
|
5035
|
+
fs8.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
5036
|
+
} catch (error) {
|
|
5037
|
+
throw new Error(`Failed to write projects.json: ${error}`);
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
function addProject2(projectId, projectPath, options) {
|
|
5041
|
+
const data = readProjects();
|
|
5042
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5043
|
+
const existingByPath = data.projects.find((p) => p.path === projectPath);
|
|
5044
|
+
if (existingByPath) {
|
|
5045
|
+
existingByPath.id = projectId;
|
|
5046
|
+
existingByPath.last_active = now;
|
|
5047
|
+
if (options?.worktreeMode !== void 0) {
|
|
5048
|
+
existingByPath.worktreeMode = options.worktreeMode;
|
|
5049
|
+
}
|
|
5050
|
+
if (options?.bareRepoPath) {
|
|
5051
|
+
existingByPath.bareRepoPath = options.bareRepoPath;
|
|
5052
|
+
}
|
|
5053
|
+
writeProjects(data);
|
|
5054
|
+
return existingByPath;
|
|
5055
|
+
}
|
|
5056
|
+
const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
|
|
5057
|
+
if (existingByIdIndex !== -1) {
|
|
5058
|
+
const existingById = data.projects[existingByIdIndex];
|
|
5059
|
+
console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
|
|
5060
|
+
data.projects.splice(existingByIdIndex, 1);
|
|
5061
|
+
}
|
|
5062
|
+
const projectName = path9.basename(projectPath);
|
|
5063
|
+
const newProject = {
|
|
5064
|
+
id: projectId,
|
|
5065
|
+
path: projectPath,
|
|
5066
|
+
name: projectName,
|
|
5067
|
+
added_at: now,
|
|
5068
|
+
last_active: now,
|
|
5069
|
+
// EP944: Worktree mode fields
|
|
5070
|
+
worktreeMode: options?.worktreeMode,
|
|
5071
|
+
bareRepoPath: options?.bareRepoPath
|
|
5072
|
+
};
|
|
5073
|
+
data.projects.push(newProject);
|
|
5074
|
+
writeProjects(data);
|
|
5075
|
+
return newProject;
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
// src/commands/clone.ts
|
|
5247
5079
|
async function cloneCommand(slugArg, options = {}) {
|
|
5248
5080
|
const slugParts = slugArg.split("/");
|
|
5249
5081
|
if (slugParts.length !== 2 || !slugParts[0] || !slugParts[1]) {
|
|
@@ -5254,7 +5086,7 @@ async function cloneCommand(slugArg, options = {}) {
|
|
|
5254
5086
|
const [workspaceSlug, projectSlug] = slugParts;
|
|
5255
5087
|
status.info(`Cloning ${workspaceSlug}/${projectSlug}...`);
|
|
5256
5088
|
status.info("");
|
|
5257
|
-
const config = await (0,
|
|
5089
|
+
const config = await (0, import_core10.loadConfig)();
|
|
5258
5090
|
if (!config || !config.access_token) {
|
|
5259
5091
|
throw new Error(
|
|
5260
5092
|
"Not authenticated. Please run `episoda auth` first."
|
|
@@ -5262,9 +5094,9 @@ async function cloneCommand(slugArg, options = {}) {
|
|
|
5262
5094
|
}
|
|
5263
5095
|
const apiUrl = options.apiUrl || config.api_url || "https://episoda.dev";
|
|
5264
5096
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
5265
|
-
if (
|
|
5266
|
-
const bareRepoPath =
|
|
5267
|
-
if (
|
|
5097
|
+
if (fs9.existsSync(projectPath)) {
|
|
5098
|
+
const bareRepoPath = path10.join(projectPath, ".bare");
|
|
5099
|
+
if (fs9.existsSync(bareRepoPath)) {
|
|
5268
5100
|
status.warning(`Project already cloned at ${projectPath}`);
|
|
5269
5101
|
status.info("");
|
|
5270
5102
|
status.info("Next steps:");
|
|
@@ -5296,7 +5128,7 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5296
5128
|
status.info("");
|
|
5297
5129
|
status.info("Creating project directory...");
|
|
5298
5130
|
const episodaRoot = getEpisodaRoot();
|
|
5299
|
-
|
|
5131
|
+
fs9.mkdirSync(projectPath, { recursive: true });
|
|
5300
5132
|
status.success(`\u2713 Created ${projectPath}`);
|
|
5301
5133
|
status.info("Cloning repository (bare)...");
|
|
5302
5134
|
try {
|
|
@@ -5310,6 +5142,9 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5310
5142
|
status.success("\u2713 Repository cloned");
|
|
5311
5143
|
status.info("");
|
|
5312
5144
|
const bareRepoPath = worktreeManager.getBareRepoPath();
|
|
5145
|
+
await extractBootstrapScripts(bareRepoPath, projectPath);
|
|
5146
|
+
status.success("\u2713 Bootstrap scripts extracted");
|
|
5147
|
+
status.info("");
|
|
5313
5148
|
addProject2(projectDetails.id, projectPath, {
|
|
5314
5149
|
worktreeMode: true,
|
|
5315
5150
|
bareRepoPath
|
|
@@ -5320,18 +5155,21 @@ Please configure a repository in the project settings on episoda.dev.`
|
|
|
5320
5155
|
status.info("");
|
|
5321
5156
|
status.info("Project structure:");
|
|
5322
5157
|
status.info(` ${projectPath}/`);
|
|
5323
|
-
status.info(" \u251C\u2500\u2500 .bare/
|
|
5158
|
+
status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
|
|
5324
5159
|
status.info(" \u2514\u2500\u2500 .episoda/");
|
|
5325
|
-
status.info(" \
|
|
5160
|
+
status.info(" \u251C\u2500\u2500 config.json # Project config");
|
|
5161
|
+
status.info(" \u2514\u2500\u2500 scripts/");
|
|
5162
|
+
status.info(" \u2514\u2500\u2500 api-helper.sh # API authentication");
|
|
5326
5163
|
status.info("");
|
|
5327
5164
|
status.info("Next steps:");
|
|
5328
5165
|
status.info(` 1. cd ${projectPath}`);
|
|
5329
|
-
status.info(" 2. episoda
|
|
5166
|
+
status.info(" 2. source .episoda/scripts/api-helper.sh # Load API helpers");
|
|
5167
|
+
status.info(" 3. episoda checkout {moduleUid} # e.g., episoda checkout EP100");
|
|
5330
5168
|
status.info("");
|
|
5331
5169
|
} catch (error) {
|
|
5332
|
-
if (
|
|
5170
|
+
if (fs9.existsSync(projectPath)) {
|
|
5333
5171
|
try {
|
|
5334
|
-
|
|
5172
|
+
fs9.rmSync(projectPath, { recursive: true, force: true });
|
|
5335
5173
|
} catch {
|
|
5336
5174
|
}
|
|
5337
5175
|
}
|
|
@@ -5404,9 +5242,23 @@ ${errorBody}`
|
|
|
5404
5242
|
}
|
|
5405
5243
|
return data;
|
|
5406
5244
|
}
|
|
5245
|
+
async function extractBootstrapScripts(bareRepoPath, projectPath) {
|
|
5246
|
+
const scriptsDir = path10.join(projectPath, ".episoda", "scripts");
|
|
5247
|
+
fs9.mkdirSync(scriptsDir, { recursive: true });
|
|
5248
|
+
try {
|
|
5249
|
+
const scriptContent = (0, import_child_process6.execSync)(
|
|
5250
|
+
`git --git-dir="${bareRepoPath}" show main:scripts/api-helper.sh`,
|
|
5251
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5252
|
+
);
|
|
5253
|
+
const scriptPath = path10.join(scriptsDir, "api-helper.sh");
|
|
5254
|
+
fs9.writeFileSync(scriptPath, scriptContent, { mode: 493 });
|
|
5255
|
+
} catch (error) {
|
|
5256
|
+
console.log("[clone] Could not extract api-helper.sh:", error);
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5407
5259
|
|
|
5408
5260
|
// src/commands/checkout.ts
|
|
5409
|
-
var
|
|
5261
|
+
var import_core11 = __toESM(require_dist());
|
|
5410
5262
|
|
|
5411
5263
|
// src/utils/http.ts
|
|
5412
5264
|
async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
@@ -5446,7 +5298,7 @@ async function checkoutCommand(moduleUid, options = {}) {
|
|
|
5446
5298
|
"Not in a worktree project.\nRun this command from within a project cloned with `episoda clone`.\nOr cd to ~/episoda/{workspace}/{project}/"
|
|
5447
5299
|
);
|
|
5448
5300
|
}
|
|
5449
|
-
const config = await (0,
|
|
5301
|
+
const config = await (0, import_core11.loadConfig)();
|
|
5450
5302
|
if (!config || !config.access_token) {
|
|
5451
5303
|
throw new Error(
|
|
5452
5304
|
"Not authenticated. Please run `episoda auth` first."
|
|
@@ -5485,8 +5337,8 @@ The .bare directory or .episoda/config.json may be missing.`
|
|
|
5485
5337
|
const branchName = moduleDetails.branch_name || `feature/${moduleUid.toLowerCase()}`;
|
|
5486
5338
|
let createBranch = !moduleDetails.branch_name || options.create;
|
|
5487
5339
|
if (createBranch && !moduleDetails.branch_name) {
|
|
5488
|
-
const { GitExecutor:
|
|
5489
|
-
const gitExecutor = new
|
|
5340
|
+
const { GitExecutor: GitExecutor4 } = await Promise.resolve().then(() => __toESM(require_dist()));
|
|
5341
|
+
const gitExecutor = new GitExecutor4();
|
|
5490
5342
|
const branchCheck = await gitExecutor.execute(
|
|
5491
5343
|
{ action: "branch_exists", branch: branchName },
|
|
5492
5344
|
{ cwd: worktreeManager.getBareRepoPath() }
|
|
@@ -5583,8 +5435,8 @@ async function updateModuleCheckout(apiUrl, moduleId, accessToken, branchName) {
|
|
|
5583
5435
|
}
|
|
5584
5436
|
|
|
5585
5437
|
// src/commands/release.ts
|
|
5586
|
-
var
|
|
5587
|
-
var
|
|
5438
|
+
var path11 = __toESM(require("path"));
|
|
5439
|
+
var import_core12 = __toESM(require_dist());
|
|
5588
5440
|
async function releaseCommand(moduleUid, options = {}) {
|
|
5589
5441
|
if (!moduleUid || !moduleUid.match(/^EP\d+$/)) {
|
|
5590
5442
|
throw new Error(
|
|
@@ -5630,12 +5482,24 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5630
5482
|
);
|
|
5631
5483
|
}
|
|
5632
5484
|
}
|
|
5633
|
-
const currentPath =
|
|
5485
|
+
const currentPath = path11.resolve(process.cwd());
|
|
5634
5486
|
if (currentPath.startsWith(existing.worktreePath)) {
|
|
5635
5487
|
status.warning("You are inside the worktree being released.");
|
|
5636
5488
|
status.info(`Please cd to ${projectRoot} first.`);
|
|
5637
5489
|
throw new Error("Cannot release worktree while inside it");
|
|
5638
5490
|
}
|
|
5491
|
+
const config = await (0, import_core12.loadConfig)();
|
|
5492
|
+
const cleanupScript = config?.project_settings?.worktree_cleanup_script;
|
|
5493
|
+
if (cleanupScript) {
|
|
5494
|
+
status.info("Running cleanup script...");
|
|
5495
|
+
const cleanupResult = await worktreeManager.runCleanupScript(moduleUid, cleanupScript);
|
|
5496
|
+
if (!cleanupResult.success) {
|
|
5497
|
+
status.warning(`Cleanup script failed: ${cleanupResult.error}`);
|
|
5498
|
+
status.info("Continuing with worktree removal...");
|
|
5499
|
+
} else {
|
|
5500
|
+
status.success("\u2713 Cleanup script completed");
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5639
5503
|
status.info("Removing worktree...");
|
|
5640
5504
|
const result = await worktreeManager.removeWorktree(moduleUid, options.force);
|
|
5641
5505
|
if (!result.success) {
|
|
@@ -5644,7 +5508,6 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5644
5508
|
status.success("\u2713 Worktree removed");
|
|
5645
5509
|
status.info("");
|
|
5646
5510
|
try {
|
|
5647
|
-
const config = await (0, import_core13.loadConfig)();
|
|
5648
5511
|
if (config?.access_token) {
|
|
5649
5512
|
const worktreeConfig = worktreeManager.getConfig();
|
|
5650
5513
|
if (worktreeConfig) {
|
|
@@ -5668,7 +5531,7 @@ Commit or stash your changes first, or use --force to discard them.`
|
|
|
5668
5531
|
status.info("");
|
|
5669
5532
|
}
|
|
5670
5533
|
async function checkUncommittedChanges(worktreePath) {
|
|
5671
|
-
const gitExecutor = new
|
|
5534
|
+
const gitExecutor = new import_core12.GitExecutor();
|
|
5672
5535
|
const result = await gitExecutor.execute(
|
|
5673
5536
|
{ action: "status" },
|
|
5674
5537
|
{ cwd: worktreePath }
|
|
@@ -5693,10 +5556,10 @@ async function updateModuleRelease(apiUrl, projectId, workspaceSlug, moduleUid,
|
|
|
5693
5556
|
}
|
|
5694
5557
|
|
|
5695
5558
|
// src/commands/list.ts
|
|
5696
|
-
var
|
|
5697
|
-
var
|
|
5559
|
+
var fs10 = __toESM(require("fs"));
|
|
5560
|
+
var path12 = __toESM(require("path"));
|
|
5698
5561
|
var import_chalk2 = __toESM(require("chalk"));
|
|
5699
|
-
var
|
|
5562
|
+
var import_core13 = __toESM(require_dist());
|
|
5700
5563
|
async function listCommand(subcommand, options = {}) {
|
|
5701
5564
|
if (subcommand === "worktrees" || subcommand === "wt") {
|
|
5702
5565
|
await listWorktrees(options);
|
|
@@ -5706,7 +5569,7 @@ async function listCommand(subcommand, options = {}) {
|
|
|
5706
5569
|
}
|
|
5707
5570
|
async function listProjects(options) {
|
|
5708
5571
|
const episodaRoot = getEpisodaRoot();
|
|
5709
|
-
if (!
|
|
5572
|
+
if (!fs10.existsSync(episodaRoot)) {
|
|
5710
5573
|
status.info("No projects cloned yet.");
|
|
5711
5574
|
status.info("");
|
|
5712
5575
|
status.info("Clone a project with:");
|
|
@@ -5714,12 +5577,12 @@ async function listProjects(options) {
|
|
|
5714
5577
|
return;
|
|
5715
5578
|
}
|
|
5716
5579
|
const projects = [];
|
|
5717
|
-
const workspaces =
|
|
5580
|
+
const workspaces = fs10.readdirSync(episodaRoot, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
5718
5581
|
for (const workspace of workspaces) {
|
|
5719
|
-
const workspacePath =
|
|
5720
|
-
const projectDirs =
|
|
5582
|
+
const workspacePath = path12.join(episodaRoot, workspace.name);
|
|
5583
|
+
const projectDirs = fs10.readdirSync(workspacePath, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
5721
5584
|
for (const projectDir of projectDirs) {
|
|
5722
|
-
const projectPath =
|
|
5585
|
+
const projectPath = path12.join(workspacePath, projectDir.name);
|
|
5723
5586
|
if (await isWorktreeProject(projectPath)) {
|
|
5724
5587
|
const manager = new WorktreeManager(projectPath);
|
|
5725
5588
|
await manager.initialize();
|
|
@@ -5784,7 +5647,7 @@ async function listWorktrees(options) {
|
|
|
5784
5647
|
status.info(" episoda checkout {moduleUid}");
|
|
5785
5648
|
return;
|
|
5786
5649
|
}
|
|
5787
|
-
const gitExecutor = new
|
|
5650
|
+
const gitExecutor = new import_core13.GitExecutor();
|
|
5788
5651
|
for (const wt of worktrees) {
|
|
5789
5652
|
console.log("");
|
|
5790
5653
|
let statusIndicator = import_chalk2.default.green("\u25CF");
|
|
@@ -5821,7 +5684,7 @@ async function listWorktrees(options) {
|
|
|
5821
5684
|
}
|
|
5822
5685
|
|
|
5823
5686
|
// src/index.ts
|
|
5824
|
-
import_commander.program.name("episoda").description("Episoda local development
|
|
5687
|
+
import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core14.VERSION);
|
|
5825
5688
|
import_commander.program.command("auth").description("Authenticate to Episoda via OAuth and configure CLI").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (options) => {
|
|
5826
5689
|
try {
|
|
5827
5690
|
await authCommand(options);
|
|
@@ -5838,7 +5701,7 @@ import_commander.program.command("connect").description("Connect using a pre-aut
|
|
|
5838
5701
|
process.exit(1);
|
|
5839
5702
|
}
|
|
5840
5703
|
});
|
|
5841
|
-
import_commander.program.command("dev").description("
|
|
5704
|
+
import_commander.program.command("dev").description("Connect to Episoda and start dev server (worktree mode required)").option("--auto-restart", "Auto-restart dev server if it crashes").option("--connection-only", "Connection-only mode (don't start dev server)").allowUnknownOption().action(async (options, cmd) => {
|
|
5842
5705
|
try {
|
|
5843
5706
|
const args = process.argv.slice(process.argv.indexOf("dev") + 1);
|
|
5844
5707
|
const separatorIndex = args.indexOf("--");
|
|
@@ -5909,14 +5772,6 @@ import_commander.program.command("list").description("List cloned projects and w
|
|
|
5909
5772
|
process.exit(1);
|
|
5910
5773
|
}
|
|
5911
5774
|
});
|
|
5912
|
-
import_commander.program.command("migrate").description("Convert existing project to worktree mode").option("--force", "Force migration even with uncommitted changes (auto-stash)").option("--dry-run", "Show what would be done without making changes").action(async (options) => {
|
|
5913
|
-
try {
|
|
5914
|
-
await migrateCommand(options);
|
|
5915
|
-
} catch (error) {
|
|
5916
|
-
status.error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5917
|
-
process.exit(1);
|
|
5918
|
-
}
|
|
5919
|
-
});
|
|
5920
5775
|
import_commander.program.command("dev:restart").description("Restart the dev server for a module").argument("[moduleUid]", "Module UID (required)").action(async (moduleUid) => {
|
|
5921
5776
|
try {
|
|
5922
5777
|
if (!moduleUid) {
|