zdev 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -475,11 +475,11 @@ var require_option = __commonJS((exports) => {
475
475
  }
476
476
 
477
477
  class DualOptions {
478
- constructor(options) {
478
+ constructor(options2) {
479
479
  this.positiveOptions = new Map;
480
480
  this.negativeOptions = new Map;
481
481
  this.dualOptions = new Set;
482
- options.forEach((option) => {
482
+ options2.forEach((option) => {
483
483
  if (option.negate) {
484
484
  this.negativeOptions.set(option.attributeName(), option);
485
485
  } else {
@@ -940,12 +940,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
940
940
  }
941
941
  return this;
942
942
  }
943
- _optionEx(config, flags, description, fn, defaultValue) {
943
+ _optionEx(config2, flags, description, fn, defaultValue) {
944
944
  if (typeof flags === "object" && flags instanceof Option) {
945
945
  throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");
946
946
  }
947
947
  const option = this.createOption(flags, description);
948
- option.makeOptionMandatory(!!config.mandatory);
948
+ option.makeOptionMandatory(!!config2.mandatory);
949
949
  if (typeof fn === "function") {
950
950
  option.default(defaultValue).argParser(fn);
951
951
  } else if (fn instanceof RegExp) {
@@ -1514,9 +1514,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1514
1514
  `);
1515
1515
  this.outputHelp({ error: true });
1516
1516
  }
1517
- const config = errorOptions || {};
1518
- const exitCode = config.exitCode || 1;
1519
- const code = config.code || "commander.error";
1517
+ const config2 = errorOptions || {};
1518
+ const exitCode = config2.exitCode || 1;
1519
+ const code = config2.code || "commander.error";
1520
1520
  this._exit(exitCode, code, message);
1521
1521
  }
1522
1522
  _parseOptionsEnv() {
@@ -1929,32 +1929,32 @@ function loadConfig() {
1929
1929
  return DEFAULT_CONFIG;
1930
1930
  }
1931
1931
  }
1932
- function saveConfig(config) {
1932
+ function saveConfig(config2) {
1933
1933
  ensurezdevDirs();
1934
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
1934
+ writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2));
1935
1935
  }
1936
- function allocatePorts(config, includeConvex = true) {
1937
- const frontend = config.nextFrontendPort;
1938
- config.nextFrontendPort = frontend + 1;
1936
+ function allocatePorts(config2, includeConvex = true) {
1937
+ const frontend = config2.nextFrontendPort;
1938
+ config2.nextFrontendPort = frontend + 1;
1939
1939
  let convex = 0;
1940
1940
  if (includeConvex) {
1941
- convex = config.nextConvexPort;
1942
- config.nextConvexPort = convex + 1;
1941
+ convex = config2.nextConvexPort;
1942
+ config2.nextConvexPort = convex + 1;
1943
1943
  }
1944
1944
  return { frontend, convex };
1945
1945
  }
1946
1946
  function getWorktreePath(name) {
1947
1947
  return join(WORKTREES_DIR, name);
1948
1948
  }
1949
- function getSeedPath(projectName) {
1950
- return join(SEEDS_DIR, `${projectName}.zip`);
1949
+ function getSeedPath(projectName2) {
1950
+ return join(SEEDS_DIR, `${projectName2}.zip`);
1951
1951
  }
1952
1952
 
1953
1953
  // src/utils.ts
1954
- function run(command, args, options) {
1954
+ function run(command, args, options2) {
1955
1955
  const result = spawnSync(command, args, {
1956
1956
  encoding: "utf-8",
1957
- ...options
1957
+ ...options2
1958
1958
  });
1959
1959
  return {
1960
1960
  success: result.status === 0,
@@ -1963,11 +1963,11 @@ function run(command, args, options) {
1963
1963
  code: result.status
1964
1964
  };
1965
1965
  }
1966
- function runBackground(command, args, options) {
1966
+ function runBackground(command, args, options2) {
1967
1967
  const child = spawn(command, args, {
1968
1968
  detached: true,
1969
1969
  stdio: "ignore",
1970
- ...options
1970
+ ...options2
1971
1971
  });
1972
1972
  child.unref();
1973
1973
  return child.pid;
@@ -1989,15 +1989,15 @@ function gitFetch(repoPath) {
1989
1989
  const result = run("git", ["fetch", "origin"], { cwd: repoPath });
1990
1990
  return result.success;
1991
1991
  }
1992
- function createWorktree(repoPath, worktreePath, branch, baseBranch = "origin/main") {
1993
- const result = run("git", ["worktree", "add", worktreePath, "-b", branch, baseBranch], { cwd: repoPath });
1992
+ function createWorktree(repoPath, worktreePath2, branch2, baseBranch = "origin/main") {
1993
+ const result = run("git", ["worktree", "add", worktreePath2, "-b", branch2, baseBranch], { cwd: repoPath });
1994
1994
  if (!result.success) {
1995
1995
  return { success: false, error: result.stderr };
1996
1996
  }
1997
1997
  return { success: true };
1998
1998
  }
1999
- function removeWorktree(repoPath, worktreePath) {
2000
- const result = run("git", ["worktree", "remove", worktreePath, "--force"], {
1999
+ function removeWorktree(repoPath, worktreePath2) {
2000
+ const result = run("git", ["worktree", "remove", worktreePath2, "--force"], {
2001
2001
  cwd: repoPath
2002
2002
  });
2003
2003
  if (!result.success) {
@@ -2007,14 +2007,14 @@ function removeWorktree(repoPath, worktreePath) {
2007
2007
  return { success: true };
2008
2008
  }
2009
2009
  function traefikAddRoute(name, port) {
2010
- const config = loadConfig();
2011
- const configPath = `${config.traefikConfigDir}/${name}.yml`;
2010
+ const config2 = loadConfig();
2011
+ const configPath = `${config2.traefikConfigDir}/${name}.yml`;
2012
2012
  const subdomain = name;
2013
2013
  const traefikConfig = `# zdev auto-generated config for ${name}
2014
2014
  http:
2015
2015
  routers:
2016
2016
  ${name}:
2017
- rule: "Host(\`${subdomain}.${config.devDomain}\`)"
2017
+ rule: "Host(\`${subdomain}.${config2.devDomain}\`)"
2018
2018
  entrypoints:
2019
2019
  - websecure
2020
2020
  service: ${name}
@@ -2025,7 +2025,7 @@ http:
2025
2025
  ${name}:
2026
2026
  loadBalancer:
2027
2027
  servers:
2028
- - url: "http://${config.dockerHostIp}:${port}"
2028
+ - url: "http://${config2.dockerHostIp}:${port}"
2029
2029
  `;
2030
2030
  try {
2031
2031
  writeFileSync2(configPath, traefikConfig);
@@ -2047,15 +2047,15 @@ function traefikRemoveRoute(name) {
2047
2047
  }
2048
2048
  }
2049
2049
  function getTraefikStatus() {
2050
- const config = loadConfig();
2051
- if (!config.devDomain) {
2050
+ const config2 = loadConfig();
2051
+ if (!config2.devDomain) {
2052
2052
  return { running: false, devDomain: undefined };
2053
2053
  }
2054
- const configDirExists = existsSync2(config.traefikConfigDir);
2054
+ const configDirExists = existsSync2(config2.traefikConfigDir);
2055
2055
  return {
2056
2056
  running: configDirExists,
2057
- baseUrl: configDirExists ? `https://*.${config.devDomain}` : undefined,
2058
- devDomain: config.devDomain
2057
+ baseUrl: configDirExists ? `https://*.${config2.devDomain}` : undefined,
2058
+ devDomain: config2.devDomain
2059
2059
  };
2060
2060
  }
2061
2061
  function killProcess(pid) {
@@ -2238,22 +2238,22 @@ bun install
2238
2238
  # bunx prisma generate
2239
2239
  # cp ../.env.local .
2240
2240
  `;
2241
- async function create(projectName, options = {}) {
2242
- const targetPath = resolve2(projectName);
2241
+ async function create(projectName2, options2 = {}) {
2242
+ const targetPath = resolve2(projectName2);
2243
2243
  if (existsSync3(targetPath)) {
2244
2244
  console.error(`❌ Directory already exists: ${targetPath}`);
2245
2245
  process.exit(1);
2246
2246
  }
2247
- console.log(`\uD83D\uDC02 Creating new project: ${projectName}`);
2248
- console.log(` Convex: ${options.convex ? "yes" : "no"}`);
2249
- console.log(` Structure: ${options.flat ? "flat" : "monorepo"}`);
2247
+ console.log(`\uD83D\uDC02 Creating new project: ${projectName2}`);
2248
+ console.log(` Convex: ${options2.convex ? "yes" : "no"}`);
2249
+ console.log(` Structure: ${options2.flat ? "flat" : "monorepo"}`);
2250
2250
  console.log(`
2251
2251
  \uD83D\uDCE5 Cloning TanStack Start template...`);
2252
2252
  const cloneResult = run("npx", [
2253
2253
  "-y",
2254
2254
  "gitpick",
2255
2255
  "TanStack/router/tree/main/examples/react/start-basic",
2256
- projectName
2256
+ projectName2
2257
2257
  ]);
2258
2258
  if (!cloneResult.success) {
2259
2259
  console.error(`❌ Failed to clone template: ${cloneResult.stderr}`);
@@ -2261,7 +2261,7 @@ async function create(projectName, options = {}) {
2261
2261
  }
2262
2262
  console.log(` Template cloned`);
2263
2263
  let webPath;
2264
- if (options.flat) {
2264
+ if (options2.flat) {
2265
2265
  webPath = targetPath;
2266
2266
  } else {
2267
2267
  console.log(`
@@ -2278,7 +2278,7 @@ async function create(projectName, options = {}) {
2278
2278
  renameSync(tempDir, webDir);
2279
2279
  webPath = webDir;
2280
2280
  const rootPackageJson = {
2281
- name: projectName,
2281
+ name: projectName2,
2282
2282
  private: true,
2283
2283
  workspaces: ["web"],
2284
2284
  scripts: {
@@ -2324,12 +2324,12 @@ async function create(projectName, options = {}) {
2324
2324
  const pkgPath = join2(webPath, "package.json");
2325
2325
  if (existsSync3(pkgPath)) {
2326
2326
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
2327
- pkg.name = options.flat ? projectName : `${projectName}-web`;
2327
+ pkg.name = options2.flat ? projectName2 : `${projectName2}-web`;
2328
2328
  pkg.dependencies = pkg.dependencies || {};
2329
2329
  pkg.dependencies["agentation"] = "latest";
2330
2330
  writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2));
2331
2331
  }
2332
- if (options.convex) {
2332
+ if (options2.convex) {
2333
2333
  console.log(`
2334
2334
  \uD83D\uDD27 Setting up Convex...`);
2335
2335
  const addResult = run("bun", ["add", "convex", "convex-react"], { cwd: webPath });
@@ -2353,7 +2353,7 @@ async function create(projectName, options = {}) {
2353
2353
  console.log(` Created .env.local.example`);
2354
2354
  console.log(`
2355
2355
  ⚠️ To complete Convex setup:`);
2356
- console.log(` 1. cd ${options.flat ? projectName : projectName + "/web"}`);
2356
+ console.log(` 1. cd ${options2.flat ? projectName2 : projectName2 + "/web"}`);
2357
2357
  console.log(` 2. bunx convex dev (select/create project)`);
2358
2358
  console.log(` 3. Wrap your app with <ConvexClientProvider> in app/root.tsx`);
2359
2359
  }
@@ -2380,16 +2380,16 @@ async function create(projectName, options = {}) {
2380
2380
  console.log(` Git initialized`);
2381
2381
  console.log(`
2382
2382
  ${"-".repeat(50)}`);
2383
- console.log(`✅ Project "${projectName}" created!
2383
+ console.log(`✅ Project "${projectName2}" created!
2384
2384
  `);
2385
2385
  console.log(`\uD83D\uDCC1 Location: ${targetPath}`);
2386
2386
  console.log(`
2387
2387
  \uD83D\uDCDD Next steps:`);
2388
- console.log(` cd ${projectName}`);
2389
- if (!options.flat) {
2388
+ console.log(` cd ${projectName2}`);
2389
+ if (!options2.flat) {
2390
2390
  console.log(` cd web`);
2391
2391
  }
2392
- if (options.convex) {
2392
+ if (options2.convex) {
2393
2393
  console.log(` bunx convex dev # Setup Convex project`);
2394
2394
  }
2395
2395
  console.log(` bun dev # Start dev server`);
@@ -2399,7 +2399,7 @@ ${"-".repeat(50)}`);
2399
2399
  // src/commands/init.ts
2400
2400
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync3 } from "fs";
2401
2401
  import { resolve as resolve3 } from "path";
2402
- async function init(projectPath = ".", options = {}) {
2402
+ async function init(projectPath = ".", options2 = {}) {
2403
2403
  const fullPath = resolve3(projectPath);
2404
2404
  if (!existsSync4(fullPath)) {
2405
2405
  console.error(`❌ Path does not exist: ${fullPath}`);
@@ -2434,7 +2434,7 @@ async function init(projectPath = ".", options = {}) {
2434
2434
  console.log(` Added .zdev/ to .gitignore`);
2435
2435
  }
2436
2436
  }
2437
- if (options.seed) {
2437
+ if (options2.seed) {
2438
2438
  console.log(`
2439
2439
  \uD83D\uDCE6 Creating seed data...`);
2440
2440
  const convexDir = resolve3(fullPath, "convex");
@@ -2463,20 +2463,20 @@ Next steps:`);
2463
2463
  // src/commands/start.ts
2464
2464
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
2465
2465
  import { resolve as resolve4, basename as basename3, join as join3 } from "path";
2466
- function detectWebDir(worktreePath) {
2466
+ function detectWebDir(worktreePath2) {
2467
2467
  const commonDirs = ["web", "frontend", "app", "client", "packages/web", "apps/web"];
2468
2468
  for (const dir of commonDirs) {
2469
- const packagePath = join3(worktreePath, dir, "package.json");
2469
+ const packagePath = join3(worktreePath2, dir, "package.json");
2470
2470
  if (existsSync5(packagePath)) {
2471
2471
  return dir;
2472
2472
  }
2473
2473
  }
2474
- if (existsSync5(join3(worktreePath, "package.json"))) {
2474
+ if (existsSync5(join3(worktreePath2, "package.json"))) {
2475
2475
  return ".";
2476
2476
  }
2477
2477
  return "web";
2478
2478
  }
2479
- async function start(featureName, projectPath = ".", options = {}) {
2479
+ async function start(featureName, projectPath = ".", options2 = {}) {
2480
2480
  const fullPath = resolve4(projectPath);
2481
2481
  if (!existsSync5(fullPath)) {
2482
2482
  console.error(`❌ Path does not exist: ${fullPath}`);
@@ -2488,9 +2488,9 @@ async function start(featureName, projectPath = ".", options = {}) {
2488
2488
  }
2489
2489
  const repoName = getRepoName(fullPath);
2490
2490
  const worktreeName = `${repoName}-${featureName}`;
2491
- const worktreePath = getWorktreePath(worktreeName);
2491
+ const worktreePath2 = getWorktreePath(worktreeName);
2492
2492
  const branchName = `feature/${featureName}`;
2493
- let baseBranch = options.baseBranch;
2493
+ let baseBranch = options2.baseBranch;
2494
2494
  if (!baseBranch) {
2495
2495
  const candidates = ["origin/main", "origin/master", "main", "master"];
2496
2496
  for (const candidate of candidates) {
@@ -2508,8 +2508,8 @@ async function start(featureName, projectPath = ".", options = {}) {
2508
2508
  console.log(`\uD83D\uDC02 Starting feature: ${featureName}`);
2509
2509
  console.log(` Project: ${repoName}`);
2510
2510
  console.log(` Branch: ${branchName}`);
2511
- const config = loadConfig();
2512
- if (config.allocations[worktreeName]) {
2511
+ const config2 = loadConfig();
2512
+ if (config2.allocations[worktreeName]) {
2513
2513
  console.error(`
2514
2514
  ❌ Feature "${featureName}" already exists for ${repoName}`);
2515
2515
  console.log(` Run: zdev stop ${featureName} --project ${fullPath}`);
@@ -2525,25 +2525,25 @@ async function start(featureName, projectPath = ".", options = {}) {
2525
2525
  }
2526
2526
  console.log(`
2527
2527
  \uD83C\uDF33 Creating worktree...`);
2528
- if (existsSync5(worktreePath)) {
2529
- console.error(` Worktree path already exists: ${worktreePath}`);
2528
+ if (existsSync5(worktreePath2)) {
2529
+ console.error(` Worktree path already exists: ${worktreePath2}`);
2530
2530
  process.exit(1);
2531
2531
  }
2532
- const worktreeResult = createWorktree(fullPath, worktreePath, branchName, baseBranch);
2532
+ const worktreeResult = createWorktree(fullPath, worktreePath2, branchName, baseBranch);
2533
2533
  if (!worktreeResult.success) {
2534
2534
  console.error(` Failed to create worktree: ${worktreeResult.error}`);
2535
2535
  process.exit(1);
2536
2536
  }
2537
- console.log(` Created: ${worktreePath}`);
2538
- const webDir = options.webDir || detectWebDir(worktreePath);
2539
- const webPath = webDir === "." ? worktreePath : join3(worktreePath, webDir);
2537
+ console.log(` Created: ${worktreePath2}`);
2538
+ const webDir = options2.webDir || detectWebDir(worktreePath2);
2539
+ const webPath = webDir === "." ? worktreePath2 : join3(worktreePath2, webDir);
2540
2540
  console.log(`
2541
2541
  \uD83D\uDCC1 Web directory: ${webDir === "." ? "(root)" : webDir}`);
2542
- if (config.copyPatterns && config.copyPatterns.length > 0) {
2542
+ if (config2.copyPatterns && config2.copyPatterns.length > 0) {
2543
2543
  console.log(`
2544
2544
  \uD83D\uDCCB Copying config files...`);
2545
2545
  const mainWebPath = webDir === "." ? fullPath : join3(fullPath, webDir);
2546
- for (const pattern of config.copyPatterns) {
2546
+ for (const pattern of config2.copyPatterns) {
2547
2547
  const srcPath = join3(mainWebPath, pattern);
2548
2548
  const destPath = join3(webPath, pattern);
2549
2549
  if (existsSync5(srcPath) && !existsSync5(destPath)) {
@@ -2557,7 +2557,7 @@ async function start(featureName, projectPath = ".", options = {}) {
2557
2557
  }
2558
2558
  }
2559
2559
  }
2560
- const setupScriptPath = join3(worktreePath, ".zdev", "setup.sh");
2560
+ const setupScriptPath = join3(worktreePath2, ".zdev", "setup.sh");
2561
2561
  if (existsSync5(setupScriptPath)) {
2562
2562
  console.log(`
2563
2563
  \uD83D\uDCE6 Running setup script...`);
@@ -2572,9 +2572,9 @@ async function start(featureName, projectPath = ".", options = {}) {
2572
2572
  ⚠️ No .zdev/setup.sh found, skipping setup`);
2573
2573
  console.log(` Create one in your project to automate dependency installation`);
2574
2574
  }
2575
- const hasConvex = existsSync5(join3(webPath, "convex")) || existsSync5(join3(worktreePath, "convex"));
2575
+ const hasConvex = existsSync5(join3(webPath, "convex")) || existsSync5(join3(worktreePath2, "convex"));
2576
2576
  const seedPath = getSeedPath(repoName);
2577
- if (options.seed && hasConvex && existsSync5(seedPath)) {
2577
+ if (options2.seed && hasConvex && existsSync5(seedPath)) {
2578
2578
  console.log(`
2579
2579
  \uD83C\uDF31 Importing seed data...`);
2580
2580
  const seedResult = run("bunx", ["convex", "import", seedPath], {
@@ -2586,7 +2586,7 @@ async function start(featureName, projectPath = ".", options = {}) {
2586
2586
  console.error(` Failed to import seed: ${seedResult.stderr}`);
2587
2587
  }
2588
2588
  }
2589
- const ports = options.port ? { frontend: options.port, convex: hasConvex ? options.port + 100 : 0 } : allocatePorts(config, hasConvex);
2589
+ const ports = options2.port ? { frontend: options2.port, convex: hasConvex ? options2.port + 100 : 0 } : allocatePorts(config2, hasConvex);
2590
2590
  console.log(`
2591
2591
  \uD83D\uDD0C Allocated ports:`);
2592
2592
  console.log(` Frontend: ${ports.frontend}`);
@@ -2636,7 +2636,7 @@ async function start(featureName, projectPath = ".", options = {}) {
2636
2636
  console.log(` Frontend PID: ${frontendPid}`);
2637
2637
  let routePath = "";
2638
2638
  let publicUrl = "";
2639
- if (!options.local) {
2639
+ if (!options2.local) {
2640
2640
  const traefikStatus = getTraefikStatus();
2641
2641
  if (traefikStatus.running && traefikStatus.devDomain) {
2642
2642
  routePath = worktreeName;
@@ -2655,7 +2655,7 @@ async function start(featureName, projectPath = ".", options = {}) {
2655
2655
  console.log(` Run: zdev config --set devDomain=dev.yourdomain.com`);
2656
2656
  }
2657
2657
  }
2658
- const allocation = {
2658
+ const allocation2 = {
2659
2659
  project: repoName,
2660
2660
  projectPath: fullPath,
2661
2661
  branch: branchName,
@@ -2663,7 +2663,7 @@ async function start(featureName, projectPath = ".", options = {}) {
2663
2663
  frontendPort: ports.frontend,
2664
2664
  convexPort: ports.convex,
2665
2665
  funnelPath: routePath,
2666
- worktreePath,
2666
+ worktreePath: worktreePath2,
2667
2667
  publicUrl: publicUrl || undefined,
2668
2668
  pids: {
2669
2669
  frontend: frontendPid,
@@ -2671,104 +2671,104 @@ async function start(featureName, projectPath = ".", options = {}) {
2671
2671
  },
2672
2672
  started: new Date().toISOString()
2673
2673
  };
2674
- config.allocations[worktreeName] = allocation;
2675
- saveConfig(config);
2674
+ config2.allocations[worktreeName] = allocation2;
2675
+ saveConfig(config2);
2676
2676
  console.log(`
2677
2677
  ${"-".repeat(50)}`);
2678
2678
  console.log(`✅ Feature "${featureName}" is ready!
2679
2679
  `);
2680
- console.log(`\uD83D\uDCC1 Worktree: ${worktreePath}`);
2680
+ console.log(`\uD83D\uDCC1 Worktree: ${worktreePath2}`);
2681
2681
  console.log(`\uD83C\uDF10 Local: http://localhost:${ports.frontend}`);
2682
2682
  if (publicUrl) {
2683
2683
  console.log(`\uD83D\uDD17 Public: ${publicUrl}`);
2684
2684
  }
2685
2685
  console.log(`
2686
2686
  \uD83D\uDCDD Commands:`);
2687
- console.log(` cd ${worktreePath}`);
2687
+ console.log(` cd ${worktreePath2}`);
2688
2688
  console.log(` zdev stop ${featureName} --project ${fullPath}`);
2689
2689
  console.log(`${"-".repeat(50)}`);
2690
2690
  }
2691
2691
 
2692
2692
  // src/commands/stop.ts
2693
2693
  import { resolve as resolve5 } from "path";
2694
- async function stop(featureName, options = {}) {
2695
- const config = loadConfig();
2694
+ async function stop(featureName, options2 = {}) {
2695
+ const config2 = loadConfig();
2696
2696
  let worktreeName;
2697
- let allocation;
2698
- if (options.project) {
2699
- const projectPath = resolve5(options.project);
2700
- const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options.project;
2697
+ let allocation2;
2698
+ if (options2.project) {
2699
+ const projectPath = resolve5(options2.project);
2700
+ const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options2.project;
2701
2701
  worktreeName = `${repoName}-${featureName}`;
2702
- allocation = config.allocations[worktreeName];
2702
+ allocation2 = config2.allocations[worktreeName];
2703
2703
  } else {
2704
- for (const [name, alloc] of Object.entries(config.allocations)) {
2704
+ for (const [name, alloc] of Object.entries(config2.allocations)) {
2705
2705
  if (name.endsWith(`-${featureName}`)) {
2706
2706
  worktreeName = name;
2707
- allocation = alloc;
2707
+ allocation2 = alloc;
2708
2708
  break;
2709
2709
  }
2710
2710
  }
2711
2711
  }
2712
- if (!worktreeName || !allocation) {
2712
+ if (!worktreeName || !allocation2) {
2713
2713
  console.error(`❌ Feature "${featureName}" not found`);
2714
2714
  console.log(`
2715
2715
  Run 'zdev list' to see active features`);
2716
2716
  process.exit(1);
2717
2717
  }
2718
2718
  console.log(`\uD83D\uDC02 Stopping feature: ${featureName}`);
2719
- console.log(` Project: ${allocation.project}`);
2720
- if (allocation.pids.frontend && isProcessRunning(allocation.pids.frontend)) {
2719
+ console.log(` Project: ${allocation2.project}`);
2720
+ if (allocation2.pids.frontend && isProcessRunning(allocation2.pids.frontend)) {
2721
2721
  console.log(`
2722
- \uD83D\uDED1 Stopping frontend (PID: ${allocation.pids.frontend})...`);
2723
- if (killProcess(allocation.pids.frontend)) {
2722
+ \uD83D\uDED1 Stopping frontend (PID: ${allocation2.pids.frontend})...`);
2723
+ if (killProcess(allocation2.pids.frontend)) {
2724
2724
  console.log(` Frontend stopped`);
2725
2725
  } else {
2726
2726
  console.error(` Failed to stop frontend`);
2727
2727
  }
2728
2728
  }
2729
- if (allocation.pids.convex && isProcessRunning(allocation.pids.convex)) {
2729
+ if (allocation2.pids.convex && isProcessRunning(allocation2.pids.convex)) {
2730
2730
  console.log(`
2731
- \uD83D\uDED1 Stopping Convex (PID: ${allocation.pids.convex})...`);
2732
- if (killProcess(allocation.pids.convex)) {
2731
+ \uD83D\uDED1 Stopping Convex (PID: ${allocation2.pids.convex})...`);
2732
+ if (killProcess(allocation2.pids.convex)) {
2733
2733
  console.log(` Convex stopped`);
2734
2734
  } else {
2735
2735
  console.error(` Failed to stop Convex`);
2736
2736
  }
2737
2737
  }
2738
- if (allocation.funnelPath) {
2738
+ if (allocation2.funnelPath) {
2739
2739
  console.log(`
2740
2740
  \uD83D\uDD17 Removing Traefik route...`);
2741
- if (traefikRemoveRoute(allocation.funnelPath)) {
2741
+ if (traefikRemoveRoute(allocation2.funnelPath)) {
2742
2742
  console.log(` Route removed`);
2743
2743
  } else {
2744
2744
  console.error(` Failed to remove route (may already be removed)`);
2745
2745
  }
2746
2746
  }
2747
- delete config.allocations[worktreeName];
2748
- saveConfig(config);
2749
- const worktreePath = getWorktreePath(worktreeName);
2750
- if (options.keep) {
2747
+ delete config2.allocations[worktreeName];
2748
+ saveConfig(config2);
2749
+ const worktreePath2 = getWorktreePath(worktreeName);
2750
+ if (options2.keep) {
2751
2751
  console.log(`
2752
2752
  ✅ Feature "${featureName}" stopped (worktree kept)`);
2753
- console.log(` Worktree: ${worktreePath}`);
2753
+ console.log(` Worktree: ${worktreePath2}`);
2754
2754
  console.log(`
2755
2755
  To remove worktree: zdev clean ${featureName}`);
2756
2756
  } else {
2757
2757
  console.log(`
2758
2758
  ✅ Feature "${featureName}" stopped`);
2759
2759
  console.log(`
2760
- Worktree still exists at: ${worktreePath}`);
2761
- console.log(` To remove: zdev clean ${featureName} --project ${allocation.projectPath}`);
2760
+ Worktree still exists at: ${worktreePath2}`);
2761
+ console.log(` To remove: zdev clean ${featureName} --project ${allocation2.projectPath}`);
2762
2762
  }
2763
2763
  }
2764
2764
 
2765
2765
  // src/commands/list.ts
2766
2766
  import { existsSync as existsSync6 } from "fs";
2767
- async function list(options = {}) {
2768
- const config = loadConfig();
2769
- const allocations = Object.entries(config.allocations);
2770
- if (options.json) {
2771
- console.log(JSON.stringify(config, null, 2));
2767
+ async function list(options2 = {}) {
2768
+ const config2 = loadConfig();
2769
+ const allocations = Object.entries(config2.allocations);
2770
+ if (options2.json) {
2771
+ console.log(JSON.stringify(config2, null, 2));
2772
2772
  return;
2773
2773
  }
2774
2774
  console.log(`\uD83D\uDC02 zdev Status
@@ -2794,15 +2794,15 @@ No active features.
2794
2794
  \uD83D\uDCCB Active Features (${allocations.length}):
2795
2795
  `);
2796
2796
  for (const [name, alloc] of allocations) {
2797
- const worktreePath = getWorktreePath(name);
2798
- const worktreeExists = existsSync6(worktreePath);
2797
+ const worktreePath2 = getWorktreePath(name);
2798
+ const worktreeExists = existsSync6(worktreePath2);
2799
2799
  const frontendRunning = alloc.pids.frontend ? isProcessRunning(alloc.pids.frontend) : false;
2800
2800
  const convexRunning = alloc.pids.convex ? isProcessRunning(alloc.pids.convex) : false;
2801
2801
  const statusEmoji = frontendRunning && convexRunning ? "\uD83D\uDFE2" : frontendRunning || convexRunning ? "\uD83D\uDFE1" : "\uD83D\uDD34";
2802
2802
  console.log(`${statusEmoji} ${name}`);
2803
2803
  console.log(` Project: ${alloc.project}`);
2804
2804
  console.log(` Branch: ${alloc.branch}`);
2805
- console.log(` Path: ${worktreePath} ${worktreeExists ? "" : "(missing)"}`);
2805
+ console.log(` Path: ${worktreePath2} ${worktreeExists ? "" : "(missing)"}`);
2806
2806
  console.log(` Local: http://localhost:${alloc.frontendPort}`);
2807
2807
  if (alloc.funnelPath && traefikStatus.devDomain) {
2808
2808
  console.log(` Public: https://${alloc.funnelPath}.${traefikStatus.devDomain}`);
@@ -2822,27 +2822,27 @@ Commands:`);
2822
2822
  // src/commands/clean.ts
2823
2823
  import { existsSync as existsSync7, rmSync as rmSync2 } from "fs";
2824
2824
  import { resolve as resolve6 } from "path";
2825
- async function clean(featureName, options = {}) {
2826
- const config = loadConfig();
2825
+ async function clean(featureName, options2 = {}) {
2826
+ const config2 = loadConfig();
2827
2827
  let worktreeName;
2828
- let allocation;
2828
+ let allocation2;
2829
2829
  let projectPath;
2830
- if (options.project) {
2831
- projectPath = resolve6(options.project);
2832
- const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options.project;
2830
+ if (options2.project) {
2831
+ projectPath = resolve6(options2.project);
2832
+ const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options2.project;
2833
2833
  worktreeName = `${repoName}-${featureName}`;
2834
- allocation = config.allocations[worktreeName];
2834
+ allocation2 = config2.allocations[worktreeName];
2835
2835
  } else {
2836
- for (const [name, alloc] of Object.entries(config.allocations)) {
2836
+ for (const [name, alloc] of Object.entries(config2.allocations)) {
2837
2837
  if (name.endsWith(`-${featureName}`)) {
2838
2838
  worktreeName = name;
2839
- allocation = alloc;
2839
+ allocation2 = alloc;
2840
2840
  projectPath = alloc.projectPath;
2841
2841
  break;
2842
2842
  }
2843
2843
  }
2844
2844
  if (!worktreeName) {
2845
- const entries = Object.keys(config.allocations);
2845
+ const entries = Object.keys(config2.allocations);
2846
2846
  console.error(`❌ Feature "${featureName}" not found in active allocations`);
2847
2847
  if (entries.length > 0) {
2848
2848
  console.log(`
@@ -2852,41 +2852,41 @@ Active features:`);
2852
2852
  process.exit(1);
2853
2853
  }
2854
2854
  }
2855
- const worktreePath = getWorktreePath(worktreeName);
2855
+ const worktreePath2 = getWorktreePath(worktreeName);
2856
2856
  console.log(`\uD83D\uDC02 Cleaning feature: ${featureName}`);
2857
- if (allocation) {
2858
- if (allocation.pids.frontend && isProcessRunning(allocation.pids.frontend)) {
2857
+ if (allocation2) {
2858
+ if (allocation2.pids.frontend && isProcessRunning(allocation2.pids.frontend)) {
2859
2859
  console.log(`
2860
2860
  \uD83D\uDED1 Stopping frontend...`);
2861
- killProcess(allocation.pids.frontend);
2861
+ killProcess(allocation2.pids.frontend);
2862
2862
  }
2863
- if (allocation.pids.convex && isProcessRunning(allocation.pids.convex)) {
2863
+ if (allocation2.pids.convex && isProcessRunning(allocation2.pids.convex)) {
2864
2864
  console.log(`\uD83D\uDED1 Stopping Convex...`);
2865
- killProcess(allocation.pids.convex);
2865
+ killProcess(allocation2.pids.convex);
2866
2866
  }
2867
- if (allocation.funnelPath) {
2867
+ if (allocation2.funnelPath) {
2868
2868
  console.log(`\uD83D\uDD17 Removing Traefik route...`);
2869
- traefikRemoveRoute(allocation.funnelPath);
2869
+ traefikRemoveRoute(allocation2.funnelPath);
2870
2870
  }
2871
- projectPath = allocation.projectPath;
2871
+ projectPath = allocation2.projectPath;
2872
2872
  }
2873
- if (existsSync7(worktreePath)) {
2873
+ if (existsSync7(worktreePath2)) {
2874
2874
  console.log(`
2875
2875
  \uD83D\uDDD1️ Removing worktree...`);
2876
2876
  if (projectPath && isGitRepo(projectPath)) {
2877
- const result = removeWorktree(projectPath, worktreePath);
2877
+ const result = removeWorktree(projectPath, worktreePath2);
2878
2878
  if (!result.success) {
2879
- if (options.force) {
2879
+ if (options2.force) {
2880
2880
  console.log(` Git worktree remove failed, force removing directory...`);
2881
- rmSync2(worktreePath, { recursive: true, force: true });
2881
+ rmSync2(worktreePath2, { recursive: true, force: true });
2882
2882
  } else {
2883
2883
  console.error(` Failed to remove worktree: ${result.error}`);
2884
2884
  console.log(` Use --force to force remove`);
2885
2885
  process.exit(1);
2886
2886
  }
2887
2887
  }
2888
- } else if (options.force) {
2889
- rmSync2(worktreePath, { recursive: true, force: true });
2888
+ } else if (options2.force) {
2889
+ rmSync2(worktreePath2, { recursive: true, force: true });
2890
2890
  } else {
2891
2891
  console.error(` Cannot remove worktree: project path unknown`);
2892
2892
  console.log(` Use --force to force remove, or specify --project`);
@@ -2897,9 +2897,9 @@ Active features:`);
2897
2897
  console.log(`
2898
2898
  Worktree already removed`);
2899
2899
  }
2900
- if (worktreeName && config.allocations[worktreeName]) {
2901
- delete config.allocations[worktreeName];
2902
- saveConfig(config);
2900
+ if (worktreeName && config2.allocations[worktreeName]) {
2901
+ delete config2.allocations[worktreeName];
2902
+ saveConfig(config2);
2903
2903
  }
2904
2904
  console.log(`
2905
2905
  ✅ Feature "${featureName}" cleaned up`);
@@ -2908,7 +2908,7 @@ Active features:`);
2908
2908
  // src/commands/seed.ts
2909
2909
  import { existsSync as existsSync8 } from "fs";
2910
2910
  import { resolve as resolve7 } from "path";
2911
- async function seedExport(projectPath = ".", options = {}) {
2911
+ async function seedExport(projectPath = ".", options2 = {}) {
2912
2912
  const fullPath = resolve7(projectPath);
2913
2913
  if (!existsSync8(fullPath)) {
2914
2914
  console.error(`❌ Path does not exist: ${fullPath}`);
@@ -2935,7 +2935,7 @@ async function seedExport(projectPath = ".", options = {}) {
2935
2935
  process.exit(1);
2936
2936
  }
2937
2937
  }
2938
- async function seedImport(projectPath = ".", options = {}) {
2938
+ async function seedImport(projectPath = ".", options2 = {}) {
2939
2939
  const fullPath = resolve7(projectPath);
2940
2940
  if (!existsSync8(fullPath)) {
2941
2941
  console.error(`❌ Path does not exist: ${fullPath}`);
@@ -2945,8 +2945,8 @@ async function seedImport(projectPath = ".", options = {}) {
2945
2945
  const projectConfigPath = resolve7(fullPath, ".zdev", "project.json");
2946
2946
  if (existsSync8(projectConfigPath)) {
2947
2947
  try {
2948
- const config = JSON.parse(await Bun.file(projectConfigPath).text());
2949
- repoName = config.name;
2948
+ const config2 = JSON.parse(await Bun.file(projectConfigPath).text());
2949
+ repoName = config2.name;
2950
2950
  } catch {
2951
2951
  repoName = getRepoName(fullPath);
2952
2952
  }
@@ -2981,10 +2981,10 @@ async function seedImport(projectPath = ".", options = {}) {
2981
2981
  }
2982
2982
 
2983
2983
  // src/commands/config.ts
2984
- async function configCmd(options = {}) {
2985
- const config = loadConfig();
2986
- if (options.set) {
2987
- const [key, ...valueParts] = options.set.split("=");
2984
+ async function configCmd(options2 = {}) {
2985
+ const config2 = loadConfig();
2986
+ if (options2.set) {
2987
+ const [key, ...valueParts] = options2.set.split("=");
2988
2988
  const value = valueParts.join("=");
2989
2989
  if (!value) {
2990
2990
  console.error(`Usage: zdev config --set key=value`);
@@ -2996,35 +2996,35 @@ Configurable keys:`);
2996
2996
  return;
2997
2997
  }
2998
2998
  if (key === "devDomain") {
2999
- config.devDomain = value;
3000
- saveConfig(config);
2999
+ config2.devDomain = value;
3000
+ saveConfig(config2);
3001
3001
  console.log(`✅ Set devDomain = ${value}`);
3002
3002
  } else if (key === "dockerHostIp") {
3003
- config.dockerHostIp = value;
3004
- saveConfig(config);
3003
+ config2.dockerHostIp = value;
3004
+ saveConfig(config2);
3005
3005
  console.log(`✅ Set dockerHostIp = ${value}`);
3006
3006
  } else if (key === "traefikConfigDir") {
3007
- config.traefikConfigDir = value;
3008
- saveConfig(config);
3007
+ config2.traefikConfigDir = value;
3008
+ saveConfig(config2);
3009
3009
  console.log(`✅ Set traefikConfigDir = ${value}`);
3010
3010
  } else {
3011
3011
  console.error(`Unknown config key: ${key}`);
3012
3012
  }
3013
3013
  return;
3014
3014
  }
3015
- if (options.list || !options.add && !options.remove) {
3015
+ if (options2.list || !options2.add && !options2.remove) {
3016
3016
  console.log(`\uD83D\uDC02 zdev Configuration
3017
3017
  `);
3018
3018
  console.log(`\uD83D\uDCC1 Config file: ${CONFIG_PATH}`);
3019
3019
  console.log(`
3020
3020
  \uD83C\uDF10 Traefik / Public URLs:`);
3021
- console.log(` Dev domain: ${config.devDomain}`);
3022
- console.log(` Docker host IP: ${config.dockerHostIp}`);
3023
- console.log(` Config dir: ${config.traefikConfigDir}`);
3021
+ console.log(` Dev domain: ${config2.devDomain}`);
3022
+ console.log(` Docker host IP: ${config2.dockerHostIp}`);
3023
+ console.log(` Config dir: ${config2.traefikConfigDir}`);
3024
3024
  console.log(`
3025
3025
  \uD83D\uDCCB Copy patterns (files auto-copied to worktrees):`);
3026
- if (config.copyPatterns && config.copyPatterns.length > 0) {
3027
- for (const pattern of config.copyPatterns) {
3026
+ if (config2.copyPatterns && config2.copyPatterns.length > 0) {
3027
+ for (const pattern of config2.copyPatterns) {
3028
3028
  console.log(` - ${pattern}`);
3029
3029
  }
3030
3030
  } else {
@@ -3032,8 +3032,8 @@ Configurable keys:`);
3032
3032
  }
3033
3033
  console.log(`
3034
3034
  \uD83D\uDD0C Port allocation:`);
3035
- console.log(` Next frontend port: ${config.nextFrontendPort}`);
3036
- console.log(` Next Convex port: ${config.nextConvexPort}`);
3035
+ console.log(` Next frontend port: ${config2.nextFrontendPort}`);
3036
+ console.log(` Next Convex port: ${config2.nextConvexPort}`);
3037
3037
  console.log(`
3038
3038
  Commands:`);
3039
3039
  console.log(` zdev config --set devDomain=dev.example.com`);
@@ -3041,31 +3041,31 @@ Commands:`);
3041
3041
  console.log(` zdev config --remove ".env.local"`);
3042
3042
  return;
3043
3043
  }
3044
- if (options.add) {
3045
- if (!config.copyPatterns) {
3046
- config.copyPatterns = [];
3044
+ if (options2.add) {
3045
+ if (!config2.copyPatterns) {
3046
+ config2.copyPatterns = [];
3047
3047
  }
3048
- if (config.copyPatterns.includes(options.add)) {
3049
- console.log(`Pattern "${options.add}" already exists`);
3048
+ if (config2.copyPatterns.includes(options2.add)) {
3049
+ console.log(`Pattern "${options2.add}" already exists`);
3050
3050
  } else {
3051
- config.copyPatterns.push(options.add);
3052
- saveConfig(config);
3053
- console.log(`✅ Added copy pattern: ${options.add}`);
3051
+ config2.copyPatterns.push(options2.add);
3052
+ saveConfig(config2);
3053
+ console.log(`✅ Added copy pattern: ${options2.add}`);
3054
3054
  }
3055
3055
  return;
3056
3056
  }
3057
- if (options.remove) {
3058
- if (!config.copyPatterns) {
3059
- console.log(`Pattern "${options.remove}" not found`);
3057
+ if (options2.remove) {
3058
+ if (!config2.copyPatterns) {
3059
+ console.log(`Pattern "${options2.remove}" not found`);
3060
3060
  return;
3061
3061
  }
3062
- const index = config.copyPatterns.indexOf(options.remove);
3062
+ const index = config2.copyPatterns.indexOf(options2.remove);
3063
3063
  if (index === -1) {
3064
- console.log(`Pattern "${options.remove}" not found`);
3064
+ console.log(`Pattern "${options2.remove}" not found`);
3065
3065
  } else {
3066
- config.copyPatterns.splice(index, 1);
3067
- saveConfig(config);
3068
- console.log(`✅ Removed copy pattern: ${options.remove}`);
3066
+ config2.copyPatterns.splice(index, 1);
3067
+ saveConfig(config2);
3068
+ console.log(`✅ Removed copy pattern: ${options2.remove}`);
3069
3069
  }
3070
3070
  return;
3071
3071
  }
@@ -3073,48 +3073,48 @@ Commands:`);
3073
3073
 
3074
3074
  // src/commands/pr.ts
3075
3075
  import { resolve as resolve8, basename as basename4 } from "path";
3076
- async function pr(featureName, projectPath = ".", options = {}) {
3076
+ async function pr(featureName, projectPath = ".", options2 = {}) {
3077
3077
  const fullPath = resolve8(projectPath);
3078
- let worktreePath = fullPath;
3079
- let allocation;
3080
- let projectName = getRepoName(fullPath) || basename4(fullPath);
3081
- const config = loadConfig();
3078
+ let worktreePath2 = fullPath;
3079
+ let allocation2;
3080
+ let projectName2 = getRepoName(fullPath) || basename4(fullPath);
3081
+ const config2 = loadConfig();
3082
3082
  if (featureName) {
3083
- const allocKey = `${projectName}-${featureName}`;
3084
- allocation = config.allocations[allocKey];
3085
- if (!allocation) {
3086
- const found = Object.entries(config.allocations).find(([key, alloc]) => key.endsWith(`-${featureName}`));
3083
+ const allocKey = `${projectName2}-${featureName}`;
3084
+ allocation2 = config2.allocations[allocKey];
3085
+ if (!allocation2) {
3086
+ const found = Object.entries(config2.allocations).find(([key, alloc]) => key.endsWith(`-${featureName}`));
3087
3087
  if (found) {
3088
- allocation = found[1];
3089
- projectName = allocation.project;
3088
+ allocation2 = found[1];
3089
+ projectName2 = allocation2.project;
3090
3090
  }
3091
3091
  }
3092
- if (allocation) {
3093
- worktreePath = allocation.worktreePath || resolve8(config.worktreesDir, `${projectName}-${featureName}`);
3092
+ if (allocation2) {
3093
+ worktreePath2 = allocation2.worktreePath || resolve8(config2.worktreesDir, `${projectName2}-${featureName}`);
3094
3094
  }
3095
3095
  } else {
3096
3096
  const cwd = process.cwd();
3097
- const found = Object.entries(config.allocations).find(([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || ""));
3097
+ const found = Object.entries(config2.allocations).find(([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || ""));
3098
3098
  if (found) {
3099
- allocation = found[1];
3099
+ allocation2 = found[1];
3100
3100
  featureName = found[0].split("-").slice(1).join("-");
3101
- projectName = allocation.project;
3102
- worktreePath = allocation.worktreePath || cwd;
3101
+ projectName2 = allocation2.project;
3102
+ worktreePath2 = allocation2.worktreePath || cwd;
3103
3103
  }
3104
3104
  }
3105
- if (!isGitRepo(worktreePath)) {
3106
- console.error(`❌ Not a git repository: ${worktreePath}`);
3105
+ if (!isGitRepo(worktreePath2)) {
3106
+ console.error(`❌ Not a git repository: ${worktreePath2}`);
3107
3107
  process.exit(1);
3108
3108
  }
3109
- const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath });
3109
+ const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath2 });
3110
3110
  if (!branchResult.success || !branchResult.stdout.trim()) {
3111
3111
  console.error("❌ Could not determine current branch");
3112
3112
  process.exit(1);
3113
3113
  }
3114
- const branch = branchResult.stdout.trim();
3115
- console.log(`\uD83D\uDC02 Creating PR for: ${branch}`);
3116
- if (allocation) {
3117
- console.log(` Project: ${projectName}`);
3114
+ const branch2 = branchResult.stdout.trim();
3115
+ console.log(`\uD83D\uDC02 Creating PR for: ${branch2}`);
3116
+ if (allocation2) {
3117
+ console.log(` Project: ${projectName2}`);
3118
3118
  console.log(` Feature: ${featureName}`);
3119
3119
  }
3120
3120
  const ghCheck = run("which", ["gh"]);
@@ -3122,14 +3122,14 @@ async function pr(featureName, projectPath = ".", options = {}) {
3122
3122
  console.error("❌ GitHub CLI (gh) not found. Install: https://cli.github.com");
3123
3123
  process.exit(1);
3124
3124
  }
3125
- const authCheck = run("gh", ["auth", "status"], { cwd: worktreePath });
3125
+ const authCheck = run("gh", ["auth", "status"], { cwd: worktreePath2 });
3126
3126
  if (!authCheck.success) {
3127
3127
  console.error("❌ Not authenticated with GitHub. Run: gh auth login");
3128
3128
  process.exit(1);
3129
3129
  }
3130
3130
  console.log(`
3131
3131
  \uD83D\uDCE4 Pushing branch...`);
3132
- const pushResult = run("git", ["push", "-u", "origin", branch], { cwd: worktreePath });
3132
+ const pushResult = run("git", ["push", "-u", "origin", branch2], { cwd: worktreePath2 });
3133
3133
  if (!pushResult.success) {
3134
3134
  if (!pushResult.stderr.includes("Everything up-to-date")) {
3135
3135
  console.error(` Failed to push: ${pushResult.stderr}`);
@@ -3137,26 +3137,77 @@ async function pr(featureName, projectPath = ".", options = {}) {
3137
3137
  }
3138
3138
  console.log(` Already up to date`);
3139
3139
  } else {
3140
- console.log(` Pushed to origin/${branch}`);
3140
+ console.log(` Pushed to origin/${branch2}`);
3141
3141
  }
3142
- const defaultBranch = run("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], { cwd: worktreePath });
3142
+ const defaultBranch = run("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], { cwd: worktreePath2 });
3143
3143
  const baseBranch = defaultBranch.success ? defaultBranch.stdout.trim().replace("origin/", "") : "main";
3144
- const commitsResult = run("git", ["log", `origin/${baseBranch}..HEAD`, "--pretty=format:%s"], { cwd: worktreePath });
3144
+ const commitsResult = run("git", ["log", `origin/${baseBranch}..HEAD`, "--pretty=format:%s"], { cwd: worktreePath2 });
3145
3145
  const commits = commitsResult.success ? commitsResult.stdout.trim().split(`
3146
3146
  `).filter(Boolean) : [];
3147
- const filesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--stat", "--stat-width=60"], { cwd: worktreePath });
3147
+ const filesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--stat", "--stat-width=60"], { cwd: worktreePath2 });
3148
3148
  const filesSummary = filesResult.success ? filesResult.stdout.trim() : "";
3149
- const diffStatResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--shortstat"], { cwd: worktreePath });
3150
- const diffStat = diffStatResult.success ? diffStatResult.stdout.trim() : "";
3151
- let title = options.title;
3152
- if (!title) {
3153
- if (commits.length > 0) {
3154
- title = commits[0];
3155
- } else {
3156
- const featureForTitle = featureName || branch.replace(/^feature\//, "");
3157
- title = featureForTitle.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3158
- }
3149
+ const diffStatResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--shortstat"], { cwd: worktreePath2 });
3150
+ const diffStat2 = diffStatResult.success ? diffStatResult.stdout.trim() : "";
3151
+ const changedFilesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--name-only"], { cwd: worktreePath2 });
3152
+ const changedFiles = changedFilesResult.success ? changedFilesResult.stdout.trim().split(`
3153
+ `).filter(Boolean) : [];
3154
+ let title2 = options2.title;
3155
+ if (!title2) {
3156
+ title2 = generateSmartTitle(changedFiles, commits, featureName || branch2.replace(/^feature\//, ""));
3159
3157
  }
3158
+ }
3159
+ function generateSmartTitle(files, commits, featureName) {
3160
+ const components = new Set;
3161
+ const areas = new Set;
3162
+ for (const file of files) {
3163
+ if (!file.match(/\.(tsx?|jsx?|css|scss)$/))
3164
+ continue;
3165
+ const componentMatch = file.match(/components\/([^/]+)\/([^/]+)\.(tsx?|jsx?)$/);
3166
+ if (componentMatch) {
3167
+ components.add(componentMatch[2].replace(/\.(tsx?|jsx?)$/, ""));
3168
+ continue;
3169
+ }
3170
+ const singleComponent = file.match(/components\/([^/]+)\.(tsx?|jsx?)$/);
3171
+ if (singleComponent) {
3172
+ components.add(singleComponent[1]);
3173
+ continue;
3174
+ }
3175
+ const routeMatch = file.match(/routes\/(.+)\.(tsx?|jsx?)$/);
3176
+ if (routeMatch) {
3177
+ const routeName = routeMatch[1].replace(/[[\]$_.]/g, " ").trim();
3178
+ if (routeName && routeName !== "index") {
3179
+ areas.add(routeName);
3180
+ }
3181
+ continue;
3182
+ }
3183
+ const pathParts = file.split("/");
3184
+ if (pathParts.length > 1) {
3185
+ const folder = pathParts[pathParts.length - 2];
3186
+ if (!["src", "web", "app", "lib", "utils"].includes(folder)) {
3187
+ areas.add(folder);
3188
+ }
3189
+ }
3190
+ }
3191
+ const items = [...components, ...areas].slice(0, 3);
3192
+ if (items.length > 0) {
3193
+ let action = "Update";
3194
+ const commitText = commits.join(" ").toLowerCase();
3195
+ if (commitText.includes("fix"))
3196
+ action = "Fix";
3197
+ else if (commitText.includes("add") || commitText.includes("new"))
3198
+ action = "Add";
3199
+ else if (commitText.includes("refactor"))
3200
+ action = "Refactor";
3201
+ else if (commitText.includes("improve") || commitText.includes("enhance"))
3202
+ action = "Improve";
3203
+ else if (commitText.includes("mobile") || commitText.includes("responsive"))
3204
+ action = "Improve";
3205
+ return `${action} ${items.join(", ")}`;
3206
+ }
3207
+ if (commits.length > 0 && commits[0].length < 72) {
3208
+ return commits[0];
3209
+ }
3210
+ return featureName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3160
3211
  let body = options.body || "";
3161
3212
  if (allocation && allocation.publicUrl) {
3162
3213
  body += `## Preview
@@ -3257,32 +3308,32 @@ var __dirname2 = dirname(fileURLToPath(import.meta.url));
3257
3308
  var pkg = JSON.parse(readFileSync5(join4(__dirname2, "..", "package.json"), "utf-8"));
3258
3309
  var program2 = new Command;
3259
3310
  program2.name("zdev").description(`\uD83D\uDC02 zdev v${pkg.version} - Multi-agent worktree development environment`).version(pkg.version);
3260
- program2.command("create <name>").description("Create a new TanStack Start project").option("--convex", "Add Convex backend integration").option("--flat", "Flat structure (no monorepo)").action(async (name, options) => {
3311
+ program2.command("create <name>").description("Create a new TanStack Start project").option("--convex", "Add Convex backend integration").option("--flat", "Flat structure (no monorepo)").action(async (name, options2) => {
3261
3312
  await create(name, {
3262
- convex: options.convex,
3263
- flat: options.flat
3313
+ convex: options2.convex,
3314
+ flat: options2.flat
3264
3315
  });
3265
3316
  });
3266
- program2.command("init [path]").description("Initialize zdev for a project").option("-s, --seed", "Create initial seed data from current Convex state").action(async (path, options) => {
3267
- await init(path, options);
3317
+ program2.command("init [path]").description("Initialize zdev for a project").option("-s, --seed", "Create initial seed data from current Convex state").action(async (path, options2) => {
3318
+ await init(path, options2);
3268
3319
  });
3269
- program2.command("start <feature>").description("Start working on a feature (creates worktree, starts servers)").option("-p, --project <path>", "Project path (default: current directory)", ".").option("--port <number>", "Frontend port (auto-allocated if not specified)", parseInt).option("--local", "Local only - skip public URL setup via Traefik").option("-s, --seed", "Import seed data into the new worktree").option("-b, --base-branch <branch>", "Base branch to create from (auto-detected if not specified)").option("-w, --web-dir <dir>", "Subdirectory containing package.json (auto-detected if not specified)").action(async (feature, options) => {
3270
- await start(feature, options.project, {
3271
- port: options.port,
3272
- local: options.local,
3273
- seed: options.seed,
3274
- baseBranch: options.baseBranch,
3275
- webDir: options.webDir
3320
+ program2.command("start <feature>").description("Start working on a feature (creates worktree, starts servers)").option("-p, --project <path>", "Project path (default: current directory)", ".").option("--port <number>", "Frontend port (auto-allocated if not specified)", parseInt).option("--local", "Local only - skip public URL setup via Traefik").option("-s, --seed", "Import seed data into the new worktree").option("-b, --base-branch <branch>", "Base branch to create from (auto-detected if not specified)").option("-w, --web-dir <dir>", "Subdirectory containing package.json (auto-detected if not specified)").action(async (feature, options2) => {
3321
+ await start(feature, options2.project, {
3322
+ port: options2.port,
3323
+ local: options2.local,
3324
+ seed: options2.seed,
3325
+ baseBranch: options2.baseBranch,
3326
+ webDir: options2.webDir
3276
3327
  });
3277
3328
  });
3278
- program2.command("stop <feature>").description("Stop servers for a feature").option("-p, --project <path>", "Project path (to disambiguate features)").option("-k, --keep", "Keep worktree, just stop servers").action(async (feature, options) => {
3279
- await stop(feature, options);
3329
+ program2.command("stop <feature>").description("Stop servers for a feature").option("-p, --project <path>", "Project path (to disambiguate features)").option("-k, --keep", "Keep worktree, just stop servers").action(async (feature, options2) => {
3330
+ await stop(feature, options2);
3280
3331
  });
3281
- program2.command("list").description("List active features and their status").option("--json", "Output as JSON").action(async (options) => {
3282
- await list(options);
3332
+ program2.command("list").description("List active features and their status").option("--json", "Output as JSON").action(async (options2) => {
3333
+ await list(options2);
3283
3334
  });
3284
- program2.command("clean <feature>").description("Remove a feature worktree (use after PR is merged)").option("-p, --project <path>", "Project path").option("-f, --force", "Force remove even if git worktree fails").action(async (feature, options) => {
3285
- await clean(feature, options);
3335
+ program2.command("clean <feature>").description("Remove a feature worktree (use after PR is merged)").option("-p, --project <path>", "Project path").option("-f, --force", "Force remove even if git worktree fails").action(async (feature, options2) => {
3336
+ await clean(feature, options2);
3286
3337
  });
3287
3338
  var seedCmd = program2.command("seed").description("Manage seed data for projects");
3288
3339
  seedCmd.command("export [path]").description("Export current Convex data as seed").action(async (path) => {
@@ -3291,15 +3342,15 @@ seedCmd.command("export [path]").description("Export current Convex data as seed
3291
3342
  seedCmd.command("import [path]").description("Import seed data into current worktree").action(async (path) => {
3292
3343
  await seedImport(path);
3293
3344
  });
3294
- program2.command("config").description("View and manage zdev configuration").option("-a, --add <pattern>", "Add a file pattern to auto-copy").option("-r, --remove <pattern>", "Remove a file pattern").option("-s, --set <key=value>", "Set a config value (devDomain, dockerHostIp, traefikConfigDir)").option("-l, --list", "List current configuration").action(async (options) => {
3295
- await configCmd(options);
3345
+ program2.command("config").description("View and manage zdev configuration").option("-a, --add <pattern>", "Add a file pattern to auto-copy").option("-r, --remove <pattern>", "Remove a file pattern").option("-s, --set <key=value>", "Set a config value (devDomain, dockerHostIp, traefikConfigDir)").option("-l, --list", "List current configuration").action(async (options2) => {
3346
+ await configCmd(options2);
3296
3347
  });
3297
- program2.command("pr [feature]").description("Create a pull request for a feature branch").option("-p, --project <path>", "Project path", ".").option("-t, --title <title>", "PR title (auto-generated if not specified)").option("-b, --body <body>", "PR body (preview URL auto-added)").option("-d, --draft", "Create as draft PR").option("-w, --web", "Open in browser instead of CLI").action(async (feature, options) => {
3298
- await pr(feature, options.project, {
3299
- title: options.title,
3300
- body: options.body,
3301
- draft: options.draft,
3302
- web: options.web
3348
+ program2.command("pr [feature]").description("Create a pull request for a feature branch").option("-p, --project <path>", "Project path", ".").option("-t, --title <title>", "PR title (auto-generated if not specified)").option("-b, --body <body>", "PR body (preview URL auto-added)").option("-d, --draft", "Create as draft PR").option("-w, --web", "Open in browser instead of CLI").action(async (feature, options2) => {
3349
+ await pr(feature, options2.project, {
3350
+ title: options2.title,
3351
+ body: options2.body,
3352
+ draft: options2.draft,
3353
+ web: options2.web
3303
3354
  });
3304
3355
  });
3305
3356
  program2.command("status").description("Show zdev status (alias for list)").action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zdev",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Multi-agent worktree development environment for cloud dev with preview URLs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -120,21 +120,90 @@ export async function pr(
120
120
  const diffStatResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--shortstat"], { cwd: worktreePath });
121
121
  const diffStat = diffStatResult.success ? diffStatResult.stdout.trim() : "";
122
122
 
123
+ // Get changed files for smart title generation
124
+ const changedFilesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--name-only"], { cwd: worktreePath });
125
+ const changedFiles = changedFilesResult.success ? changedFilesResult.stdout.trim().split("\n").filter(Boolean) : [];
126
+
123
127
  // Build PR title
124
128
  let title = options.title;
125
129
  if (!title) {
126
- if (commits.length > 0) {
127
- // Use first commit message as title
128
- title = commits[0];
129
- } else {
130
- // Fallback to feature name
131
- const featureForTitle = featureName || branch.replace(/^feature\//, "");
132
- title = featureForTitle
133
- .split(/[-_]/)
134
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
135
- .join(" ");
130
+ // Try to generate smart title from changed files
131
+ title = generateSmartTitle(changedFiles, commits, featureName || branch.replace(/^feature\//, ""));
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Generate a smart PR title based on changed files and commits
137
+ */
138
+ function generateSmartTitle(files: string[], commits: string[], featureName: string): string {
139
+ // Extract meaningful names from file paths
140
+ const components = new Set<string>();
141
+ const areas = new Set<string>();
142
+
143
+ for (const file of files) {
144
+ // Skip non-code files
145
+ if (!file.match(/\.(tsx?|jsx?|css|scss)$/)) continue;
146
+
147
+ // Extract component names from paths like src/components/OrderCard.tsx
148
+ const componentMatch = file.match(/components\/([^/]+)\/([^/]+)\.(tsx?|jsx?)$/);
149
+ if (componentMatch) {
150
+ components.add(componentMatch[2].replace(/\.(tsx?|jsx?)$/, ""));
151
+ continue;
152
+ }
153
+
154
+ // Single component file
155
+ const singleComponent = file.match(/components\/([^/]+)\.(tsx?|jsx?)$/);
156
+ if (singleComponent) {
157
+ components.add(singleComponent[1]);
158
+ continue;
159
+ }
160
+
161
+ // Routes
162
+ const routeMatch = file.match(/routes\/(.+)\.(tsx?|jsx?)$/);
163
+ if (routeMatch) {
164
+ const routeName = routeMatch[1].replace(/[[\]$_.]/g, " ").trim();
165
+ if (routeName && routeName !== "index") {
166
+ areas.add(routeName);
167
+ }
168
+ continue;
136
169
  }
170
+
171
+ // Other meaningful paths
172
+ const pathParts = file.split("/");
173
+ if (pathParts.length > 1) {
174
+ const folder = pathParts[pathParts.length - 2];
175
+ if (!["src", "web", "app", "lib", "utils"].includes(folder)) {
176
+ areas.add(folder);
177
+ }
178
+ }
179
+ }
180
+
181
+ // Build title from components/areas
182
+ const items = [...components, ...areas].slice(0, 3);
183
+
184
+ if (items.length > 0) {
185
+ // Determine action from commits
186
+ let action = "Update";
187
+ const commitText = commits.join(" ").toLowerCase();
188
+ if (commitText.includes("fix")) action = "Fix";
189
+ else if (commitText.includes("add") || commitText.includes("new")) action = "Add";
190
+ else if (commitText.includes("refactor")) action = "Refactor";
191
+ else if (commitText.includes("improve") || commitText.includes("enhance")) action = "Improve";
192
+ else if (commitText.includes("mobile") || commitText.includes("responsive")) action = "Improve";
193
+
194
+ return `${action} ${items.join(", ")}`;
195
+ }
196
+
197
+ // Fallback: use first commit or feature name
198
+ if (commits.length > 0 && commits[0].length < 72) {
199
+ return commits[0];
137
200
  }
201
+
202
+ // Final fallback: humanize feature name
203
+ return featureName
204
+ .split(/[-_]/)
205
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
206
+ .join(" ");
138
207
 
139
208
  // Build PR body
140
209
  let body = options.body || "";