@coderook/cli 0.2.0 → 0.3.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/cli/src/api.js +24 -0
- package/dist/cli/src/cli.js +154 -9
- package/dist/desktop-app/src/main/upload.js +1 -0
- package/package.json +1 -1
package/dist/cli/src/api.js
CHANGED
|
@@ -4,6 +4,11 @@ exports.whoami = whoami;
|
|
|
4
4
|
exports.projects = projects;
|
|
5
5
|
exports.findProject = findProject;
|
|
6
6
|
exports.health = health;
|
|
7
|
+
exports.mergeTracks = mergeTracks;
|
|
8
|
+
exports.mergeTrack = mergeTrack;
|
|
9
|
+
exports.resolveMergeConflict = resolveMergeConflict;
|
|
10
|
+
exports.applyMerge = applyMerge;
|
|
11
|
+
exports.cancelMerge = cancelMerge;
|
|
7
12
|
/** The small part of the API the command-line tool needs directly. */
|
|
8
13
|
const config_js_1 = require("./config.js");
|
|
9
14
|
async function call(route, options = {}) {
|
|
@@ -65,3 +70,22 @@ async function health() {
|
|
|
65
70
|
});
|
|
66
71
|
return (await response.json());
|
|
67
72
|
}
|
|
73
|
+
async function mergeTracks(repositoryId) {
|
|
74
|
+
const body = await call(`/v1/repositories/${repositoryId}/merge-tracks`);
|
|
75
|
+
return body.mergeTracks;
|
|
76
|
+
}
|
|
77
|
+
async function mergeTrack(id) {
|
|
78
|
+
return call(`/v1/merge-tracks/${id}`);
|
|
79
|
+
}
|
|
80
|
+
async function resolveMergeConflict(mergeTrackId, conflictId, resolution) {
|
|
81
|
+
await call(`/v1/merge-tracks/${mergeTrackId}/conflicts/${conflictId}`, {
|
|
82
|
+
method: "PUT",
|
|
83
|
+
body: { resolution },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function applyMerge(mergeTrackId) {
|
|
87
|
+
return call(`/v1/merge-tracks/${mergeTrackId}/apply`, { method: "POST" });
|
|
88
|
+
}
|
|
89
|
+
async function cancelMerge(mergeTrackId) {
|
|
90
|
+
await call(`/v1/merge-tracks/${mergeTrackId}/cancel`, { method: "POST" });
|
|
91
|
+
}
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -18,11 +18,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
const promises_1 = require("node:readline/promises");
|
|
19
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
20
20
|
const node_process_1 = __importDefault(require("node:process"));
|
|
21
|
+
const api_js_1 = require("./api.js");
|
|
21
22
|
const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
|
|
22
23
|
const upload_js_1 = require("../../desktop-app/src/main/upload.js");
|
|
23
24
|
const download_js_1 = require("../../desktop-app/src/main/download.js");
|
|
24
25
|
const cbx_js_1 = require("../../desktop-app/src/main/cbx.js");
|
|
25
|
-
const
|
|
26
|
+
const api_js_2 = require("./api.js");
|
|
26
27
|
const config_js_1 = require("./config.js");
|
|
27
28
|
/*
|
|
28
29
|
Read from the package rather than written twice. A hardcoded copy had
|
|
@@ -122,8 +123,8 @@ async function reconcile(folder, options = {}) {
|
|
|
122
123
|
const link = await (0, config_js_1.readLink)(folder);
|
|
123
124
|
const reference = link?.slug ?? node_path_1.default.basename(folder);
|
|
124
125
|
const project = link
|
|
125
|
-
? (await (0,
|
|
126
|
-
: await (0,
|
|
126
|
+
? (await (0, api_js_2.projects)()).find((candidate) => candidate.id === link.repositoryId)
|
|
127
|
+
: await (0, api_js_2.findProject)(reference);
|
|
127
128
|
if (!project?.versionCount) {
|
|
128
129
|
// Nothing saved on the account, so everything here is outstanding.
|
|
129
130
|
return { link: link ?? null, baseline: null, behind: null };
|
|
@@ -184,7 +185,7 @@ async function commandSignIn(parsed) {
|
|
|
184
185
|
}
|
|
185
186
|
await (0, config_js_1.storeToken)(token);
|
|
186
187
|
try {
|
|
187
|
-
const account = await (0,
|
|
188
|
+
const account = await (0, api_js_2.whoami)();
|
|
188
189
|
console.log(`Signed in as ${bold(account.displayName)} ${dim(`<${account.email}>`)} · ${account.plan}`);
|
|
189
190
|
console.log(dim(`Token stored in ${(0, config_js_1.configDirectory)()}`));
|
|
190
191
|
return 0;
|
|
@@ -201,12 +202,12 @@ async function commandSignOut() {
|
|
|
201
202
|
return 0;
|
|
202
203
|
}
|
|
203
204
|
async function commandWhoami() {
|
|
204
|
-
const account = await (0,
|
|
205
|
+
const account = await (0, api_js_2.whoami)();
|
|
205
206
|
console.log(`${bold(account.displayName)} <${account.email}> · ${account.plan}`);
|
|
206
207
|
return 0;
|
|
207
208
|
}
|
|
208
209
|
async function commandProjects() {
|
|
209
|
-
const all = await (0,
|
|
210
|
+
const all = await (0, api_js_2.projects)();
|
|
210
211
|
if (!all.length) {
|
|
211
212
|
console.log("No projects on this account yet.");
|
|
212
213
|
return 0;
|
|
@@ -320,6 +321,23 @@ async function commandSubmit(parsed) {
|
|
|
320
321
|
throw error;
|
|
321
322
|
}
|
|
322
323
|
done(line);
|
|
324
|
+
if (result.mergeTrack) {
|
|
325
|
+
/*
|
|
326
|
+
The upload is complete and kept, but the project has not changed —
|
|
327
|
+
calling this "saved" would be false, and the base is deliberately left
|
|
328
|
+
alone so the folder still knows what it was working from.
|
|
329
|
+
*/
|
|
330
|
+
console.log(`\nSomebody else saved first, and ${result.mergeTrack.conflicts.length === 1
|
|
331
|
+
? "one file overlaps"
|
|
332
|
+
: `${result.mergeTrack.conflicts.length} files overlap`}.`);
|
|
333
|
+
console.log(`Your work is complete and kept as ${accent(result.mergeTrack.reference)}:`);
|
|
334
|
+
for (const conflict of result.mergeTrack.conflicts.slice(0, 20)) {
|
|
335
|
+
console.log(` ${red(conflict.path)} ${dim(conflict.kind)}`);
|
|
336
|
+
}
|
|
337
|
+
console.log(`\nRun ${accent(`coderook merge ${result.mergeTrack.reference}`)} to decide,` +
|
|
338
|
+
` or ${accent("coderook merges")} to see everything waiting.`);
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
323
341
|
await (0, config_js_1.writeLink)(folder, {
|
|
324
342
|
repositoryId: result.repositoryId,
|
|
325
343
|
slug: link?.slug ?? node_path_1.default.basename(folder),
|
|
@@ -349,7 +367,7 @@ async function commandClone(parsed) {
|
|
|
349
367
|
console.error(red("Which project? Try: coderook clone <project>"));
|
|
350
368
|
return 1;
|
|
351
369
|
}
|
|
352
|
-
const project = await (0,
|
|
370
|
+
const project = await (0, api_js_2.findProject)(reference);
|
|
353
371
|
if (!project) {
|
|
354
372
|
console.error(red(`No project named ${reference} on this account.`));
|
|
355
373
|
return 1;
|
|
@@ -454,7 +472,7 @@ async function commandInspect(parsed) {
|
|
|
454
472
|
return 0;
|
|
455
473
|
}
|
|
456
474
|
async function commandDoctor() {
|
|
457
|
-
const service = await (0,
|
|
475
|
+
const service = await (0, api_js_2.health)().catch(() => null);
|
|
458
476
|
console.log(`CodeRook CLI ${VERSION} · Node ${node_process_1.default.versions.node} · ${node_process_1.default.platform}`);
|
|
459
477
|
console.log(`Config: ${(0, config_js_1.configDirectory)()}`);
|
|
460
478
|
console.log(service
|
|
@@ -467,7 +485,7 @@ async function commandDoctor() {
|
|
|
467
485
|
return 0;
|
|
468
486
|
}
|
|
469
487
|
try {
|
|
470
|
-
const account = await (0,
|
|
488
|
+
const account = await (0, api_js_2.whoami)();
|
|
471
489
|
console.log(`Account: ${account.email} · ${account.plan}`);
|
|
472
490
|
}
|
|
473
491
|
catch (error) {
|
|
@@ -489,6 +507,10 @@ ${bold("Working with a folder")}
|
|
|
489
507
|
coderook clone <project> [dir] Fetch a project into a new folder
|
|
490
508
|
coderook rules [folder] Show the ignore rules (--init to start one)
|
|
491
509
|
|
|
510
|
+
${bold("When somebody saved first")}
|
|
511
|
+
coderook merges Uploads of yours waiting on a decision
|
|
512
|
+
coderook merge <ref> Look at one (--mine --theirs --both --drop)
|
|
513
|
+
|
|
492
514
|
${bold("Bundles")}
|
|
493
515
|
coderook bundle [folder] [out] Pack the project as a .cbx
|
|
494
516
|
coderook unbundle <file> [dir] Extract a .cbx
|
|
@@ -506,6 +528,127 @@ ${bold("Options")}
|
|
|
506
528
|
|
|
507
529
|
${dim("The environment variable CODEROOK_TOKEN is used when set, so automated")}
|
|
508
530
|
${dim("runs need nothing on disk. CODEROOK_API_URL points at another service.")}`;
|
|
531
|
+
/**
|
|
532
|
+
* Everything waiting on a decision, for one folder's project.
|
|
533
|
+
*
|
|
534
|
+
* A diverted upload is easy to forget about — it is not an error and the
|
|
535
|
+
* project looks untouched — so this is the way to find out that work of
|
|
536
|
+
* yours is sitting somewhere, still complete, waiting.
|
|
537
|
+
*/
|
|
538
|
+
async function commandMerges(parsed) {
|
|
539
|
+
const folder = folderFor(parsed);
|
|
540
|
+
const link = await (0, config_js_1.readLink)(folder);
|
|
541
|
+
if (!link) {
|
|
542
|
+
console.error(red("This folder is not linked to a project on your account."));
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
const waiting = (await (0, api_js_1.mergeTracks)(link.repositoryId)).filter((merge) => merge.state === "open");
|
|
546
|
+
if (!waiting.length) {
|
|
547
|
+
console.log("Nothing is waiting to be merged.");
|
|
548
|
+
return 0;
|
|
549
|
+
}
|
|
550
|
+
for (const merge of waiting) {
|
|
551
|
+
const counts = merge.conflicts;
|
|
552
|
+
console.log(`${accent(merge.reference)} ${counts ? `${counts.unresolved} of ${counts.total} still to decide` : ""} ${dim(new Date(merge.createdAt).toLocaleString())}`);
|
|
553
|
+
}
|
|
554
|
+
console.log(dim(`
|
|
555
|
+
Run coderook merge <reference> to look at one.`));
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
/** Find a merge by the reference a person would type, such as M-2. */
|
|
559
|
+
async function findMerge(folder, reference) {
|
|
560
|
+
const link = await (0, config_js_1.readLink)(folder);
|
|
561
|
+
if (!link)
|
|
562
|
+
throw new Error("This folder is not linked to a project on your account.");
|
|
563
|
+
const all = await (0, api_js_1.mergeTracks)(link.repositoryId);
|
|
564
|
+
const found = all.find((merge) => merge.reference.toLowerCase() === reference.toLowerCase());
|
|
565
|
+
if (!found)
|
|
566
|
+
throw new Error(`No merge called ${reference} on this project.`);
|
|
567
|
+
return found;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Look at one merge, and optionally finish it.
|
|
571
|
+
*
|
|
572
|
+
* Deciding happens here rather than in a command of its own because the
|
|
573
|
+
* decision only means anything next to the thing it is about.
|
|
574
|
+
*/
|
|
575
|
+
async function commandMerge(parsed) {
|
|
576
|
+
const reference = parsed.positional[0];
|
|
577
|
+
if (!reference) {
|
|
578
|
+
console.error(red("Which merge? Try: coderook merge M-1"));
|
|
579
|
+
return 1;
|
|
580
|
+
}
|
|
581
|
+
const folder = folderFor({ ...parsed, positional: parsed.positional.slice(1) });
|
|
582
|
+
const summary = await findMerge(folder, reference);
|
|
583
|
+
const detail = await (0, api_js_1.mergeTrack)(summary.id);
|
|
584
|
+
if (hasFlag(parsed, "cancel")) {
|
|
585
|
+
await (0, api_js_1.cancelMerge)(summary.id);
|
|
586
|
+
console.log(`${summary.reference} cancelled. Your upload is still stored and nothing published was touched.`);
|
|
587
|
+
return 0;
|
|
588
|
+
}
|
|
589
|
+
const outstanding = detail.conflicts.filter((conflict) => !conflict.resolvedAt);
|
|
590
|
+
const decision = hasFlag(parsed, "mine")
|
|
591
|
+
? "take_candidate"
|
|
592
|
+
: hasFlag(parsed, "theirs")
|
|
593
|
+
? "take_target"
|
|
594
|
+
: hasFlag(parsed, "drop")
|
|
595
|
+
? "delete"
|
|
596
|
+
: hasFlag(parsed, "both", "keep-both")
|
|
597
|
+
? "keep_both"
|
|
598
|
+
: null;
|
|
599
|
+
if (decision) {
|
|
600
|
+
const only = flagText(parsed, "path");
|
|
601
|
+
const chosen = only
|
|
602
|
+
? outstanding.filter((conflict) => conflict.path === only)
|
|
603
|
+
: outstanding;
|
|
604
|
+
if (!chosen.length) {
|
|
605
|
+
console.error(red(only ? `${only} has no outstanding decision.` : "Nothing left to decide."));
|
|
606
|
+
return 1;
|
|
607
|
+
}
|
|
608
|
+
for (const conflict of chosen) {
|
|
609
|
+
await (0, api_js_1.resolveMergeConflict)(summary.id, conflict.id, decision);
|
|
610
|
+
console.log(` ${conflict.path} → ${decision === "take_candidate"
|
|
611
|
+
? "yours"
|
|
612
|
+
: decision === "take_target"
|
|
613
|
+
? "theirs"
|
|
614
|
+
: decision === "keep_both"
|
|
615
|
+
? `both, yours saved beside it`
|
|
616
|
+
: "removed"}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const now = await (0, api_js_1.mergeTrack)(summary.id);
|
|
620
|
+
console.log(`
|
|
621
|
+
${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
|
|
622
|
+
(now.provisional.ready
|
|
623
|
+
? "ready to apply"
|
|
624
|
+
: `${now.provisional.unresolvedPaths.length} still to decide`));
|
|
625
|
+
for (const conflict of now.conflicts) {
|
|
626
|
+
console.log(` ${conflict.resolvedAt ? accent("decided") : red("waiting")} ${conflict.path}` +
|
|
627
|
+
` ${dim(conflict.kind)}${conflict.resolution ? dim(` (${conflict.resolution})`) : ""}`);
|
|
628
|
+
}
|
|
629
|
+
if (hasFlag(parsed, "apply")) {
|
|
630
|
+
if (!now.provisional.ready) {
|
|
631
|
+
console.error(red("\nStill undecided files; nothing was applied."));
|
|
632
|
+
return 1;
|
|
633
|
+
}
|
|
634
|
+
const applied = await (0, api_js_1.applyMerge)(summary.id);
|
|
635
|
+
console.log(`
|
|
636
|
+
Applied as ${accent(`v${applied.version.sequence}`)}.`);
|
|
637
|
+
console.log(dim("Run coderook get to bring it down to this folder."));
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
if (!decision) {
|
|
641
|
+
console.log(dim(`
|
|
642
|
+
--mine keeps yours, --theirs keeps what was already saved,` +
|
|
643
|
+
` --drop removes the file.
|
|
644
|
+
Add --path <file> for one file, then --apply when ready.`));
|
|
645
|
+
}
|
|
646
|
+
else if (now.provisional.ready) {
|
|
647
|
+
console.log(dim(`
|
|
648
|
+
Run coderook merge ${now.mergeTrack.reference} --apply to publish it.`));
|
|
649
|
+
}
|
|
650
|
+
return 0;
|
|
651
|
+
}
|
|
509
652
|
const COMMANDS = {
|
|
510
653
|
"sign-in": commandSignIn,
|
|
511
654
|
login: commandSignIn,
|
|
@@ -522,6 +665,8 @@ const COMMANDS = {
|
|
|
522
665
|
bundle: commandBundle,
|
|
523
666
|
unbundle: commandUnbundle,
|
|
524
667
|
inspect: commandInspect,
|
|
668
|
+
merges: commandMerges,
|
|
669
|
+
merge: commandMerge,
|
|
525
670
|
doctor: () => commandDoctor(),
|
|
526
671
|
};
|
|
527
672
|
async function main(argv) {
|