zdev 0.1.5 → 0.2.0
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 +159 -0
- package/package.json +1 -1
- package/src/commands/pr.ts +201 -0
- package/src/commands/start.ts +2 -0
- package/src/config.ts +2 -0
- package/src/index.ts +19 -0
package/dist/index.js
CHANGED
|
@@ -2663,6 +2663,8 @@ async function start(featureName, projectPath = ".", options = {}) {
|
|
|
2663
2663
|
frontendPort: ports.frontend,
|
|
2664
2664
|
convexPort: ports.convex,
|
|
2665
2665
|
funnelPath: routePath,
|
|
2666
|
+
worktreePath,
|
|
2667
|
+
publicUrl: publicUrl || undefined,
|
|
2666
2668
|
pids: {
|
|
2667
2669
|
frontend: frontendPid,
|
|
2668
2670
|
convex: convexPid
|
|
@@ -3069,6 +3071,155 @@ Commands:`);
|
|
|
3069
3071
|
}
|
|
3070
3072
|
}
|
|
3071
3073
|
|
|
3074
|
+
// src/commands/pr.ts
|
|
3075
|
+
import { resolve as resolve8, basename as basename4 } from "path";
|
|
3076
|
+
async function pr(featureName, projectPath = ".", options = {}) {
|
|
3077
|
+
const fullPath = resolve8(projectPath);
|
|
3078
|
+
let worktreePath = fullPath;
|
|
3079
|
+
let allocation;
|
|
3080
|
+
let projectName = getRepoName(fullPath) || basename4(fullPath);
|
|
3081
|
+
const config = loadConfig();
|
|
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}`));
|
|
3087
|
+
if (found) {
|
|
3088
|
+
allocation = found[1];
|
|
3089
|
+
projectName = allocation.project;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (allocation) {
|
|
3093
|
+
worktreePath = allocation.worktreePath || resolve8(config.worktreesDir, `${projectName}-${featureName}`);
|
|
3094
|
+
}
|
|
3095
|
+
} else {
|
|
3096
|
+
const cwd = process.cwd();
|
|
3097
|
+
const found = Object.entries(config.allocations).find(([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || ""));
|
|
3098
|
+
if (found) {
|
|
3099
|
+
allocation = found[1];
|
|
3100
|
+
featureName = found[0].split("-").slice(1).join("-");
|
|
3101
|
+
projectName = allocation.project;
|
|
3102
|
+
worktreePath = allocation.worktreePath || cwd;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
if (!isGitRepo(worktreePath)) {
|
|
3106
|
+
console.error(`❌ Not a git repository: ${worktreePath}`);
|
|
3107
|
+
process.exit(1);
|
|
3108
|
+
}
|
|
3109
|
+
const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath });
|
|
3110
|
+
if (!branchResult.success || !branchResult.stdout.trim()) {
|
|
3111
|
+
console.error("❌ Could not determine current branch");
|
|
3112
|
+
process.exit(1);
|
|
3113
|
+
}
|
|
3114
|
+
const branch = branchResult.stdout.trim();
|
|
3115
|
+
console.log(`\uD83D\uDC02 Creating PR for: ${branch}`);
|
|
3116
|
+
if (allocation) {
|
|
3117
|
+
console.log(` Project: ${projectName}`);
|
|
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: worktreePath });
|
|
3126
|
+
if (!authCheck.success) {
|
|
3127
|
+
console.error("❌ Not authenticated with GitHub. Run: gh auth login");
|
|
3128
|
+
process.exit(1);
|
|
3129
|
+
}
|
|
3130
|
+
console.log(`
|
|
3131
|
+
\uD83D\uDCE4 Pushing branch...`);
|
|
3132
|
+
const pushResult = run("git", ["push", "-u", "origin", branch], { cwd: worktreePath });
|
|
3133
|
+
if (!pushResult.success) {
|
|
3134
|
+
if (!pushResult.stderr.includes("Everything up-to-date")) {
|
|
3135
|
+
console.error(` Failed to push: ${pushResult.stderr}`);
|
|
3136
|
+
process.exit(1);
|
|
3137
|
+
}
|
|
3138
|
+
console.log(` Already up to date`);
|
|
3139
|
+
} else {
|
|
3140
|
+
console.log(` Pushed to origin/${branch}`);
|
|
3141
|
+
}
|
|
3142
|
+
let title = options.title;
|
|
3143
|
+
if (!title) {
|
|
3144
|
+
const featureForTitle = featureName || branch.replace(/^feature\//, "");
|
|
3145
|
+
title = featureForTitle.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3146
|
+
}
|
|
3147
|
+
let body = options.body || "";
|
|
3148
|
+
if (allocation && allocation.publicUrl) {
|
|
3149
|
+
const previewSection = `## Preview
|
|
3150
|
+
\uD83D\uDD17 ${allocation.publicUrl}
|
|
3151
|
+
|
|
3152
|
+
`;
|
|
3153
|
+
body = previewSection + body;
|
|
3154
|
+
} else if (config.devDomain && featureName && projectName) {
|
|
3155
|
+
const previewUrl = `https://${projectName}-${featureName}.${config.devDomain}`;
|
|
3156
|
+
const previewSection = `## Preview
|
|
3157
|
+
\uD83D\uDD17 ${previewUrl}
|
|
3158
|
+
|
|
3159
|
+
`;
|
|
3160
|
+
body = previewSection + body;
|
|
3161
|
+
}
|
|
3162
|
+
if (!body.includes("Created with zdev")) {
|
|
3163
|
+
body = body.trim() + `
|
|
3164
|
+
|
|
3165
|
+
---
|
|
3166
|
+
*Created with [zdev](https://github.com/5hanth/zdev)*`;
|
|
3167
|
+
}
|
|
3168
|
+
const existingPr = run("gh", ["pr", "view", branch, "--json", "url"], { cwd: worktreePath });
|
|
3169
|
+
if (existingPr.success) {
|
|
3170
|
+
try {
|
|
3171
|
+
const prData = JSON.parse(existingPr.stdout);
|
|
3172
|
+
console.log(`
|
|
3173
|
+
✅ PR already exists!`);
|
|
3174
|
+
console.log(`
|
|
3175
|
+
\uD83D\uDD17 ${prData.url}`);
|
|
3176
|
+
return;
|
|
3177
|
+
} catch {}
|
|
3178
|
+
}
|
|
3179
|
+
console.log(`
|
|
3180
|
+
\uD83D\uDCDD Creating pull request...`);
|
|
3181
|
+
const prArgs = ["pr", "create", "--title", title, "--body", body];
|
|
3182
|
+
if (options.draft) {
|
|
3183
|
+
prArgs.push("--draft");
|
|
3184
|
+
}
|
|
3185
|
+
if (options.web) {
|
|
3186
|
+
prArgs.push("--web");
|
|
3187
|
+
const webResult = run("gh", prArgs, { cwd: worktreePath });
|
|
3188
|
+
if (!webResult.success) {
|
|
3189
|
+
console.error(` Failed: ${webResult.stderr}`);
|
|
3190
|
+
process.exit(1);
|
|
3191
|
+
}
|
|
3192
|
+
console.log(` Opened in browser`);
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
const prResult = run("gh", prArgs, { cwd: worktreePath });
|
|
3196
|
+
if (!prResult.success) {
|
|
3197
|
+
if (prResult.stderr.includes("already exists")) {
|
|
3198
|
+
console.log(` PR already exists for this branch`);
|
|
3199
|
+
const viewResult = run("gh", ["pr", "view", "--json", "url"], { cwd: worktreePath });
|
|
3200
|
+
if (viewResult.success) {
|
|
3201
|
+
try {
|
|
3202
|
+
const prData = JSON.parse(viewResult.stdout);
|
|
3203
|
+
console.log(`
|
|
3204
|
+
\uD83D\uDD17 ${prData.url}`);
|
|
3205
|
+
} catch {}
|
|
3206
|
+
}
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
console.error(` Failed: ${prResult.stderr}`);
|
|
3210
|
+
process.exit(1);
|
|
3211
|
+
}
|
|
3212
|
+
const prUrl = prResult.stdout.trim();
|
|
3213
|
+
console.log(`
|
|
3214
|
+
✅ Pull request created!`);
|
|
3215
|
+
console.log(`
|
|
3216
|
+
\uD83D\uDD17 ${prUrl}`);
|
|
3217
|
+
if (allocation?.publicUrl) {
|
|
3218
|
+
console.log(`
|
|
3219
|
+
\uD83D\uDCF1 Preview: ${allocation.publicUrl}`);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3072
3223
|
// src/index.ts
|
|
3073
3224
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3074
3225
|
import { fileURLToPath } from "url";
|
|
@@ -3114,6 +3265,14 @@ seedCmd.command("import [path]").description("Import seed data into current work
|
|
|
3114
3265
|
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) => {
|
|
3115
3266
|
await configCmd(options);
|
|
3116
3267
|
});
|
|
3268
|
+
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) => {
|
|
3269
|
+
await pr(feature, options.project, {
|
|
3270
|
+
title: options.title,
|
|
3271
|
+
body: options.body,
|
|
3272
|
+
draft: options.draft,
|
|
3273
|
+
web: options.web
|
|
3274
|
+
});
|
|
3275
|
+
});
|
|
3117
3276
|
program2.command("status").description("Show zdev status (alias for list)").action(async () => {
|
|
3118
3277
|
await list({});
|
|
3119
3278
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { resolve, basename } from "path";
|
|
3
|
+
import { run, isGitRepo, getRepoName } from "../utils.js";
|
|
4
|
+
import { loadConfig, type WorktreeAllocation } from "../config.js";
|
|
5
|
+
|
|
6
|
+
export interface PrOptions {
|
|
7
|
+
title?: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
draft?: boolean;
|
|
10
|
+
web?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function pr(
|
|
14
|
+
featureName: string | undefined,
|
|
15
|
+
projectPath: string = ".",
|
|
16
|
+
options: PrOptions = {}
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const fullPath = resolve(projectPath);
|
|
19
|
+
|
|
20
|
+
// Check if we're in a worktree or need to find one
|
|
21
|
+
let worktreePath = fullPath;
|
|
22
|
+
let allocation: WorktreeAllocation | undefined;
|
|
23
|
+
let projectName = getRepoName(fullPath) || basename(fullPath);
|
|
24
|
+
|
|
25
|
+
const config = loadConfig();
|
|
26
|
+
|
|
27
|
+
// If featureName provided, find the allocation
|
|
28
|
+
if (featureName) {
|
|
29
|
+
const allocKey = `${projectName}-${featureName}`;
|
|
30
|
+
allocation = config.allocations[allocKey];
|
|
31
|
+
|
|
32
|
+
if (!allocation) {
|
|
33
|
+
// Try to find by just feature name
|
|
34
|
+
const found = Object.entries(config.allocations).find(
|
|
35
|
+
([key, alloc]) => key.endsWith(`-${featureName}`)
|
|
36
|
+
);
|
|
37
|
+
if (found) {
|
|
38
|
+
allocation = found[1];
|
|
39
|
+
projectName = allocation.project;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (allocation) {
|
|
44
|
+
worktreePath = allocation.worktreePath || resolve(config.worktreesDir, `${projectName}-${featureName}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Try to detect from current directory
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const found = Object.entries(config.allocations).find(
|
|
50
|
+
([_, alloc]) => alloc.worktreePath === cwd || cwd.startsWith(alloc.worktreePath || "")
|
|
51
|
+
);
|
|
52
|
+
if (found) {
|
|
53
|
+
allocation = found[1];
|
|
54
|
+
featureName = found[0].split("-").slice(1).join("-");
|
|
55
|
+
projectName = allocation.project;
|
|
56
|
+
worktreePath = allocation.worktreePath || cwd;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isGitRepo(worktreePath)) {
|
|
61
|
+
console.error(`❌ Not a git repository: ${worktreePath}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get current branch
|
|
66
|
+
const branchResult = run("git", ["branch", "--show-current"], { cwd: worktreePath });
|
|
67
|
+
if (!branchResult.success || !branchResult.stdout.trim()) {
|
|
68
|
+
console.error("❌ Could not determine current branch");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const branch = branchResult.stdout.trim();
|
|
72
|
+
|
|
73
|
+
console.log(`🐂 Creating PR for: ${branch}`);
|
|
74
|
+
if (allocation) {
|
|
75
|
+
console.log(` Project: ${projectName}`);
|
|
76
|
+
console.log(` Feature: ${featureName}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if gh CLI is available
|
|
80
|
+
const ghCheck = run("which", ["gh"]);
|
|
81
|
+
if (!ghCheck.success) {
|
|
82
|
+
console.error("❌ GitHub CLI (gh) not found. Install: https://cli.github.com");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if authenticated
|
|
87
|
+
const authCheck = run("gh", ["auth", "status"], { cwd: worktreePath });
|
|
88
|
+
if (!authCheck.success) {
|
|
89
|
+
console.error("❌ Not authenticated with GitHub. Run: gh auth login");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Push branch if not pushed
|
|
94
|
+
console.log(`\n📤 Pushing branch...`);
|
|
95
|
+
const pushResult = run("git", ["push", "-u", "origin", branch], { cwd: worktreePath });
|
|
96
|
+
if (!pushResult.success) {
|
|
97
|
+
// Check if it's just "already up to date"
|
|
98
|
+
if (!pushResult.stderr.includes("Everything up-to-date")) {
|
|
99
|
+
console.error(` Failed to push: ${pushResult.stderr}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
console.log(` Already up to date`);
|
|
103
|
+
} else {
|
|
104
|
+
console.log(` Pushed to origin/${branch}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build PR title
|
|
108
|
+
let title = options.title;
|
|
109
|
+
if (!title) {
|
|
110
|
+
// Generate from feature name or branch
|
|
111
|
+
const featureForTitle = featureName || branch.replace(/^feature\//, "");
|
|
112
|
+
title = featureForTitle
|
|
113
|
+
.split(/[-_]/)
|
|
114
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
115
|
+
.join(" ");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Build PR body
|
|
119
|
+
let body = options.body || "";
|
|
120
|
+
|
|
121
|
+
// Add preview URL if we have allocation
|
|
122
|
+
if (allocation && allocation.publicUrl) {
|
|
123
|
+
const previewSection = `## Preview\n🔗 ${allocation.publicUrl}\n\n`;
|
|
124
|
+
body = previewSection + body;
|
|
125
|
+
} else if (config.devDomain && featureName && projectName) {
|
|
126
|
+
// Try to construct preview URL
|
|
127
|
+
const previewUrl = `https://${projectName}-${featureName}.${config.devDomain}`;
|
|
128
|
+
const previewSection = `## Preview\n🔗 ${previewUrl}\n\n`;
|
|
129
|
+
body = previewSection + body;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add footer
|
|
133
|
+
if (!body.includes("Created with zdev")) {
|
|
134
|
+
body = body.trim() + "\n\n---\n*Created with [zdev](https://github.com/5hanth/zdev)*";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if PR already exists
|
|
138
|
+
const existingPr = run("gh", ["pr", "view", branch, "--json", "url"], { cwd: worktreePath });
|
|
139
|
+
if (existingPr.success) {
|
|
140
|
+
try {
|
|
141
|
+
const prData = JSON.parse(existingPr.stdout);
|
|
142
|
+
console.log(`\n✅ PR already exists!`);
|
|
143
|
+
console.log(`\n🔗 ${prData.url}`);
|
|
144
|
+
return;
|
|
145
|
+
} catch {
|
|
146
|
+
// Continue to create
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create PR
|
|
151
|
+
console.log(`\n📝 Creating pull request...`);
|
|
152
|
+
|
|
153
|
+
const prArgs = ["pr", "create", "--title", title, "--body", body];
|
|
154
|
+
|
|
155
|
+
if (options.draft) {
|
|
156
|
+
prArgs.push("--draft");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.web) {
|
|
160
|
+
prArgs.push("--web");
|
|
161
|
+
const webResult = run("gh", prArgs, { cwd: worktreePath });
|
|
162
|
+
if (!webResult.success) {
|
|
163
|
+
console.error(` Failed: ${webResult.stderr}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
console.log(` Opened in browser`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const prResult = run("gh", prArgs, { cwd: worktreePath });
|
|
171
|
+
|
|
172
|
+
if (!prResult.success) {
|
|
173
|
+
// Check if it's because PR already exists
|
|
174
|
+
if (prResult.stderr.includes("already exists")) {
|
|
175
|
+
console.log(` PR already exists for this branch`);
|
|
176
|
+
const viewResult = run("gh", ["pr", "view", "--json", "url"], { cwd: worktreePath });
|
|
177
|
+
if (viewResult.success) {
|
|
178
|
+
try {
|
|
179
|
+
const prData = JSON.parse(viewResult.stdout);
|
|
180
|
+
console.log(`\n🔗 ${prData.url}`);
|
|
181
|
+
} catch {
|
|
182
|
+
// Ignore
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.error(` Failed: ${prResult.stderr}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Extract PR URL from output
|
|
192
|
+
const prUrl = prResult.stdout.trim();
|
|
193
|
+
|
|
194
|
+
console.log(`\n✅ Pull request created!`);
|
|
195
|
+
console.log(`\n🔗 ${prUrl}`);
|
|
196
|
+
|
|
197
|
+
// Show preview URL again for easy access
|
|
198
|
+
if (allocation?.publicUrl) {
|
|
199
|
+
console.log(`\n📱 Preview: ${allocation.publicUrl}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
package/src/commands/start.ts
CHANGED
package/src/config.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { list } from "./commands/list.js";
|
|
|
8
8
|
import { clean } from "./commands/clean.js";
|
|
9
9
|
import { seedExport, seedImport } from "./commands/seed.js";
|
|
10
10
|
import { configCmd } from "./commands/config.js";
|
|
11
|
+
import { pr } from "./commands/pr.js";
|
|
11
12
|
import { readFileSync } from "fs";
|
|
12
13
|
import { fileURLToPath } from "url";
|
|
13
14
|
import { dirname, join } from "path";
|
|
@@ -124,6 +125,24 @@ program
|
|
|
124
125
|
await configCmd(options);
|
|
125
126
|
});
|
|
126
127
|
|
|
128
|
+
// zdev pr
|
|
129
|
+
program
|
|
130
|
+
.command("pr [feature]")
|
|
131
|
+
.description("Create a pull request for a feature branch")
|
|
132
|
+
.option("-p, --project <path>", "Project path", ".")
|
|
133
|
+
.option("-t, --title <title>", "PR title (auto-generated if not specified)")
|
|
134
|
+
.option("-b, --body <body>", "PR body (preview URL auto-added)")
|
|
135
|
+
.option("-d, --draft", "Create as draft PR")
|
|
136
|
+
.option("-w, --web", "Open in browser instead of CLI")
|
|
137
|
+
.action(async (feature, options) => {
|
|
138
|
+
await pr(feature, options.project, {
|
|
139
|
+
title: options.title,
|
|
140
|
+
body: options.body,
|
|
141
|
+
draft: options.draft,
|
|
142
|
+
web: options.web,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
127
146
|
// zdev status (alias for list)
|
|
128
147
|
program
|
|
129
148
|
.command("status")
|