@fractary/faber-cli 1.1.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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +163 -0
- package/dist/commands/logs/index.d.ts +11 -0
- package/dist/commands/logs/index.d.ts.map +1 -0
- package/dist/commands/logs/index.js +330 -0
- package/dist/commands/repo/index.d.ts +11 -0
- package/dist/commands/repo/index.d.ts.map +1 -0
- package/dist/commands/repo/index.js +612 -0
- package/dist/commands/spec/index.d.ts +11 -0
- package/dist/commands/spec/index.d.ts.map +1 -0
- package/dist/commands/spec/index.js +280 -0
- package/dist/commands/work/index.d.ts +11 -0
- package/dist/commands/work/index.d.ts.map +1 -0
- package/dist/commands/work/index.js +748 -0
- package/dist/commands/workflow/index.d.ts +31 -0
- package/dist/commands/workflow/index.d.ts.map +1 -0
- package/dist/commands/workflow/index.js +298 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/utils/errors.d.ts +14 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +44 -0
- package/dist/utils/output.d.ts +18 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +39 -0
- package/dist/utils/validation.d.ts +30 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +49 -0
- package/package.json +66 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo subcommand - Repository operations
|
|
3
|
+
*
|
|
4
|
+
* Provides branch, commit, pr, tag, and worktree operations via @fractary/faber RepoManager.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { RepoManager } from '@fractary/faber';
|
|
9
|
+
import { parseOptionalInteger, parseValidInteger } from '../../utils/validation.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create the repo command tree
|
|
12
|
+
*/
|
|
13
|
+
export function createRepoCommand() {
|
|
14
|
+
const repo = new Command('repo')
|
|
15
|
+
.description('Repository operations');
|
|
16
|
+
// Branch operations
|
|
17
|
+
const branch = new Command('branch')
|
|
18
|
+
.description('Branch operations');
|
|
19
|
+
branch.addCommand(createBranchCreateCommand());
|
|
20
|
+
branch.addCommand(createBranchDeleteCommand());
|
|
21
|
+
branch.addCommand(createBranchListCommand());
|
|
22
|
+
// Commit operations
|
|
23
|
+
repo.addCommand(createCommitCommand());
|
|
24
|
+
// Pull request operations
|
|
25
|
+
const pr = new Command('pr')
|
|
26
|
+
.description('Pull request operations');
|
|
27
|
+
pr.addCommand(createPRCreateCommand());
|
|
28
|
+
pr.addCommand(createPRListCommand());
|
|
29
|
+
pr.addCommand(createPRMergeCommand());
|
|
30
|
+
pr.addCommand(createPRReviewCommand());
|
|
31
|
+
// Tag operations
|
|
32
|
+
const tag = new Command('tag')
|
|
33
|
+
.description('Tag operations');
|
|
34
|
+
tag.addCommand(createTagCreateCommand());
|
|
35
|
+
tag.addCommand(createTagPushCommand());
|
|
36
|
+
tag.addCommand(createTagListCommand());
|
|
37
|
+
// Worktree operations
|
|
38
|
+
const worktree = new Command('worktree')
|
|
39
|
+
.description('Worktree operations');
|
|
40
|
+
worktree.addCommand(createWorktreeCreateCommand());
|
|
41
|
+
worktree.addCommand(createWorktreeListCommand());
|
|
42
|
+
worktree.addCommand(createWorktreeRemoveCommand());
|
|
43
|
+
worktree.addCommand(createWorktreeCleanupCommand());
|
|
44
|
+
// Push/Pull operations
|
|
45
|
+
repo.addCommand(createPushCommand());
|
|
46
|
+
repo.addCommand(createPullCommand());
|
|
47
|
+
repo.addCommand(createStatusCommand());
|
|
48
|
+
repo.addCommand(branch);
|
|
49
|
+
repo.addCommand(pr);
|
|
50
|
+
repo.addCommand(tag);
|
|
51
|
+
repo.addCommand(worktree);
|
|
52
|
+
return repo;
|
|
53
|
+
}
|
|
54
|
+
// Branch Commands
|
|
55
|
+
function createBranchCreateCommand() {
|
|
56
|
+
return new Command('create')
|
|
57
|
+
.description('Create a new branch')
|
|
58
|
+
.argument('<name>', 'Branch name')
|
|
59
|
+
.option('--base <branch>', 'Base branch')
|
|
60
|
+
.option('--checkout', 'Checkout after creation')
|
|
61
|
+
.option('--json', 'Output as JSON')
|
|
62
|
+
.action(async (name, options) => {
|
|
63
|
+
try {
|
|
64
|
+
const repoManager = new RepoManager();
|
|
65
|
+
const branch = await repoManager.createBranch(name, {
|
|
66
|
+
baseBranch: options.base,
|
|
67
|
+
checkout: options.checkout,
|
|
68
|
+
});
|
|
69
|
+
if (options.json) {
|
|
70
|
+
console.log(JSON.stringify({ status: 'success', data: branch }, null, 2));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(chalk.green(`✓ Created branch: ${branch.name}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
handleRepoError(error, options);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function createBranchDeleteCommand() {
|
|
82
|
+
return new Command('delete')
|
|
83
|
+
.description('Delete a branch')
|
|
84
|
+
.argument('<name>', 'Branch name')
|
|
85
|
+
.option('--location <where>', 'Delete location: local|remote|both', 'local')
|
|
86
|
+
.option('--force', 'Force delete even if not merged')
|
|
87
|
+
.option('--json', 'Output as JSON')
|
|
88
|
+
.action(async (name, options) => {
|
|
89
|
+
try {
|
|
90
|
+
const repoManager = new RepoManager();
|
|
91
|
+
await repoManager.deleteBranch(name, {
|
|
92
|
+
location: options.location,
|
|
93
|
+
force: options.force,
|
|
94
|
+
});
|
|
95
|
+
if (options.json) {
|
|
96
|
+
console.log(JSON.stringify({ status: 'success', data: { deleted: name } }, null, 2));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(chalk.green(`✓ Deleted branch: ${name}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
handleRepoError(error, options);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function createBranchListCommand() {
|
|
108
|
+
return new Command('list')
|
|
109
|
+
.description('List branches')
|
|
110
|
+
.option('--merged', 'Show only merged branches')
|
|
111
|
+
.option('--stale', 'Show stale branches')
|
|
112
|
+
.option('--pattern <glob>', 'Filter by pattern')
|
|
113
|
+
.option('--limit <n>', 'Limit results')
|
|
114
|
+
.option('--json', 'Output as JSON')
|
|
115
|
+
.action(async (options) => {
|
|
116
|
+
try {
|
|
117
|
+
const repoManager = new RepoManager();
|
|
118
|
+
const branches = await repoManager.listBranches({
|
|
119
|
+
merged: options.merged,
|
|
120
|
+
stale: options.stale,
|
|
121
|
+
pattern: options.pattern,
|
|
122
|
+
limit: parseOptionalInteger(options.limit, 'limit'),
|
|
123
|
+
});
|
|
124
|
+
if (options.json) {
|
|
125
|
+
console.log(JSON.stringify({ status: 'success', data: branches }, null, 2));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (branches.length === 0) {
|
|
129
|
+
console.log(chalk.yellow('No branches found'));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
branches.forEach((branch) => {
|
|
133
|
+
const prefix = branch.isDefault ? chalk.green('* ') : ' ';
|
|
134
|
+
const protection = branch.isProtected ? chalk.yellow(' [protected]') : '';
|
|
135
|
+
console.log(`${prefix}${branch.name}${protection}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
handleRepoError(error, options);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// Commit Command
|
|
146
|
+
function createCommitCommand() {
|
|
147
|
+
return new Command('commit')
|
|
148
|
+
.description('Create a commit')
|
|
149
|
+
.requiredOption('--message <msg>', 'Commit message')
|
|
150
|
+
.option('--type <type>', 'Commit type (feat, fix, chore, etc.)', 'feat')
|
|
151
|
+
.option('--scope <scope>', 'Commit scope')
|
|
152
|
+
.option('--work-id <id>', 'Associated work item ID')
|
|
153
|
+
.option('--breaking', 'Mark as breaking change')
|
|
154
|
+
.option('--all', 'Stage all changes before committing')
|
|
155
|
+
.option('--json', 'Output as JSON')
|
|
156
|
+
.action(async (options) => {
|
|
157
|
+
try {
|
|
158
|
+
const repoManager = new RepoManager();
|
|
159
|
+
// Stage all changes if requested
|
|
160
|
+
if (options.all) {
|
|
161
|
+
repoManager.stageAll();
|
|
162
|
+
}
|
|
163
|
+
const commit = repoManager.commit({
|
|
164
|
+
message: options.message,
|
|
165
|
+
type: options.type,
|
|
166
|
+
scope: options.scope,
|
|
167
|
+
workId: options.workId,
|
|
168
|
+
breaking: options.breaking,
|
|
169
|
+
});
|
|
170
|
+
if (options.json) {
|
|
171
|
+
console.log(JSON.stringify({ status: 'success', data: commit }, null, 2));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.log(chalk.green(`✓ Created commit: ${commit.sha.slice(0, 7)}`));
|
|
175
|
+
console.log(chalk.gray(` ${commit.message}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
handleRepoError(error, options);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// PR Commands
|
|
184
|
+
function createPRCreateCommand() {
|
|
185
|
+
return new Command('create')
|
|
186
|
+
.description('Create a pull request')
|
|
187
|
+
.requiredOption('--title <title>', 'PR title')
|
|
188
|
+
.option('--body <text>', 'PR body')
|
|
189
|
+
.option('--base <branch>', 'Base branch', 'main')
|
|
190
|
+
.option('--head <branch>', 'Head branch (current by default)')
|
|
191
|
+
.option('--draft', 'Create as draft')
|
|
192
|
+
.option('--json', 'Output as JSON')
|
|
193
|
+
.action(async (options) => {
|
|
194
|
+
try {
|
|
195
|
+
const repoManager = new RepoManager();
|
|
196
|
+
const pr = await repoManager.createPR({
|
|
197
|
+
title: options.title,
|
|
198
|
+
body: options.body,
|
|
199
|
+
base: options.base,
|
|
200
|
+
head: options.head,
|
|
201
|
+
draft: options.draft,
|
|
202
|
+
});
|
|
203
|
+
if (options.json) {
|
|
204
|
+
console.log(JSON.stringify({ status: 'success', data: pr }, null, 2));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.log(chalk.green(`✓ Created PR #${pr.number}: ${pr.title}`));
|
|
208
|
+
console.log(chalk.gray(` ${pr.url}`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
handleRepoError(error, options);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function createPRListCommand() {
|
|
217
|
+
return new Command('list')
|
|
218
|
+
.description('List pull requests')
|
|
219
|
+
.option('--state <state>', 'Filter by state (open, closed, all)', 'open')
|
|
220
|
+
.option('--author <user>', 'Filter by author')
|
|
221
|
+
.option('--json', 'Output as JSON')
|
|
222
|
+
.action(async (options) => {
|
|
223
|
+
try {
|
|
224
|
+
const repoManager = new RepoManager();
|
|
225
|
+
const prs = await repoManager.listPRs({
|
|
226
|
+
state: options.state,
|
|
227
|
+
author: options.author,
|
|
228
|
+
});
|
|
229
|
+
if (options.json) {
|
|
230
|
+
console.log(JSON.stringify({ status: 'success', data: prs }, null, 2));
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
if (prs.length === 0) {
|
|
234
|
+
console.log(chalk.yellow('No pull requests found'));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
prs.forEach((pr) => {
|
|
238
|
+
console.log(`#${pr.number} ${pr.title} [${pr.state}]`);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
handleRepoError(error, options);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function createPRMergeCommand() {
|
|
249
|
+
return new Command('merge')
|
|
250
|
+
.description('Merge a pull request')
|
|
251
|
+
.argument('<number>', 'PR number')
|
|
252
|
+
.option('--strategy <strategy>', 'Merge strategy (merge, squash, rebase)', 'squash')
|
|
253
|
+
.option('--delete-branch', 'Delete branch after merge')
|
|
254
|
+
.option('--json', 'Output as JSON')
|
|
255
|
+
.action(async (number, options) => {
|
|
256
|
+
try {
|
|
257
|
+
const repoManager = new RepoManager();
|
|
258
|
+
const pr = await repoManager.mergePR(parseValidInteger(number, 'PR number'), {
|
|
259
|
+
strategy: options.strategy,
|
|
260
|
+
deleteBranch: options.deleteBranch,
|
|
261
|
+
});
|
|
262
|
+
if (options.json) {
|
|
263
|
+
console.log(JSON.stringify({ status: 'success', data: pr }, null, 2));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(chalk.green(`✓ Merged PR #${number}`));
|
|
267
|
+
if (options.deleteBranch) {
|
|
268
|
+
console.log(chalk.gray(' Branch deleted'));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
handleRepoError(error, options);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function createPRReviewCommand() {
|
|
278
|
+
return new Command('review')
|
|
279
|
+
.description('Review a pull request')
|
|
280
|
+
.argument('<number>', 'PR number')
|
|
281
|
+
.option('--approve', 'Approve the PR')
|
|
282
|
+
.option('--request-changes', 'Request changes')
|
|
283
|
+
.option('--comment <text>', 'Review comment')
|
|
284
|
+
.option('--json', 'Output as JSON')
|
|
285
|
+
.action(async (number, options) => {
|
|
286
|
+
try {
|
|
287
|
+
const repoManager = new RepoManager();
|
|
288
|
+
const action = options.approve ? 'approve' :
|
|
289
|
+
options.requestChanges ? 'request_changes' : 'comment';
|
|
290
|
+
await repoManager.reviewPR(parseValidInteger(number, 'PR number'), {
|
|
291
|
+
action,
|
|
292
|
+
comment: options.comment,
|
|
293
|
+
});
|
|
294
|
+
if (options.json) {
|
|
295
|
+
console.log(JSON.stringify({ status: 'success', data: { reviewed: number } }, null, 2));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const action = options.approve ? 'Approved' : options.requestChanges ? 'Requested changes on' : 'Commented on';
|
|
299
|
+
console.log(chalk.green(`✓ ${action} PR #${number}`));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
handleRepoError(error, options);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// Tag Commands
|
|
308
|
+
function createTagCreateCommand() {
|
|
309
|
+
return new Command('create')
|
|
310
|
+
.description('Create a tag')
|
|
311
|
+
.argument('<name>', 'Tag name')
|
|
312
|
+
.option('--message <text>', 'Tag message')
|
|
313
|
+
.option('--sign', 'Sign the tag')
|
|
314
|
+
.option('--json', 'Output as JSON')
|
|
315
|
+
.action(async (name, options) => {
|
|
316
|
+
try {
|
|
317
|
+
const repoManager = new RepoManager();
|
|
318
|
+
repoManager.createTag(name, {
|
|
319
|
+
name,
|
|
320
|
+
message: options.message,
|
|
321
|
+
sign: options.sign,
|
|
322
|
+
});
|
|
323
|
+
if (options.json) {
|
|
324
|
+
console.log(JSON.stringify({ status: 'success', data: { name } }, null, 2));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.log(chalk.green(`✓ Created tag: ${name}`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
handleRepoError(error, options);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function createTagPushCommand() {
|
|
336
|
+
return new Command('push')
|
|
337
|
+
.description('Push tag(s) to remote')
|
|
338
|
+
.argument('<name>', 'Tag name or "all"')
|
|
339
|
+
.option('--remote <name>', 'Remote name', 'origin')
|
|
340
|
+
.option('--json', 'Output as JSON')
|
|
341
|
+
.action(async (name, options) => {
|
|
342
|
+
try {
|
|
343
|
+
const repoManager = new RepoManager();
|
|
344
|
+
repoManager.pushTag(name, options.remote);
|
|
345
|
+
if (options.json) {
|
|
346
|
+
console.log(JSON.stringify({ status: 'success', data: { pushed: name } }, null, 2));
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
console.log(chalk.green(`✓ Pushed tag: ${name}`));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
handleRepoError(error, options);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function createTagListCommand() {
|
|
358
|
+
return new Command('list')
|
|
359
|
+
.description('List tags')
|
|
360
|
+
.option('--pattern <glob>', 'Filter by pattern')
|
|
361
|
+
.option('--latest <n>', 'Show only latest N tags')
|
|
362
|
+
.option('--json', 'Output as JSON')
|
|
363
|
+
.action(async (options) => {
|
|
364
|
+
try {
|
|
365
|
+
const repoManager = new RepoManager();
|
|
366
|
+
const tags = repoManager.listTags({
|
|
367
|
+
pattern: options.pattern,
|
|
368
|
+
latest: parseOptionalInteger(options.latest, 'latest'),
|
|
369
|
+
});
|
|
370
|
+
if (options.json) {
|
|
371
|
+
console.log(JSON.stringify({ status: 'success', data: tags }, null, 2));
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
if (tags.length === 0) {
|
|
375
|
+
console.log(chalk.yellow('No tags found'));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
tags.forEach((tag) => {
|
|
379
|
+
console.log(tag.name);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
handleRepoError(error, options);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// Worktree Commands
|
|
390
|
+
function createWorktreeCreateCommand() {
|
|
391
|
+
return new Command('create')
|
|
392
|
+
.description('Create a worktree')
|
|
393
|
+
.argument('<branch>', 'Branch name')
|
|
394
|
+
.option('--path <path>', 'Worktree path')
|
|
395
|
+
.option('--work-id <id>', 'Associated work item ID')
|
|
396
|
+
.option('--json', 'Output as JSON')
|
|
397
|
+
.action(async (branch, options) => {
|
|
398
|
+
try {
|
|
399
|
+
const repoManager = new RepoManager();
|
|
400
|
+
const worktree = repoManager.createWorktree({
|
|
401
|
+
branch,
|
|
402
|
+
path: options.path,
|
|
403
|
+
workId: options.workId,
|
|
404
|
+
});
|
|
405
|
+
if (options.json) {
|
|
406
|
+
console.log(JSON.stringify({ status: 'success', data: worktree }, null, 2));
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
console.log(chalk.green(`✓ Created worktree: ${worktree.path}`));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
handleRepoError(error, options);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function createWorktreeListCommand() {
|
|
418
|
+
return new Command('list')
|
|
419
|
+
.description('List worktrees')
|
|
420
|
+
.option('--json', 'Output as JSON')
|
|
421
|
+
.action(async (options) => {
|
|
422
|
+
try {
|
|
423
|
+
const repoManager = new RepoManager();
|
|
424
|
+
const worktrees = repoManager.listWorktrees();
|
|
425
|
+
if (options.json) {
|
|
426
|
+
console.log(JSON.stringify({ status: 'success', data: worktrees }, null, 2));
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
if (worktrees.length === 0) {
|
|
430
|
+
console.log(chalk.yellow('No worktrees found'));
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
worktrees.forEach((wt) => {
|
|
434
|
+
console.log(`${wt.branch} → ${wt.path}`);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
handleRepoError(error, options);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function createWorktreeRemoveCommand() {
|
|
445
|
+
return new Command('remove')
|
|
446
|
+
.description('Remove a worktree')
|
|
447
|
+
.argument('<path>', 'Worktree path')
|
|
448
|
+
.option('--force', 'Force removal')
|
|
449
|
+
.option('--json', 'Output as JSON')
|
|
450
|
+
.action(async (path, options) => {
|
|
451
|
+
try {
|
|
452
|
+
const repoManager = new RepoManager();
|
|
453
|
+
repoManager.removeWorktree(path, options.force);
|
|
454
|
+
if (options.json) {
|
|
455
|
+
console.log(JSON.stringify({ status: 'success', data: { removed: path } }, null, 2));
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.log(chalk.green(`✓ Removed worktree: ${path}`));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
handleRepoError(error, options);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function createWorktreeCleanupCommand() {
|
|
467
|
+
return new Command('cleanup')
|
|
468
|
+
.description('Clean up worktrees')
|
|
469
|
+
.option('--merged', 'Clean merged worktrees')
|
|
470
|
+
.option('--stale', 'Clean stale worktrees')
|
|
471
|
+
.option('--dry-run', 'Show what would be cleaned')
|
|
472
|
+
.option('--json', 'Output as JSON')
|
|
473
|
+
.action(async (options) => {
|
|
474
|
+
try {
|
|
475
|
+
const repoManager = new RepoManager();
|
|
476
|
+
const result = await repoManager.cleanupWorktrees({
|
|
477
|
+
merged: options.merged,
|
|
478
|
+
stale: options.stale,
|
|
479
|
+
dryRun: options.dryRun,
|
|
480
|
+
});
|
|
481
|
+
if (options.json) {
|
|
482
|
+
console.log(JSON.stringify({ status: 'success', data: result }, null, 2));
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const prefix = options.dryRun ? 'Would clean' : 'Cleaned';
|
|
486
|
+
if (result.removed.length === 0) {
|
|
487
|
+
console.log(chalk.yellow('No worktrees to clean'));
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
result.removed.forEach((wt) => {
|
|
491
|
+
console.log(chalk.green(`✓ ${prefix}: ${wt}`));
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
handleRepoError(error, options);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
// Push/Pull Commands
|
|
502
|
+
function createPushCommand() {
|
|
503
|
+
return new Command('push')
|
|
504
|
+
.description('Push to remote')
|
|
505
|
+
.option('--remote <name>', 'Remote name', 'origin')
|
|
506
|
+
.option('--set-upstream', 'Set upstream tracking')
|
|
507
|
+
.option('--force', 'Force push (use with caution)')
|
|
508
|
+
.option('--json', 'Output as JSON')
|
|
509
|
+
.action(async (options) => {
|
|
510
|
+
try {
|
|
511
|
+
const repoManager = new RepoManager();
|
|
512
|
+
repoManager.push({
|
|
513
|
+
remote: options.remote,
|
|
514
|
+
setUpstream: options.setUpstream,
|
|
515
|
+
force: options.force,
|
|
516
|
+
});
|
|
517
|
+
if (options.json) {
|
|
518
|
+
console.log(JSON.stringify({ status: 'success', data: { pushed: true } }, null, 2));
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
console.log(chalk.green('✓ Pushed to remote'));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
handleRepoError(error, options);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function createPullCommand() {
|
|
530
|
+
return new Command('pull')
|
|
531
|
+
.description('Pull from remote')
|
|
532
|
+
.option('--rebase', 'Use rebase instead of merge')
|
|
533
|
+
.option('--remote <name>', 'Remote name', 'origin')
|
|
534
|
+
.option('--json', 'Output as JSON')
|
|
535
|
+
.action(async (options) => {
|
|
536
|
+
try {
|
|
537
|
+
const repoManager = new RepoManager();
|
|
538
|
+
repoManager.pull({
|
|
539
|
+
rebase: options.rebase,
|
|
540
|
+
remote: options.remote,
|
|
541
|
+
});
|
|
542
|
+
if (options.json) {
|
|
543
|
+
console.log(JSON.stringify({ status: 'success', data: { pulled: true } }, null, 2));
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
console.log(chalk.green('✓ Pulled from remote'));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
handleRepoError(error, options);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
function createStatusCommand() {
|
|
555
|
+
return new Command('status')
|
|
556
|
+
.description('Show repository status')
|
|
557
|
+
.option('--json', 'Output as JSON')
|
|
558
|
+
.action(async (options) => {
|
|
559
|
+
try {
|
|
560
|
+
const repoManager = new RepoManager();
|
|
561
|
+
const status = repoManager.getStatus();
|
|
562
|
+
const currentBranch = repoManager.getCurrentBranch();
|
|
563
|
+
if (options.json) {
|
|
564
|
+
const data = { branchName: currentBranch, ...status };
|
|
565
|
+
console.log(JSON.stringify({ status: 'success', data }, null, 2));
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
const isClean = status.staged.length === 0 &&
|
|
569
|
+
status.modified.length === 0 &&
|
|
570
|
+
status.untracked.length === 0 &&
|
|
571
|
+
status.conflicts.length === 0;
|
|
572
|
+
console.log(chalk.bold(`Branch: ${currentBranch}`));
|
|
573
|
+
console.log(`Clean: ${isClean ? chalk.green('yes') : chalk.yellow('no')}`);
|
|
574
|
+
if (status.ahead > 0) {
|
|
575
|
+
console.log(chalk.cyan(`Ahead: ${status.ahead} commit(s)`));
|
|
576
|
+
}
|
|
577
|
+
if (status.behind > 0) {
|
|
578
|
+
console.log(chalk.yellow(`Behind: ${status.behind} commit(s)`));
|
|
579
|
+
}
|
|
580
|
+
if (status.staged.length > 0) {
|
|
581
|
+
console.log(chalk.green(`Staged: ${status.staged.length} file(s)`));
|
|
582
|
+
}
|
|
583
|
+
if (status.modified.length > 0) {
|
|
584
|
+
console.log(chalk.yellow(`Modified: ${status.modified.length} file(s)`));
|
|
585
|
+
}
|
|
586
|
+
if (status.untracked.length > 0) {
|
|
587
|
+
console.log(chalk.gray(`Untracked: ${status.untracked.length} file(s)`));
|
|
588
|
+
}
|
|
589
|
+
if (status.conflicts.length > 0) {
|
|
590
|
+
console.log(chalk.red(`Conflicts: ${status.conflicts.length} file(s)`));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
handleRepoError(error, options);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
// Error handling
|
|
600
|
+
function handleRepoError(error, options) {
|
|
601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
602
|
+
if (options.json) {
|
|
603
|
+
console.error(JSON.stringify({
|
|
604
|
+
status: 'error',
|
|
605
|
+
error: { code: 'REPO_ERROR', message },
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
console.error(chalk.red('Error:'), message);
|
|
610
|
+
}
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec subcommand - Specification management
|
|
3
|
+
*
|
|
4
|
+
* Provides spec operations via @fractary/faber SpecManager.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Create the spec command tree
|
|
9
|
+
*/
|
|
10
|
+
export declare function createSpecCommand(): Command;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/spec/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAc3C"}
|