zdev 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +343 -284
- package/package.json +1 -1
- package/src/commands/pr.ts +155 -111
- package/src/index.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -475,11 +475,11 @@ var require_option = __commonJS((exports) => {
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
class DualOptions {
|
|
478
|
-
constructor(
|
|
478
|
+
constructor(options) {
|
|
479
479
|
this.positiveOptions = new Map;
|
|
480
480
|
this.negativeOptions = new Map;
|
|
481
481
|
this.dualOptions = new Set;
|
|
482
|
-
|
|
482
|
+
options.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(
|
|
943
|
+
_optionEx(config, 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(!!
|
|
948
|
+
option.makeOptionMandatory(!!config.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
|
|
1518
|
-
const exitCode =
|
|
1519
|
-
const code =
|
|
1517
|
+
const config = errorOptions || {};
|
|
1518
|
+
const exitCode = config.exitCode || 1;
|
|
1519
|
+
const code = config.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(
|
|
1932
|
+
function saveConfig(config) {
|
|
1933
1933
|
ensurezdevDirs();
|
|
1934
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(
|
|
1934
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
1935
1935
|
}
|
|
1936
|
-
function allocatePorts(
|
|
1937
|
-
const frontend =
|
|
1938
|
-
|
|
1936
|
+
function allocatePorts(config, includeConvex = true) {
|
|
1937
|
+
const frontend = config.nextFrontendPort;
|
|
1938
|
+
config.nextFrontendPort = frontend + 1;
|
|
1939
1939
|
let convex = 0;
|
|
1940
1940
|
if (includeConvex) {
|
|
1941
|
-
convex =
|
|
1942
|
-
|
|
1941
|
+
convex = config.nextConvexPort;
|
|
1942
|
+
config.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(
|
|
1950
|
-
return join(SEEDS_DIR, `${
|
|
1949
|
+
function getSeedPath(projectName) {
|
|
1950
|
+
return join(SEEDS_DIR, `${projectName}.zip`);
|
|
1951
1951
|
}
|
|
1952
1952
|
|
|
1953
1953
|
// src/utils.ts
|
|
1954
|
-
function run(command, args,
|
|
1954
|
+
function run(command, args, options) {
|
|
1955
1955
|
const result = spawnSync(command, args, {
|
|
1956
1956
|
encoding: "utf-8",
|
|
1957
|
-
...
|
|
1957
|
+
...options
|
|
1958
1958
|
});
|
|
1959
1959
|
return {
|
|
1960
1960
|
success: result.status === 0,
|
|
@@ -1963,11 +1963,11 @@ function run(command, args, options2) {
|
|
|
1963
1963
|
code: result.status
|
|
1964
1964
|
};
|
|
1965
1965
|
}
|
|
1966
|
-
function runBackground(command, args,
|
|
1966
|
+
function runBackground(command, args, options) {
|
|
1967
1967
|
const child = spawn(command, args, {
|
|
1968
1968
|
detached: true,
|
|
1969
1969
|
stdio: "ignore",
|
|
1970
|
-
...
|
|
1970
|
+
...options
|
|
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,
|
|
1993
|
-
const result = run("git", ["worktree", "add",
|
|
1992
|
+
function createWorktree(repoPath, worktreePath, branch, baseBranch = "origin/main") {
|
|
1993
|
+
const result = run("git", ["worktree", "add", worktreePath, "-b", branch, 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,
|
|
2000
|
-
const result = run("git", ["worktree", "remove",
|
|
1999
|
+
function removeWorktree(repoPath, worktreePath) {
|
|
2000
|
+
const result = run("git", ["worktree", "remove", worktreePath, "--force"], {
|
|
2001
2001
|
cwd: repoPath
|
|
2002
2002
|
});
|
|
2003
2003
|
if (!result.success) {
|
|
@@ -2007,14 +2007,14 @@ function removeWorktree(repoPath, worktreePath2) {
|
|
|
2007
2007
|
return { success: true };
|
|
2008
2008
|
}
|
|
2009
2009
|
function traefikAddRoute(name, port) {
|
|
2010
|
-
const
|
|
2011
|
-
const configPath = `${
|
|
2010
|
+
const config = loadConfig();
|
|
2011
|
+
const configPath = `${config.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}.${
|
|
2017
|
+
rule: "Host(\`${subdomain}.${config.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://${
|
|
2028
|
+
- url: "http://${config.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
|
|
2051
|
-
if (!
|
|
2050
|
+
const config = loadConfig();
|
|
2051
|
+
if (!config.devDomain) {
|
|
2052
2052
|
return { running: false, devDomain: undefined };
|
|
2053
2053
|
}
|
|
2054
|
-
const configDirExists = existsSync2(
|
|
2054
|
+
const configDirExists = existsSync2(config.traefikConfigDir);
|
|
2055
2055
|
return {
|
|
2056
2056
|
running: configDirExists,
|
|
2057
|
-
baseUrl: configDirExists ? `https://*.${
|
|
2058
|
-
devDomain:
|
|
2057
|
+
baseUrl: configDirExists ? `https://*.${config.devDomain}` : undefined,
|
|
2058
|
+
devDomain: config.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(
|
|
2242
|
-
const targetPath = resolve2(
|
|
2241
|
+
async function create(projectName, options = {}) {
|
|
2242
|
+
const targetPath = resolve2(projectName);
|
|
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: ${
|
|
2248
|
-
console.log(` Convex: ${
|
|
2249
|
-
console.log(` Structure: ${
|
|
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"}`);
|
|
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
|
-
|
|
2256
|
+
projectName
|
|
2257
2257
|
]);
|
|
2258
2258
|
if (!cloneResult.success) {
|
|
2259
2259
|
console.error(`❌ Failed to clone template: ${cloneResult.stderr}`);
|
|
@@ -2261,7 +2261,7 @@ async function create(projectName2, options2 = {}) {
|
|
|
2261
2261
|
}
|
|
2262
2262
|
console.log(` Template cloned`);
|
|
2263
2263
|
let webPath;
|
|
2264
|
-
if (
|
|
2264
|
+
if (options.flat) {
|
|
2265
2265
|
webPath = targetPath;
|
|
2266
2266
|
} else {
|
|
2267
2267
|
console.log(`
|
|
@@ -2278,7 +2278,7 @@ async function create(projectName2, options2 = {}) {
|
|
|
2278
2278
|
renameSync(tempDir, webDir);
|
|
2279
2279
|
webPath = webDir;
|
|
2280
2280
|
const rootPackageJson = {
|
|
2281
|
-
name:
|
|
2281
|
+
name: projectName,
|
|
2282
2282
|
private: true,
|
|
2283
2283
|
workspaces: ["web"],
|
|
2284
2284
|
scripts: {
|
|
@@ -2324,12 +2324,12 @@ async function create(projectName2, options2 = {}) {
|
|
|
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 =
|
|
2327
|
+
pkg.name = options.flat ? projectName : `${projectName}-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 (
|
|
2332
|
+
if (options.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(projectName2, options2 = {}) {
|
|
|
2353
2353
|
console.log(` Created .env.local.example`);
|
|
2354
2354
|
console.log(`
|
|
2355
2355
|
⚠️ To complete Convex setup:`);
|
|
2356
|
-
console.log(` 1. cd ${
|
|
2356
|
+
console.log(` 1. cd ${options.flat ? projectName : projectName + "/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(projectName2, options2 = {}) {
|
|
|
2380
2380
|
console.log(` Git initialized`);
|
|
2381
2381
|
console.log(`
|
|
2382
2382
|
${"-".repeat(50)}`);
|
|
2383
|
-
console.log(`✅ Project "${
|
|
2383
|
+
console.log(`✅ Project "${projectName}" 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 ${
|
|
2389
|
-
if (!
|
|
2388
|
+
console.log(` cd ${projectName}`);
|
|
2389
|
+
if (!options.flat) {
|
|
2390
2390
|
console.log(` cd web`);
|
|
2391
2391
|
}
|
|
2392
|
-
if (
|
|
2392
|
+
if (options.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 = ".",
|
|
2402
|
+
async function init(projectPath = ".", options = {}) {
|
|
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 = ".", options2 = {}) {
|
|
|
2434
2434
|
console.log(` Added .zdev/ to .gitignore`);
|
|
2435
2435
|
}
|
|
2436
2436
|
}
|
|
2437
|
-
if (
|
|
2437
|
+
if (options.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(
|
|
2466
|
+
function detectWebDir(worktreePath) {
|
|
2467
2467
|
const commonDirs = ["web", "frontend", "app", "client", "packages/web", "apps/web"];
|
|
2468
2468
|
for (const dir of commonDirs) {
|
|
2469
|
-
const packagePath = join3(
|
|
2469
|
+
const packagePath = join3(worktreePath, dir, "package.json");
|
|
2470
2470
|
if (existsSync5(packagePath)) {
|
|
2471
2471
|
return dir;
|
|
2472
2472
|
}
|
|
2473
2473
|
}
|
|
2474
|
-
if (existsSync5(join3(
|
|
2474
|
+
if (existsSync5(join3(worktreePath, "package.json"))) {
|
|
2475
2475
|
return ".";
|
|
2476
2476
|
}
|
|
2477
2477
|
return "web";
|
|
2478
2478
|
}
|
|
2479
|
-
async function start(featureName, projectPath = ".",
|
|
2479
|
+
async function start(featureName, projectPath = ".", options = {}) {
|
|
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 = ".", options2 = {}) {
|
|
|
2488
2488
|
}
|
|
2489
2489
|
const repoName = getRepoName(fullPath);
|
|
2490
2490
|
const worktreeName = `${repoName}-${featureName}`;
|
|
2491
|
-
const
|
|
2491
|
+
const worktreePath = getWorktreePath(worktreeName);
|
|
2492
2492
|
const branchName = `feature/${featureName}`;
|
|
2493
|
-
let baseBranch =
|
|
2493
|
+
let baseBranch = options.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 = ".", options2 = {}) {
|
|
|
2508
2508
|
console.log(`\uD83D\uDC02 Starting feature: ${featureName}`);
|
|
2509
2509
|
console.log(` Project: ${repoName}`);
|
|
2510
2510
|
console.log(` Branch: ${branchName}`);
|
|
2511
|
-
const
|
|
2512
|
-
if (
|
|
2511
|
+
const config = loadConfig();
|
|
2512
|
+
if (config.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 = ".", options2 = {}) {
|
|
|
2525
2525
|
}
|
|
2526
2526
|
console.log(`
|
|
2527
2527
|
\uD83C\uDF33 Creating worktree...`);
|
|
2528
|
-
if (existsSync5(
|
|
2529
|
-
console.error(` Worktree path already exists: ${
|
|
2528
|
+
if (existsSync5(worktreePath)) {
|
|
2529
|
+
console.error(` Worktree path already exists: ${worktreePath}`);
|
|
2530
2530
|
process.exit(1);
|
|
2531
2531
|
}
|
|
2532
|
-
const worktreeResult = createWorktree(fullPath,
|
|
2532
|
+
const worktreeResult = createWorktree(fullPath, worktreePath, 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: ${
|
|
2538
|
-
const webDir =
|
|
2539
|
-
const webPath = webDir === "." ?
|
|
2537
|
+
console.log(` Created: ${worktreePath}`);
|
|
2538
|
+
const webDir = options.webDir || detectWebDir(worktreePath);
|
|
2539
|
+
const webPath = webDir === "." ? worktreePath : join3(worktreePath, webDir);
|
|
2540
2540
|
console.log(`
|
|
2541
2541
|
\uD83D\uDCC1 Web directory: ${webDir === "." ? "(root)" : webDir}`);
|
|
2542
|
-
if (
|
|
2542
|
+
if (config.copyPatterns && config.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
|
|
2546
|
+
for (const pattern of config.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 = ".", options2 = {}) {
|
|
|
2557
2557
|
}
|
|
2558
2558
|
}
|
|
2559
2559
|
}
|
|
2560
|
-
const setupScriptPath = join3(
|
|
2560
|
+
const setupScriptPath = join3(worktreePath, ".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 = ".", options2 = {}) {
|
|
|
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(
|
|
2575
|
+
const hasConvex = existsSync5(join3(webPath, "convex")) || existsSync5(join3(worktreePath, "convex"));
|
|
2576
2576
|
const seedPath = getSeedPath(repoName);
|
|
2577
|
-
if (
|
|
2577
|
+
if (options.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 = ".", options2 = {}) {
|
|
|
2586
2586
|
console.error(` Failed to import seed: ${seedResult.stderr}`);
|
|
2587
2587
|
}
|
|
2588
2588
|
}
|
|
2589
|
-
const ports =
|
|
2589
|
+
const ports = options.port ? { frontend: options.port, convex: hasConvex ? options.port + 100 : 0 } : allocatePorts(config, 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 = ".", options2 = {}) {
|
|
|
2636
2636
|
console.log(` Frontend PID: ${frontendPid}`);
|
|
2637
2637
|
let routePath = "";
|
|
2638
2638
|
let publicUrl = "";
|
|
2639
|
-
if (!
|
|
2639
|
+
if (!options.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 = ".", options2 = {}) {
|
|
|
2655
2655
|
console.log(` Run: zdev config --set devDomain=dev.yourdomain.com`);
|
|
2656
2656
|
}
|
|
2657
2657
|
}
|
|
2658
|
-
const
|
|
2658
|
+
const allocation = {
|
|
2659
2659
|
project: repoName,
|
|
2660
2660
|
projectPath: fullPath,
|
|
2661
2661
|
branch: branchName,
|
|
@@ -2663,7 +2663,7 @@ async function start(featureName, projectPath = ".", options2 = {}) {
|
|
|
2663
2663
|
frontendPort: ports.frontend,
|
|
2664
2664
|
convexPort: ports.convex,
|
|
2665
2665
|
funnelPath: routePath,
|
|
2666
|
-
worktreePath
|
|
2666
|
+
worktreePath,
|
|
2667
2667
|
publicUrl: publicUrl || undefined,
|
|
2668
2668
|
pids: {
|
|
2669
2669
|
frontend: frontendPid,
|
|
@@ -2671,104 +2671,104 @@ async function start(featureName, projectPath = ".", options2 = {}) {
|
|
|
2671
2671
|
},
|
|
2672
2672
|
started: new Date().toISOString()
|
|
2673
2673
|
};
|
|
2674
|
-
|
|
2675
|
-
saveConfig(
|
|
2674
|
+
config.allocations[worktreeName] = allocation;
|
|
2675
|
+
saveConfig(config);
|
|
2676
2676
|
console.log(`
|
|
2677
2677
|
${"-".repeat(50)}`);
|
|
2678
2678
|
console.log(`✅ Feature "${featureName}" is ready!
|
|
2679
2679
|
`);
|
|
2680
|
-
console.log(`\uD83D\uDCC1 Worktree: ${
|
|
2680
|
+
console.log(`\uD83D\uDCC1 Worktree: ${worktreePath}`);
|
|
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 ${
|
|
2687
|
+
console.log(` cd ${worktreePath}`);
|
|
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,
|
|
2695
|
-
const
|
|
2694
|
+
async function stop(featureName, options = {}) {
|
|
2695
|
+
const config = loadConfig();
|
|
2696
2696
|
let worktreeName;
|
|
2697
|
-
let
|
|
2698
|
-
if (
|
|
2699
|
-
const projectPath = resolve5(
|
|
2700
|
-
const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) :
|
|
2697
|
+
let allocation;
|
|
2698
|
+
if (options.project) {
|
|
2699
|
+
const projectPath = resolve5(options.project);
|
|
2700
|
+
const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options.project;
|
|
2701
2701
|
worktreeName = `${repoName}-${featureName}`;
|
|
2702
|
-
|
|
2702
|
+
allocation = config.allocations[worktreeName];
|
|
2703
2703
|
} else {
|
|
2704
|
-
for (const [name, alloc] of Object.entries(
|
|
2704
|
+
for (const [name, alloc] of Object.entries(config.allocations)) {
|
|
2705
2705
|
if (name.endsWith(`-${featureName}`)) {
|
|
2706
2706
|
worktreeName = name;
|
|
2707
|
-
|
|
2707
|
+
allocation = alloc;
|
|
2708
2708
|
break;
|
|
2709
2709
|
}
|
|
2710
2710
|
}
|
|
2711
2711
|
}
|
|
2712
|
-
if (!worktreeName || !
|
|
2712
|
+
if (!worktreeName || !allocation) {
|
|
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: ${
|
|
2720
|
-
if (
|
|
2719
|
+
console.log(` Project: ${allocation.project}`);
|
|
2720
|
+
if (allocation.pids.frontend && isProcessRunning(allocation.pids.frontend)) {
|
|
2721
2721
|
console.log(`
|
|
2722
|
-
\uD83D\uDED1 Stopping frontend (PID: ${
|
|
2723
|
-
if (killProcess(
|
|
2722
|
+
\uD83D\uDED1 Stopping frontend (PID: ${allocation.pids.frontend})...`);
|
|
2723
|
+
if (killProcess(allocation.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 (
|
|
2729
|
+
if (allocation.pids.convex && isProcessRunning(allocation.pids.convex)) {
|
|
2730
2730
|
console.log(`
|
|
2731
|
-
\uD83D\uDED1 Stopping Convex (PID: ${
|
|
2732
|
-
if (killProcess(
|
|
2731
|
+
\uD83D\uDED1 Stopping Convex (PID: ${allocation.pids.convex})...`);
|
|
2732
|
+
if (killProcess(allocation.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 (
|
|
2738
|
+
if (allocation.funnelPath) {
|
|
2739
2739
|
console.log(`
|
|
2740
2740
|
\uD83D\uDD17 Removing Traefik route...`);
|
|
2741
|
-
if (traefikRemoveRoute(
|
|
2741
|
+
if (traefikRemoveRoute(allocation.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
|
|
2748
|
-
saveConfig(
|
|
2749
|
-
const
|
|
2750
|
-
if (
|
|
2747
|
+
delete config.allocations[worktreeName];
|
|
2748
|
+
saveConfig(config);
|
|
2749
|
+
const worktreePath = getWorktreePath(worktreeName);
|
|
2750
|
+
if (options.keep) {
|
|
2751
2751
|
console.log(`
|
|
2752
2752
|
✅ Feature "${featureName}" stopped (worktree kept)`);
|
|
2753
|
-
console.log(` Worktree: ${
|
|
2753
|
+
console.log(` Worktree: ${worktreePath}`);
|
|
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: ${
|
|
2761
|
-
console.log(` To remove: zdev clean ${featureName} --project ${
|
|
2760
|
+
Worktree still exists at: ${worktreePath}`);
|
|
2761
|
+
console.log(` To remove: zdev clean ${featureName} --project ${allocation.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(
|
|
2768
|
-
const
|
|
2769
|
-
const allocations = Object.entries(
|
|
2770
|
-
if (
|
|
2771
|
-
console.log(JSON.stringify(
|
|
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));
|
|
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
|
|
2798
|
-
const worktreeExists = existsSync6(
|
|
2797
|
+
const worktreePath = getWorktreePath(name);
|
|
2798
|
+
const worktreeExists = existsSync6(worktreePath);
|
|
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: ${
|
|
2805
|
+
console.log(` Path: ${worktreePath} ${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,
|
|
2826
|
-
const
|
|
2825
|
+
async function clean(featureName, options = {}) {
|
|
2826
|
+
const config = loadConfig();
|
|
2827
2827
|
let worktreeName;
|
|
2828
|
-
let
|
|
2828
|
+
let allocation;
|
|
2829
2829
|
let projectPath;
|
|
2830
|
-
if (
|
|
2831
|
-
projectPath = resolve6(
|
|
2832
|
-
const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) :
|
|
2830
|
+
if (options.project) {
|
|
2831
|
+
projectPath = resolve6(options.project);
|
|
2832
|
+
const repoName = isGitRepo(projectPath) ? getRepoName(projectPath) : options.project;
|
|
2833
2833
|
worktreeName = `${repoName}-${featureName}`;
|
|
2834
|
-
|
|
2834
|
+
allocation = config.allocations[worktreeName];
|
|
2835
2835
|
} else {
|
|
2836
|
-
for (const [name, alloc] of Object.entries(
|
|
2836
|
+
for (const [name, alloc] of Object.entries(config.allocations)) {
|
|
2837
2837
|
if (name.endsWith(`-${featureName}`)) {
|
|
2838
2838
|
worktreeName = name;
|
|
2839
|
-
|
|
2839
|
+
allocation = alloc;
|
|
2840
2840
|
projectPath = alloc.projectPath;
|
|
2841
2841
|
break;
|
|
2842
2842
|
}
|
|
2843
2843
|
}
|
|
2844
2844
|
if (!worktreeName) {
|
|
2845
|
-
const entries = Object.keys(
|
|
2845
|
+
const entries = Object.keys(config.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
|
|
2855
|
+
const worktreePath = getWorktreePath(worktreeName);
|
|
2856
2856
|
console.log(`\uD83D\uDC02 Cleaning feature: ${featureName}`);
|
|
2857
|
-
if (
|
|
2858
|
-
if (
|
|
2857
|
+
if (allocation) {
|
|
2858
|
+
if (allocation.pids.frontend && isProcessRunning(allocation.pids.frontend)) {
|
|
2859
2859
|
console.log(`
|
|
2860
2860
|
\uD83D\uDED1 Stopping frontend...`);
|
|
2861
|
-
killProcess(
|
|
2861
|
+
killProcess(allocation.pids.frontend);
|
|
2862
2862
|
}
|
|
2863
|
-
if (
|
|
2863
|
+
if (allocation.pids.convex && isProcessRunning(allocation.pids.convex)) {
|
|
2864
2864
|
console.log(`\uD83D\uDED1 Stopping Convex...`);
|
|
2865
|
-
killProcess(
|
|
2865
|
+
killProcess(allocation.pids.convex);
|
|
2866
2866
|
}
|
|
2867
|
-
if (
|
|
2867
|
+
if (allocation.funnelPath) {
|
|
2868
2868
|
console.log(`\uD83D\uDD17 Removing Traefik route...`);
|
|
2869
|
-
traefikRemoveRoute(
|
|
2869
|
+
traefikRemoveRoute(allocation.funnelPath);
|
|
2870
2870
|
}
|
|
2871
|
-
projectPath =
|
|
2871
|
+
projectPath = allocation.projectPath;
|
|
2872
2872
|
}
|
|
2873
|
-
if (existsSync7(
|
|
2873
|
+
if (existsSync7(worktreePath)) {
|
|
2874
2874
|
console.log(`
|
|
2875
2875
|
\uD83D\uDDD1️ Removing worktree...`);
|
|
2876
2876
|
if (projectPath && isGitRepo(projectPath)) {
|
|
2877
|
-
const result = removeWorktree(projectPath,
|
|
2877
|
+
const result = removeWorktree(projectPath, worktreePath);
|
|
2878
2878
|
if (!result.success) {
|
|
2879
|
-
if (
|
|
2879
|
+
if (options.force) {
|
|
2880
2880
|
console.log(` Git worktree remove failed, force removing directory...`);
|
|
2881
|
-
rmSync2(
|
|
2881
|
+
rmSync2(worktreePath, { 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 (
|
|
2889
|
-
rmSync2(
|
|
2888
|
+
} else if (options.force) {
|
|
2889
|
+
rmSync2(worktreePath, { 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 &&
|
|
2901
|
-
delete
|
|
2902
|
-
saveConfig(
|
|
2900
|
+
if (worktreeName && config.allocations[worktreeName]) {
|
|
2901
|
+
delete config.allocations[worktreeName];
|
|
2902
|
+
saveConfig(config);
|
|
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 = ".",
|
|
2911
|
+
async function seedExport(projectPath = ".", options = {}) {
|
|
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 = ".", options2 = {}) {
|
|
|
2935
2935
|
process.exit(1);
|
|
2936
2936
|
}
|
|
2937
2937
|
}
|
|
2938
|
-
async function seedImport(projectPath = ".",
|
|
2938
|
+
async function seedImport(projectPath = ".", options = {}) {
|
|
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 = ".", options2 = {}) {
|
|
|
2945
2945
|
const projectConfigPath = resolve7(fullPath, ".zdev", "project.json");
|
|
2946
2946
|
if (existsSync8(projectConfigPath)) {
|
|
2947
2947
|
try {
|
|
2948
|
-
const
|
|
2949
|
-
repoName =
|
|
2948
|
+
const config = JSON.parse(await Bun.file(projectConfigPath).text());
|
|
2949
|
+
repoName = config.name;
|
|
2950
2950
|
} catch {
|
|
2951
2951
|
repoName = getRepoName(fullPath);
|
|
2952
2952
|
}
|
|
@@ -2981,10 +2981,10 @@ async function seedImport(projectPath = ".", options2 = {}) {
|
|
|
2981
2981
|
}
|
|
2982
2982
|
|
|
2983
2983
|
// src/commands/config.ts
|
|
2984
|
-
async function configCmd(
|
|
2985
|
-
const
|
|
2986
|
-
if (
|
|
2987
|
-
const [key, ...valueParts] =
|
|
2984
|
+
async function configCmd(options = {}) {
|
|
2985
|
+
const config = loadConfig();
|
|
2986
|
+
if (options.set) {
|
|
2987
|
+
const [key, ...valueParts] = options.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
|
-
|
|
3000
|
-
saveConfig(
|
|
2999
|
+
config.devDomain = value;
|
|
3000
|
+
saveConfig(config);
|
|
3001
3001
|
console.log(`✅ Set devDomain = ${value}`);
|
|
3002
3002
|
} else if (key === "dockerHostIp") {
|
|
3003
|
-
|
|
3004
|
-
saveConfig(
|
|
3003
|
+
config.dockerHostIp = value;
|
|
3004
|
+
saveConfig(config);
|
|
3005
3005
|
console.log(`✅ Set dockerHostIp = ${value}`);
|
|
3006
3006
|
} else if (key === "traefikConfigDir") {
|
|
3007
|
-
|
|
3008
|
-
saveConfig(
|
|
3007
|
+
config.traefikConfigDir = value;
|
|
3008
|
+
saveConfig(config);
|
|
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 (
|
|
3015
|
+
if (options.list || !options.add && !options.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: ${
|
|
3022
|
-
console.log(` Docker host IP: ${
|
|
3023
|
-
console.log(` Config dir: ${
|
|
3021
|
+
console.log(` Dev domain: ${config.devDomain}`);
|
|
3022
|
+
console.log(` Docker host IP: ${config.dockerHostIp}`);
|
|
3023
|
+
console.log(` Config dir: ${config.traefikConfigDir}`);
|
|
3024
3024
|
console.log(`
|
|
3025
3025
|
\uD83D\uDCCB Copy patterns (files auto-copied to worktrees):`);
|
|
3026
|
-
if (
|
|
3027
|
-
for (const pattern of
|
|
3026
|
+
if (config.copyPatterns && config.copyPatterns.length > 0) {
|
|
3027
|
+
for (const pattern of config.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: ${
|
|
3036
|
-
console.log(` Next Convex port: ${
|
|
3035
|
+
console.log(` Next frontend port: ${config.nextFrontendPort}`);
|
|
3036
|
+
console.log(` Next Convex port: ${config.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 (
|
|
3045
|
-
if (!
|
|
3046
|
-
|
|
3044
|
+
if (options.add) {
|
|
3045
|
+
if (!config.copyPatterns) {
|
|
3046
|
+
config.copyPatterns = [];
|
|
3047
3047
|
}
|
|
3048
|
-
if (
|
|
3049
|
-
console.log(`Pattern "${
|
|
3048
|
+
if (config.copyPatterns.includes(options.add)) {
|
|
3049
|
+
console.log(`Pattern "${options.add}" already exists`);
|
|
3050
3050
|
} else {
|
|
3051
|
-
|
|
3052
|
-
saveConfig(
|
|
3053
|
-
console.log(`✅ Added copy pattern: ${
|
|
3051
|
+
config.copyPatterns.push(options.add);
|
|
3052
|
+
saveConfig(config);
|
|
3053
|
+
console.log(`✅ Added copy pattern: ${options.add}`);
|
|
3054
3054
|
}
|
|
3055
3055
|
return;
|
|
3056
3056
|
}
|
|
3057
|
-
if (
|
|
3058
|
-
if (!
|
|
3059
|
-
console.log(`Pattern "${
|
|
3057
|
+
if (options.remove) {
|
|
3058
|
+
if (!config.copyPatterns) {
|
|
3059
|
+
console.log(`Pattern "${options.remove}" not found`);
|
|
3060
3060
|
return;
|
|
3061
3061
|
}
|
|
3062
|
-
const index =
|
|
3062
|
+
const index = config.copyPatterns.indexOf(options.remove);
|
|
3063
3063
|
if (index === -1) {
|
|
3064
|
-
console.log(`Pattern "${
|
|
3064
|
+
console.log(`Pattern "${options.remove}" not found`);
|
|
3065
3065
|
} else {
|
|
3066
|
-
|
|
3067
|
-
saveConfig(
|
|
3068
|
-
console.log(`✅ Removed copy pattern: ${
|
|
3066
|
+
config.copyPatterns.splice(index, 1);
|
|
3067
|
+
saveConfig(config);
|
|
3068
|
+
console.log(`✅ Removed copy pattern: ${options.remove}`);
|
|
3069
3069
|
}
|
|
3070
3070
|
return;
|
|
3071
3071
|
}
|
|
@@ -3073,88 +3073,47 @@ Commands:`);
|
|
|
3073
3073
|
|
|
3074
3074
|
// src/commands/pr.ts
|
|
3075
3075
|
import { resolve as resolve8, basename as basename4 } from "path";
|
|
3076
|
-
|
|
3077
|
-
const
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
let projectName2 = getRepoName(fullPath) || basename4(fullPath);
|
|
3081
|
-
const config2 = loadConfig();
|
|
3082
|
-
if (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
|
-
if (found) {
|
|
3088
|
-
allocation2 = found[1];
|
|
3089
|
-
projectName2 = allocation2.project;
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
if (allocation2) {
|
|
3093
|
-
worktreePath2 = allocation2.worktreePath || resolve8(config2.worktreesDir, `${projectName2}-${featureName}`);
|
|
3094
|
-
}
|
|
3095
|
-
} else {
|
|
3096
|
-
const cwd = process.cwd();
|
|
3097
|
-
const found = Object.entries(config2.allocations).find(([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || ""));
|
|
3098
|
-
if (found) {
|
|
3099
|
-
allocation2 = found[1];
|
|
3100
|
-
featureName = found[0].split("-").slice(1).join("-");
|
|
3101
|
-
projectName2 = allocation2.project;
|
|
3102
|
-
worktreePath2 = allocation2.worktreePath || cwd;
|
|
3103
|
-
}
|
|
3104
|
-
}
|
|
3105
|
-
if (!isGitRepo(worktreePath2)) {
|
|
3106
|
-
console.error(`❌ Not a git repository: ${worktreePath2}`);
|
|
3107
|
-
process.exit(1);
|
|
3108
|
-
}
|
|
3109
|
-
const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath2 });
|
|
3110
|
-
if (!branchResult.success || !branchResult.stdout.trim()) {
|
|
3111
|
-
console.error("❌ Could not determine current branch");
|
|
3112
|
-
process.exit(1);
|
|
3113
|
-
}
|
|
3114
|
-
const branch2 = branchResult.stdout.trim();
|
|
3115
|
-
console.log(`\uD83D\uDC02 Creating PR for: ${branch2}`);
|
|
3116
|
-
if (allocation2) {
|
|
3117
|
-
console.log(` Project: ${projectName2}`);
|
|
3118
|
-
console.log(` Feature: ${featureName}`);
|
|
3119
|
-
}
|
|
3120
|
-
const ghCheck = run("which", ["gh"]);
|
|
3121
|
-
if (!ghCheck.success) {
|
|
3122
|
-
console.error("❌ GitHub CLI (gh) not found. Install: https://cli.github.com");
|
|
3123
|
-
process.exit(1);
|
|
3124
|
-
}
|
|
3125
|
-
const authCheck = run("gh", ["auth", "status"], { cwd: worktreePath2 });
|
|
3126
|
-
if (!authCheck.success) {
|
|
3127
|
-
console.error("❌ Not authenticated with GitHub. Run: gh auth login");
|
|
3128
|
-
process.exit(1);
|
|
3076
|
+
function generateWithAI(diff, commits, worktreePath) {
|
|
3077
|
+
const claudeCheck = run("which", ["claude"]);
|
|
3078
|
+
if (!claudeCheck.success) {
|
|
3079
|
+
return null;
|
|
3129
3080
|
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3081
|
+
const prompt = `You are generating a GitHub PR title and description.
|
|
3082
|
+
|
|
3083
|
+
Based on the following git diff and commit messages, generate:
|
|
3084
|
+
1. A concise PR title (max 72 chars, no quotes)
|
|
3085
|
+
2. A brief description of the changes (2-4 bullet points)
|
|
3086
|
+
|
|
3087
|
+
Commit messages:
|
|
3088
|
+
${commits.map((c) => `- ${c}`).join(`
|
|
3089
|
+
`)}
|
|
3090
|
+
|
|
3091
|
+
Diff summary (first 3000 chars):
|
|
3092
|
+
${diff.slice(0, 3000)}
|
|
3093
|
+
|
|
3094
|
+
Respond in this exact format:
|
|
3095
|
+
TITLE: <your title here>
|
|
3096
|
+
BODY:
|
|
3097
|
+
- <bullet point 1>
|
|
3098
|
+
- <bullet point 2>
|
|
3099
|
+
- <bullet point 3>`;
|
|
3100
|
+
const result = run("claude", ["-p", prompt, "--no-input"], {
|
|
3101
|
+
cwd: worktreePath,
|
|
3102
|
+
env: { ...process.env, CLAUDE_CODE_ENTRYPOINT: "zdev" }
|
|
3103
|
+
});
|
|
3104
|
+
if (!result.success || !result.stdout) {
|
|
3105
|
+
return null;
|
|
3141
3106
|
}
|
|
3142
|
-
const
|
|
3143
|
-
const
|
|
3144
|
-
const
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
const filesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--stat", "--stat-width=60"], { cwd: worktreePath2 });
|
|
3148
|
-
const filesSummary = filesResult.success ? filesResult.stdout.trim() : "";
|
|
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\//, ""));
|
|
3107
|
+
const output = result.stdout.trim();
|
|
3108
|
+
const titleMatch = output.match(/TITLE:\s*(.+)/);
|
|
3109
|
+
const bodyMatch = output.match(/BODY:\s*([\s\S]+)/);
|
|
3110
|
+
if (!titleMatch) {
|
|
3111
|
+
return null;
|
|
3157
3112
|
}
|
|
3113
|
+
return {
|
|
3114
|
+
title: titleMatch[1].trim().replace(/^["']|["']$/g, ""),
|
|
3115
|
+
body: bodyMatch ? bodyMatch[1].trim() : ""
|
|
3116
|
+
};
|
|
3158
3117
|
}
|
|
3159
3118
|
function generateSmartTitle(files, commits, featureName) {
|
|
3160
3119
|
const components = new Set;
|
|
@@ -3208,8 +3167,101 @@ function generateSmartTitle(files, commits, featureName) {
|
|
|
3208
3167
|
return commits[0];
|
|
3209
3168
|
}
|
|
3210
3169
|
return featureName.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3170
|
+
}
|
|
3171
|
+
async function pr(featureName, projectPath = ".", options = {}) {
|
|
3172
|
+
const fullPath = resolve8(projectPath);
|
|
3173
|
+
let worktreePath = fullPath;
|
|
3174
|
+
let allocation;
|
|
3175
|
+
let projectName = getRepoName(fullPath) || basename4(fullPath);
|
|
3176
|
+
const config = loadConfig();
|
|
3177
|
+
if (featureName) {
|
|
3178
|
+
const allocKey = `${projectName}-${featureName}`;
|
|
3179
|
+
allocation = config.allocations[allocKey];
|
|
3180
|
+
if (!allocation) {
|
|
3181
|
+
const found = Object.entries(config.allocations).find(([key]) => key.endsWith(`-${featureName}`));
|
|
3182
|
+
if (found) {
|
|
3183
|
+
allocation = found[1];
|
|
3184
|
+
projectName = allocation.project;
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
if (allocation) {
|
|
3188
|
+
worktreePath = allocation.worktreePath || resolve8(config.worktreesDir, `${projectName}-${featureName}`);
|
|
3189
|
+
}
|
|
3190
|
+
} else {
|
|
3191
|
+
const cwd = process.cwd();
|
|
3192
|
+
const found = Object.entries(config.allocations).find(([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || ""));
|
|
3193
|
+
if (found) {
|
|
3194
|
+
allocation = found[1];
|
|
3195
|
+
featureName = found[0].split("-").slice(1).join("-");
|
|
3196
|
+
projectName = allocation.project;
|
|
3197
|
+
worktreePath = allocation.worktreePath || cwd;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
if (!isGitRepo(worktreePath)) {
|
|
3201
|
+
console.error(`❌ Not a git repository: ${worktreePath}`);
|
|
3202
|
+
process.exit(1);
|
|
3203
|
+
}
|
|
3204
|
+
const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath });
|
|
3205
|
+
if (!branchResult.success || !branchResult.stdout.trim()) {
|
|
3206
|
+
console.error("❌ Could not determine current branch");
|
|
3207
|
+
process.exit(1);
|
|
3208
|
+
}
|
|
3209
|
+
const branch = branchResult.stdout.trim();
|
|
3210
|
+
console.log(`\uD83D\uDC02 Creating PR for: ${branch}`);
|
|
3211
|
+
if (allocation) {
|
|
3212
|
+
console.log(` Project: ${projectName}`);
|
|
3213
|
+
console.log(` Feature: ${featureName}`);
|
|
3214
|
+
}
|
|
3215
|
+
const ghCheck = run("which", ["gh"]);
|
|
3216
|
+
if (!ghCheck.success) {
|
|
3217
|
+
console.error("❌ GitHub CLI (gh) not found. Install: https://cli.github.com");
|
|
3218
|
+
process.exit(1);
|
|
3219
|
+
}
|
|
3220
|
+
const authCheck = run("gh", ["auth", "status"], { cwd: worktreePath });
|
|
3221
|
+
if (!authCheck.success) {
|
|
3222
|
+
console.error("❌ Not authenticated with GitHub. Run: gh auth login");
|
|
3223
|
+
process.exit(1);
|
|
3224
|
+
}
|
|
3225
|
+
console.log(`
|
|
3226
|
+
\uD83D\uDCE4 Pushing branch...`);
|
|
3227
|
+
const pushResult = run("git", ["push", "-u", "origin", branch], { cwd: worktreePath });
|
|
3228
|
+
if (!pushResult.success && !pushResult.stderr.includes("Everything up-to-date")) {
|
|
3229
|
+
console.error(` Failed to push: ${pushResult.stderr}`);
|
|
3230
|
+
process.exit(1);
|
|
3231
|
+
}
|
|
3232
|
+
console.log(` Pushed to origin/${branch}`);
|
|
3233
|
+
const defaultBranch = run("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], { cwd: worktreePath });
|
|
3234
|
+
const baseBranch = defaultBranch.success ? defaultBranch.stdout.trim().replace("origin/", "") : "main";
|
|
3235
|
+
const commitsResult = run("git", ["log", `origin/${baseBranch}..HEAD`, "--pretty=format:%s"], { cwd: worktreePath });
|
|
3236
|
+
const commits = commitsResult.success ? commitsResult.stdout.trim().split(`
|
|
3237
|
+
`).filter(Boolean) : [];
|
|
3238
|
+
const diffResult = run("git", ["diff", `origin/${baseBranch}..HEAD`], { cwd: worktreePath });
|
|
3239
|
+
const diff = diffResult.success ? diffResult.stdout : "";
|
|
3240
|
+
const diffStatResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--shortstat"], { cwd: worktreePath });
|
|
3241
|
+
const diffStat = diffStatResult.success ? diffStatResult.stdout.trim() : "";
|
|
3242
|
+
const changedFilesResult = run("git", ["diff", `origin/${baseBranch}..HEAD`, "--name-only"], { cwd: worktreePath });
|
|
3243
|
+
const changedFiles = changedFilesResult.success ? changedFilesResult.stdout.trim().split(`
|
|
3244
|
+
`).filter(Boolean) : [];
|
|
3245
|
+
let title = options.title;
|
|
3246
|
+
let aiBody = "";
|
|
3247
|
+
if (options.ai || !title && diff.length > 0) {
|
|
3248
|
+
console.log(`
|
|
3249
|
+
\uD83E\uDD16 Generating PR content with AI...`);
|
|
3250
|
+
const aiResult = generateWithAI(diff, commits, worktreePath);
|
|
3251
|
+
if (aiResult) {
|
|
3252
|
+
if (!title)
|
|
3253
|
+
title = aiResult.title;
|
|
3254
|
+
aiBody = aiResult.body;
|
|
3255
|
+
console.log(` Generated title: ${title}`);
|
|
3256
|
+
} else if (options.ai) {
|
|
3257
|
+
console.log(` AI generation failed, using smart fallback`);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
if (!title) {
|
|
3261
|
+
title = generateSmartTitle(changedFiles, commits, featureName || branch.replace(/^feature\//, ""));
|
|
3262
|
+
}
|
|
3211
3263
|
let body = options.body || "";
|
|
3212
|
-
if (allocation
|
|
3264
|
+
if (allocation?.publicUrl) {
|
|
3213
3265
|
body += `## Preview
|
|
3214
3266
|
\uD83D\uDD17 ${allocation.publicUrl}
|
|
3215
3267
|
|
|
@@ -3221,7 +3273,12 @@ function generateSmartTitle(files, commits, featureName) {
|
|
|
3221
3273
|
|
|
3222
3274
|
`;
|
|
3223
3275
|
}
|
|
3224
|
-
if (
|
|
3276
|
+
if (aiBody) {
|
|
3277
|
+
body += `## Changes
|
|
3278
|
+
${aiBody}
|
|
3279
|
+
|
|
3280
|
+
`;
|
|
3281
|
+
} else if (commits.length > 0) {
|
|
3225
3282
|
body += `## Changes
|
|
3226
3283
|
`;
|
|
3227
3284
|
commits.forEach((commit) => {
|
|
@@ -3258,6 +3315,7 @@ ${diffStat}
|
|
|
3258
3315
|
}
|
|
3259
3316
|
console.log(`
|
|
3260
3317
|
\uD83D\uDCDD Creating pull request...`);
|
|
3318
|
+
console.log(` Title: ${title}`);
|
|
3261
3319
|
const prArgs = ["pr", "create", "--title", title, "--body", body];
|
|
3262
3320
|
if (options.draft) {
|
|
3263
3321
|
prArgs.push("--draft");
|
|
@@ -3308,32 +3366,32 @@ var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
|
3308
3366
|
var pkg = JSON.parse(readFileSync5(join4(__dirname2, "..", "package.json"), "utf-8"));
|
|
3309
3367
|
var program2 = new Command;
|
|
3310
3368
|
program2.name("zdev").description(`\uD83D\uDC02 zdev v${pkg.version} - Multi-agent worktree development environment`).version(pkg.version);
|
|
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,
|
|
3369
|
+
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) => {
|
|
3312
3370
|
await create(name, {
|
|
3313
|
-
convex:
|
|
3314
|
-
flat:
|
|
3371
|
+
convex: options.convex,
|
|
3372
|
+
flat: options.flat
|
|
3315
3373
|
});
|
|
3316
3374
|
});
|
|
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,
|
|
3318
|
-
await init(path,
|
|
3375
|
+
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) => {
|
|
3376
|
+
await init(path, options);
|
|
3319
3377
|
});
|
|
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,
|
|
3321
|
-
await start(feature,
|
|
3322
|
-
port:
|
|
3323
|
-
local:
|
|
3324
|
-
seed:
|
|
3325
|
-
baseBranch:
|
|
3326
|
-
webDir:
|
|
3378
|
+
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) => {
|
|
3379
|
+
await start(feature, options.project, {
|
|
3380
|
+
port: options.port,
|
|
3381
|
+
local: options.local,
|
|
3382
|
+
seed: options.seed,
|
|
3383
|
+
baseBranch: options.baseBranch,
|
|
3384
|
+
webDir: options.webDir
|
|
3327
3385
|
});
|
|
3328
3386
|
});
|
|
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,
|
|
3330
|
-
await stop(feature,
|
|
3387
|
+
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) => {
|
|
3388
|
+
await stop(feature, options);
|
|
3331
3389
|
});
|
|
3332
|
-
program2.command("list").description("List active features and their status").option("--json", "Output as JSON").action(async (
|
|
3333
|
-
await list(
|
|
3390
|
+
program2.command("list").description("List active features and their status").option("--json", "Output as JSON").action(async (options) => {
|
|
3391
|
+
await list(options);
|
|
3334
3392
|
});
|
|
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,
|
|
3336
|
-
await clean(feature,
|
|
3393
|
+
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) => {
|
|
3394
|
+
await clean(feature, options);
|
|
3337
3395
|
});
|
|
3338
3396
|
var seedCmd = program2.command("seed").description("Manage seed data for projects");
|
|
3339
3397
|
seedCmd.command("export [path]").description("Export current Convex data as seed").action(async (path) => {
|
|
@@ -3342,15 +3400,16 @@ seedCmd.command("export [path]").description("Export current Convex data as seed
|
|
|
3342
3400
|
seedCmd.command("import [path]").description("Import seed data into current worktree").action(async (path) => {
|
|
3343
3401
|
await seedImport(path);
|
|
3344
3402
|
});
|
|
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 (
|
|
3346
|
-
await configCmd(
|
|
3403
|
+
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) => {
|
|
3404
|
+
await configCmd(options);
|
|
3347
3405
|
});
|
|
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,
|
|
3349
|
-
await pr(feature,
|
|
3350
|
-
title:
|
|
3351
|
-
body:
|
|
3352
|
-
draft:
|
|
3353
|
-
web:
|
|
3406
|
+
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").option("--ai", "Use Claude to generate title and description from diff").action(async (feature, options) => {
|
|
3407
|
+
await pr(feature, options.project, {
|
|
3408
|
+
title: options.title,
|
|
3409
|
+
body: options.body,
|
|
3410
|
+
draft: options.draft,
|
|
3411
|
+
web: options.web,
|
|
3412
|
+
ai: options.ai
|
|
3354
3413
|
});
|
|
3355
3414
|
});
|
|
3356
3415
|
program2.command("status").description("Show zdev status (alias for list)").action(async () => {
|