bikeshed-now 0.11.0 → 0.14.1
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/README.md +15 -0
- package/package.json +1 -1
- package/src/index.mjs +174 -35
package/README.md
CHANGED
|
@@ -44,12 +44,20 @@ bikeshed comments storage-migration
|
|
|
44
44
|
# Fix that sentence, say what you changed in its thread, then publish the
|
|
45
45
|
# revision. Reply first: a thread belongs to the version it was written on.
|
|
46
46
|
bikeshed reply storage-migration <thread> "Rewritten to describe the dual-write period."
|
|
47
|
+
bikeshed resolve storage-migration <thread> # closes it, with a note saying you did
|
|
47
48
|
bikeshed update storage-migration proposal.md
|
|
48
49
|
|
|
49
50
|
# Given "Can suggest" rather than "Can edit"? Propose the change instead, for a
|
|
50
51
|
# person to accept or reject.
|
|
51
52
|
bikeshed suggest storage-migration "a single weekend" --with "two weekends"
|
|
52
53
|
|
|
54
|
+
# Own the document, or can approve on it? Decide somebody's suggestions: an
|
|
55
|
+
# accept goes into the draft, a rejection carries the reason its author is sent,
|
|
56
|
+
# and commit publishes the draft as one version.
|
|
57
|
+
bikeshed accept storage-migration <thread>
|
|
58
|
+
bikeshed reject storage-migration <thread> --reason "Already in section 3."
|
|
59
|
+
bikeshed commit storage-migration
|
|
60
|
+
|
|
53
61
|
# Something nobody has raised yet? Start a thread on the passage it is about.
|
|
54
62
|
bikeshed comment storage-migration "a single weekend" --body "Is the dual-write fortnight in this?"
|
|
55
63
|
```
|
|
@@ -72,6 +80,13 @@ bikeshed publish platform.md --id 01-platform
|
|
|
72
80
|
If the id is taken, the document is still published, at the id plus a few
|
|
73
81
|
random characters, and the CLI says so on stderr.
|
|
74
82
|
|
|
83
|
+
A rename changes the title and leaves the address alone, so links already sent
|
|
84
|
+
keep working:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bikeshed rename 01-platform Platform review, second pass
|
|
88
|
+
```
|
|
89
|
+
|
|
75
90
|
Wherever a command takes an id, it also takes the link you were sent. An
|
|
76
91
|
unlisted document you are not a member of needs the link, because its key is in
|
|
77
92
|
it; without the key the document answers as an id that does not exist.
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -53,13 +53,26 @@ const USAGE = `bikeshed ${VERSION} — publish and revise documents
|
|
|
53
53
|
bikeshed lineage <id> what it was forked from; for its owner, its forks
|
|
54
54
|
bikeshed get <id> print a document's source
|
|
55
55
|
bikeshed comments <id> unresolved feedback, with its anchored text
|
|
56
|
+
(--version <v> for an earlier version's)
|
|
56
57
|
bikeshed comment <id> <text> --body <comment>
|
|
57
58
|
start a thread on a passage
|
|
58
59
|
bikeshed reply <id> <thread> <text> reply in a thread
|
|
60
|
+
bikeshed resolve <id> <thread> close a thread once its point is dealt with
|
|
61
|
+
bikeshed reopen <id> <thread> open a resolved thread again
|
|
59
62
|
bikeshed suggest <id> <text> --with <new text>
|
|
60
63
|
propose new text for a passage, for a
|
|
61
64
|
person to accept or reject (--delete to
|
|
62
65
|
propose removing it)
|
|
66
|
+
bikeshed revise <id> <thread> --with <new text>
|
|
67
|
+
change your suggestion before it is
|
|
68
|
+
decided; what it said before is kept
|
|
69
|
+
bikeshed withdraw <id> <thread> take back your suggestion before it is
|
|
70
|
+
decided; its thread stays
|
|
71
|
+
bikeshed accept <id> <thread> accept a suggestion, as its owner or an
|
|
72
|
+
approver: the change goes into the draft
|
|
73
|
+
bikeshed reject <id> <thread> decline a suggestion, as its owner or an
|
|
74
|
+
approver (--reason <why>)
|
|
75
|
+
bikeshed commit <id> publish the draft as the next version
|
|
63
76
|
bikeshed open <id> open it in a browser
|
|
64
77
|
|
|
65
78
|
Where a command takes <id> it also takes the link you were sent. An unlisted
|
|
@@ -69,6 +82,7 @@ Organising
|
|
|
69
82
|
bikeshed folders the folder tree, with ids
|
|
70
83
|
bikeshed mkdir <name> [--parent <id>] create a folder
|
|
71
84
|
bikeshed mv <id> <folder|root> move a document
|
|
85
|
+
bikeshed rename <id> <title> change a document's title; its address stays
|
|
72
86
|
|
|
73
87
|
Skills
|
|
74
88
|
bikeshed skills skills this deployment publishes
|
|
@@ -93,15 +107,17 @@ Options
|
|
|
93
107
|
--title <text> title for publish or fork (fork default: the original's)
|
|
94
108
|
--id <id> with publish: the address, /d/<id> (default: from the title)
|
|
95
109
|
--folder <id> destination folder for publish or fork
|
|
96
|
-
--version <v> with fork: the version to copy
|
|
97
|
-
|
|
110
|
+
--version <v> with fork: the version to copy; with comments: the
|
|
111
|
+
version to read (default: current); on its own,
|
|
112
|
+
which version of bikeshed this is
|
|
98
113
|
--parent <id> parent folder for mkdir
|
|
99
114
|
--limit <n> with list: rows per page (default 20, max 50)
|
|
100
115
|
--cursor <id> with list: the page after the one that printed it
|
|
101
116
|
--all with list: every page; with comments: resolved too
|
|
102
|
-
--with <text> with suggest: what the passage should say instead
|
|
103
|
-
--delete with suggest: propose deleting the passage
|
|
104
|
-
--note <text> with suggest: why, for whoever decides
|
|
117
|
+
--with <text> with suggest or revise: what the passage should say instead
|
|
118
|
+
--delete with suggest or revise: propose deleting the passage
|
|
119
|
+
--note <text> with suggest or revise: why, for whoever decides
|
|
120
|
+
--reason <text> with reject: why, for the suggestion's author
|
|
105
121
|
--prefix <text> with suggest: words just before a passage that repeats
|
|
106
122
|
--suffix <text> with suggest: words just after it
|
|
107
123
|
--watch with publish/update: republish on every save
|
|
@@ -183,15 +199,9 @@ function isCi(value) {
|
|
|
183
199
|
|
|
184
200
|
async function browserLogin(flags) {
|
|
185
201
|
const interactive = Boolean(process.stdin.isTTY);
|
|
202
|
+
// Never asked: --origin, BIKESHED_ORIGIN, the last sign-in, then the default,
|
|
203
|
+
// the same order as every other command. The line below names it.
|
|
186
204
|
let where = resolveAuth(flags).origin;
|
|
187
|
-
if (interactive && !flags.origin) {
|
|
188
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
189
|
-
try {
|
|
190
|
-
where = (await rl.question(`Deployment [${where}]: `)).trim() || where;
|
|
191
|
-
} finally {
|
|
192
|
-
rl.close();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
205
|
try {
|
|
196
206
|
const url = new URL(where);
|
|
197
207
|
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error();
|
|
@@ -291,16 +301,17 @@ async function browserLogin(flags) {
|
|
|
291
301
|
);
|
|
292
302
|
}
|
|
293
303
|
|
|
294
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* Paste a token made at /account. The flow from before browser sign-in.
|
|
306
|
+
*
|
|
307
|
+
* Only the token is read from stdin. Asking for the deployment first read a
|
|
308
|
+
* piped token as the address, and printed it back inside the /account link.
|
|
309
|
+
*/
|
|
295
310
|
async function pasteLogin(flags) {
|
|
296
|
-
const { origin } = resolveAuth(flags);
|
|
311
|
+
const { origin: where } = resolveAuth(flags);
|
|
297
312
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
298
313
|
try {
|
|
299
|
-
|
|
300
|
-
flags.origin ||
|
|
301
|
-
(await rl.question(`Deployment [${origin}]: `)).trim() ||
|
|
302
|
-
origin;
|
|
303
|
-
console.log(`\nCreate a token at ${where}/account — choose "Command line".`);
|
|
314
|
+
console.log(`Create a token at ${where}/account — choose "Command line".`);
|
|
304
315
|
console.log("It is shown once, so copy it now.\n");
|
|
305
316
|
const token = (await rl.question("Token: ")).trim();
|
|
306
317
|
if (!token) fail("no token given");
|
|
@@ -511,6 +522,9 @@ async function comments(flags, [ref]) {
|
|
|
511
522
|
const { origin, token } = resolveAuth(flags);
|
|
512
523
|
const result = await callTool(origin, token, "get_comments", {
|
|
513
524
|
...docArgs(ref),
|
|
525
|
+
// Threads belong to the version they were opened on; a version in a link
|
|
526
|
+
// is not taken as a request for one, only the flag is.
|
|
527
|
+
...(typeof flags.version === "string" ? { version: flags.version } : {}),
|
|
514
528
|
includeResolved: Boolean(flags.all),
|
|
515
529
|
});
|
|
516
530
|
out(flags.json, result, () => {
|
|
@@ -523,11 +537,14 @@ async function comments(flags, [ref]) {
|
|
|
523
537
|
// which passage to change without opening the document.
|
|
524
538
|
console.log(`\n── ${thread.threadId}${thread.section ? ` (${thread.section})` : ""}`);
|
|
525
539
|
if (thread.quote) console.log(` “${thread.quote}”`);
|
|
526
|
-
// A suggested edit says what the passage would become,
|
|
527
|
-
//
|
|
540
|
+
// A suggested edit says what the passage would become, whether it is
|
|
541
|
+
// still open, and what its author proposed before changing it (SPEC §22).
|
|
528
542
|
if (thread.suggestion) {
|
|
529
|
-
const
|
|
530
|
-
console.log(` → ${
|
|
543
|
+
const said = (text) => (text === "" ? "(delete it)" : `“${text}”`);
|
|
544
|
+
console.log(` → ${said(thread.suggestion.replacement)} [${thread.suggestion.state}]`);
|
|
545
|
+
for (const earlier of thread.suggestion.revisions ?? []) {
|
|
546
|
+
console.log(` was ${said(earlier.replacement)}${earlier.note ? ` (${earlier.note})` : ""}`);
|
|
547
|
+
}
|
|
531
548
|
}
|
|
532
549
|
for (const comment of thread.comments) {
|
|
533
550
|
const via = comment.via && comment.via !== "web" ? ` [via ${comment.via}]` : "";
|
|
@@ -573,6 +590,27 @@ async function reply(flags, [ref, threadId, ...rest]) {
|
|
|
573
590
|
out(flags.json, result, () => console.log(`replied in ${threadId}`));
|
|
574
591
|
}
|
|
575
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Close a thread on the current version, or open it again. Either leaves a
|
|
595
|
+
* note in the thread saying who did it and through what, which is how the
|
|
596
|
+
* person who raised the point hears (SPEC §19).
|
|
597
|
+
*/
|
|
598
|
+
// `resolve` alone is node:path's, imported above.
|
|
599
|
+
async function resolveThread(flags, args) {
|
|
600
|
+
await setResolved(flags, args, "resolve");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function reopenThread(flags, args) {
|
|
604
|
+
await setResolved(flags, args, "reopen");
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function setResolved(flags, [ref, threadId, ...rest], verb) {
|
|
608
|
+
if (!ref || !threadId || rest.length) fail(`usage: bikeshed ${verb} <id> <thread>`);
|
|
609
|
+
const { origin, token } = resolveAuth(flags);
|
|
610
|
+
const result = await callTool(origin, token, `${verb}_thread`, { ...docArgs(ref), threadId });
|
|
611
|
+
out(flags.json, result, () => console.log(`${verb === "resolve" ? "resolved" : "reopened"} ${threadId}`));
|
|
612
|
+
}
|
|
613
|
+
|
|
576
614
|
/**
|
|
577
615
|
* Propose new text for one passage of a document's current version. The
|
|
578
616
|
* passage is quoted as a reader sees it, which is how `comments` prints it, so
|
|
@@ -582,16 +620,12 @@ async function suggest(flags, [ref, ...rest]) {
|
|
|
582
620
|
const exact = rest.join(" ");
|
|
583
621
|
const usage = "usage: bikeshed suggest <id> <text> (--with <new text> | --delete) [--note <why>]";
|
|
584
622
|
if (!ref || !exact) fail(usage);
|
|
585
|
-
const
|
|
586
|
-
if (replacing === Boolean(flags.delete)) fail(usage);
|
|
587
|
-
// An empty --with is what an unset shell variable sends; deleting a passage
|
|
588
|
-
// has to be asked for by name.
|
|
589
|
-
if (replacing && flags.with === "") fail("an empty --with would delete the passage; say --delete if you mean that");
|
|
623
|
+
const replacement = replacementFrom(flags, usage);
|
|
590
624
|
const { origin, token } = resolveAuth(flags);
|
|
591
625
|
const result = await callTool(origin, token, "suggest_edit", {
|
|
592
626
|
...docArgs(ref),
|
|
593
627
|
exact,
|
|
594
|
-
replacement
|
|
628
|
+
replacement,
|
|
595
629
|
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
596
630
|
...(typeof flags.prefix === "string" ? { prefix: flags.prefix } : {}),
|
|
597
631
|
...(typeof flags.suffix === "string" ? { suffix: flags.suffix } : {}),
|
|
@@ -601,6 +635,82 @@ async function suggest(flags, [ref, ...rest]) {
|
|
|
601
635
|
);
|
|
602
636
|
}
|
|
603
637
|
|
|
638
|
+
/** The replacement `suggest` and `revise` were given: --with, or --delete by name. */
|
|
639
|
+
function replacementFrom(flags, usage) {
|
|
640
|
+
const replacing = typeof flags.with === "string";
|
|
641
|
+
if (replacing === Boolean(flags.delete)) fail(usage);
|
|
642
|
+
// An empty --with is what an unset shell variable sends; deleting a passage
|
|
643
|
+
// has to be asked for by name.
|
|
644
|
+
if (replacing && flags.with === "") fail("an empty --with would delete the passage; say --delete if you mean that");
|
|
645
|
+
return replacing ? flags.with : "";
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Change the text of a suggestion of yours that nobody has decided yet. What
|
|
650
|
+
* it said before is kept, for whoever decides (SPEC §22).
|
|
651
|
+
*/
|
|
652
|
+
async function revise(flags, [ref, threadId, ...rest]) {
|
|
653
|
+
const usage = "usage: bikeshed revise <id> <thread> (--with <new text> | --delete) [--note <why>]";
|
|
654
|
+
if (!ref || !threadId || rest.length) fail(usage);
|
|
655
|
+
const replacement = replacementFrom(flags, usage);
|
|
656
|
+
const { origin, token } = resolveAuth(flags);
|
|
657
|
+
const result = await callTool(origin, token, "revise_suggestion", {
|
|
658
|
+
...docArgs(ref),
|
|
659
|
+
threadId,
|
|
660
|
+
replacement,
|
|
661
|
+
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
662
|
+
});
|
|
663
|
+
out(flags.json, result, () => console.log(`changed ${threadId}; what it said before is kept`));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Accept a suggestion, as the document's owner or an approver. The change is
|
|
668
|
+
* made in the draft, where the decisions of a review gather until `commit`
|
|
669
|
+
* publishes them as one version (SPEC §22).
|
|
670
|
+
*/
|
|
671
|
+
async function accept(flags, [ref, threadId, ...rest]) {
|
|
672
|
+
if (!ref || !threadId || rest.length) fail("usage: bikeshed accept <id> <thread>");
|
|
673
|
+
const { origin, token } = resolveAuth(flags);
|
|
674
|
+
// Deciding takes a real role on the document, never a link, so only the id is sent.
|
|
675
|
+
const result = await callTool(origin, token, "accept_suggestion", { id: docArgs(ref).id, threadId });
|
|
676
|
+
out(flags.json, result, () => console.log(`accepted ${threadId}; the change is in the draft — \`bikeshed commit\` publishes it`));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** Publish the draft as the document's next version, as "Save as new version" does in the app. */
|
|
680
|
+
async function commit(flags, [ref, ...rest]) {
|
|
681
|
+
if (!ref || rest.length) fail("usage: bikeshed commit <id>");
|
|
682
|
+
const { origin, token } = resolveAuth(flags);
|
|
683
|
+
const result = await callTool(origin, token, "commit_draft", { id: docArgs(ref).id });
|
|
684
|
+
out(flags.json, result, () =>
|
|
685
|
+
console.log(result.deduped ? `nothing new: the draft is ${result.versionId} already` : `published ${result.versionId}`),
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Decline a suggestion, as the document's owner or an approver. The passage
|
|
691
|
+
* stays as it is and the thread closes with a note giving the reason, which
|
|
692
|
+
* its author is sent (SPEC §22).
|
|
693
|
+
*/
|
|
694
|
+
async function reject(flags, [ref, threadId, ...rest]) {
|
|
695
|
+
if (!ref || !threadId || rest.length) fail("usage: bikeshed reject <id> <thread> [--reason <why>]");
|
|
696
|
+
const { origin, token } = resolveAuth(flags);
|
|
697
|
+
// Deciding takes a real role on the document, never a link, so only the id is sent.
|
|
698
|
+
const result = await callTool(origin, token, "reject_suggestion", {
|
|
699
|
+
id: docArgs(ref).id,
|
|
700
|
+
threadId,
|
|
701
|
+
...(typeof flags.reason === "string" ? { reason: flags.reason } : {}),
|
|
702
|
+
});
|
|
703
|
+
out(flags.json, result, () => console.log(`rejected ${threadId}`));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** Take back a suggestion of yours that nobody has decided yet; its thread stays. */
|
|
707
|
+
async function withdraw(flags, [ref, threadId, ...rest]) {
|
|
708
|
+
if (!ref || !threadId || rest.length) fail("usage: bikeshed withdraw <id> <thread>");
|
|
709
|
+
const { origin, token } = resolveAuth(flags);
|
|
710
|
+
const result = await callTool(origin, token, "withdraw_suggestion", { ...docArgs(ref), threadId });
|
|
711
|
+
out(flags.json, result, () => console.log(`withdrew ${threadId}`));
|
|
712
|
+
}
|
|
713
|
+
|
|
604
714
|
async function folders(flags) {
|
|
605
715
|
const { origin, token } = resolveAuth(flags);
|
|
606
716
|
const result = await callTool(origin, token, "list_folders", {});
|
|
@@ -711,13 +821,30 @@ async function mv(flags, [id, dest]) {
|
|
|
711
821
|
*/
|
|
712
822
|
if (!id || !dest) fail("usage: bikeshed mv <id> <folder|root>");
|
|
713
823
|
const { origin, token } = resolveAuth(flags);
|
|
714
|
-
|
|
824
|
+
// Moving is an editor's act, and a link's key only lets somebody read, so
|
|
825
|
+
// only the id goes along.
|
|
826
|
+
const doc = docArgs(id).id;
|
|
827
|
+
const args = dest === "root" ? { id: doc } : { id: doc, folder: dest };
|
|
715
828
|
const result = await callTool(origin, token, "move_document", args);
|
|
716
829
|
out(flags.json, result, () =>
|
|
717
|
-
console.log(result.folder ? `moved ${
|
|
830
|
+
console.log(result.folder ? `moved ${doc} into ${result.folder}` : `moved ${doc} to the root`),
|
|
718
831
|
);
|
|
719
832
|
}
|
|
720
833
|
|
|
834
|
+
/**
|
|
835
|
+
* Give a document a new title. Its address is its id, which never changes, so
|
|
836
|
+
* every link already sent keeps working (SPEC §4). The title is the rest of the
|
|
837
|
+
* line, so it needs no quotes.
|
|
838
|
+
*/
|
|
839
|
+
async function rename(flags, [ref, ...rest]) {
|
|
840
|
+
const title = rest.join(" ").trim();
|
|
841
|
+
if (!ref || !title) fail("usage: bikeshed rename <id> <title>");
|
|
842
|
+
const { origin, token } = resolveAuth(flags);
|
|
843
|
+
// Renaming takes no link key, as in the workspace list: only the id is sent.
|
|
844
|
+
const result = await callTool(origin, token, "rename_document", { id: docArgs(ref).id, title });
|
|
845
|
+
out(flags.json, result, () => console.log(`renamed ${result.id} to “${result.title}”`));
|
|
846
|
+
}
|
|
847
|
+
|
|
721
848
|
/**
|
|
722
849
|
* Skills are instructions, not code, and they are fetched from the DEPLOYMENT
|
|
723
850
|
* rather than bundled with this CLI on purpose: a deployment running an older
|
|
@@ -762,10 +889,13 @@ async function skillsCommand(flags, [sub, dir]) {
|
|
|
762
889
|
});
|
|
763
890
|
}
|
|
764
891
|
|
|
765
|
-
async function open(flags, [
|
|
766
|
-
if (!
|
|
892
|
+
async function open(flags, [ref]) {
|
|
893
|
+
if (!ref) fail("usage: bikeshed open <id>");
|
|
767
894
|
const { origin } = resolveAuth(flags);
|
|
768
|
-
|
|
895
|
+
// An unlisted document opens only with its key, and the link is the one place
|
|
896
|
+
// the key lives, so a link keeps it.
|
|
897
|
+
const { id, k } = docArgs(ref);
|
|
898
|
+
const url = `${origin}/d/${encodeURIComponent(id)}${k ? `?k=${encodeURIComponent(k)}` : ""}`;
|
|
769
899
|
// Best effort, and the address is printed either way. This used to spawn
|
|
770
900
|
// `start`, which is a cmd.exe builtin rather than a program, so on Windows
|
|
771
901
|
// the spawn failed and, with nothing listening for the error, crashed.
|
|
@@ -807,11 +937,19 @@ const COMMANDS = {
|
|
|
807
937
|
comments,
|
|
808
938
|
comment,
|
|
809
939
|
reply,
|
|
940
|
+
resolve: resolveThread,
|
|
941
|
+
reopen: reopenThread,
|
|
810
942
|
suggest,
|
|
943
|
+
revise,
|
|
944
|
+
withdraw,
|
|
945
|
+
accept,
|
|
946
|
+
reject,
|
|
947
|
+
commit,
|
|
811
948
|
open,
|
|
812
949
|
folders,
|
|
813
950
|
mkdir,
|
|
814
951
|
mv,
|
|
952
|
+
rename,
|
|
815
953
|
skills: skillsCommand,
|
|
816
954
|
};
|
|
817
955
|
|
|
@@ -842,6 +980,7 @@ async function main() {
|
|
|
842
980
|
paste: { type: "boolean", default: false },
|
|
843
981
|
"no-browser": { type: "boolean", default: false },
|
|
844
982
|
note: { type: "string" },
|
|
983
|
+
reason: { type: "string" },
|
|
845
984
|
body: { type: "string" },
|
|
846
985
|
prefix: { type: "string" },
|
|
847
986
|
suffix: { type: "string" },
|