bikeshed-now 0.16.0 → 0.18.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/README.md +40 -1
- package/package.json +1 -1
- package/src/index.mjs +139 -46
package/README.md
CHANGED
|
@@ -82,7 +82,10 @@ bikeshed publish platform.md --id 01-platform
|
|
|
82
82
|
`publish` says where it filed the document and who can read it there.
|
|
83
83
|
`bikeshed folders` counts who can read each folder, and `bikeshed folder <id>`
|
|
84
84
|
names them: filing a document in a folder shares it with all of them, and
|
|
85
|
-
nobody is emailed.
|
|
85
|
+
nobody is emailed. Under a folder shared by link or made public it is readable
|
|
86
|
+
by anyone holding the link, or anyone, and the output says through which
|
|
87
|
+
folder. Given a folder's link, `bikeshed folder <link>` lists what that link
|
|
88
|
+
opens, to read only.
|
|
86
89
|
|
|
87
90
|
Ids are shared by the whole deployment and never change, so choose one every
|
|
88
91
|
time: put the date first on something time-bound
|
|
@@ -146,6 +149,13 @@ to change visibility, `--public-edit true|false` to change open editing, or
|
|
|
146
149
|
Omitted settings stay unchanged. These commands are owner-only, including on
|
|
147
150
|
expired documents; the returned URL includes an unlisted key when needed.
|
|
148
151
|
|
|
152
|
+
`bikeshed visibility folder <id|link>` does the same for a folder, without
|
|
153
|
+
`--public-edit`. A folder's link or public level opens everything beneath it to
|
|
154
|
+
read, without an account: its subfolders, documents filed later, and documents
|
|
155
|
+
that are private on their own. Its expiry ends only that; the people it is
|
|
156
|
+
shared with keep their access. `bikeshed visibility document <id>` names a
|
|
157
|
+
document explicitly, for one whose id is `folder`.
|
|
158
|
+
|
|
149
159
|
`bikeshed rename-folder <id> <name>` and
|
|
150
160
|
`bikeshed move-folder <id> <folder|root>` require edit rights.
|
|
151
161
|
`bikeshed delete-folder <id>` is owner-only: contents move to its parent or the
|
|
@@ -166,3 +176,32 @@ reviewer can always tell which feedback came from a person.
|
|
|
166
176
|
|
|
167
177
|
Apache-2.0. The deployment it talks to is the same repository:
|
|
168
178
|
[DeputyHumans/bikeshed-now](https://github.com/DeputyHumans/bikeshed-now).
|
|
179
|
+
|
|
180
|
+
### Previous versions and write guards
|
|
181
|
+
|
|
182
|
+
Use `list_versions({id, limit:20, cursor})` or `bikeshed versions <id|link>
|
|
183
|
+
--all --json` to discover published history. Pages are newest first, default 20,
|
|
184
|
+
maximum 50; pass `nextCursor` as `cursor` until null. Each row has a pinned URL.
|
|
185
|
+
History uses the document's current access rules and expiry. A missing or
|
|
186
|
+
withdrawn version is an error, never a request for the latest version.
|
|
187
|
+
|
|
188
|
+
Read a chosen version with `get_document({id, version:"v2"})` and
|
|
189
|
+
`get_comments({id, version:"v2", includeResolved:true})`, or
|
|
190
|
+
`bikeshed get <id> --version v2` and `bikeshed comments <id> --version v2 --all`.
|
|
191
|
+
Read results name `currentVersion` and `isCurrent`; historical write capabilities
|
|
192
|
+
are false. Existing comments and suggestions remain readable as an archive.
|
|
193
|
+
Older versions cannot be edited or receive, revise, delete, reply to, resolve,
|
|
194
|
+
reopen, accept, reject, claim or withdraw feedback. Fork one to make a new,
|
|
195
|
+
independent document with `fork_document({id, version:"v2"})`.
|
|
196
|
+
|
|
197
|
+
CLI `get`, `comments`, `fork`, and `open` preserve a version in a pasted
|
|
198
|
+
`/d/<id>/<version>?k=...` URL. Feedback commands also preserve it as a write
|
|
199
|
+
guard. `--version` must agree with the URL; conflicts fail. Keys are retained.
|
|
200
|
+
Document-wide sharing, folder and lineage operations still address the document.
|
|
201
|
+
|
|
202
|
+
Before changing feedback, read the latest version and its feedback, then pass
|
|
203
|
+
that `version` to every feedback tool (`--version` in the CLI). Pass
|
|
204
|
+
`baseVersion` to `update_document` and `commit_draft` (`--base` on `bikeshed
|
|
205
|
+
update` and `bikeshed commit`). A versioned URL supplies the CLI base too.
|
|
206
|
+
If the head changes, the server refuses the write. Read the new head and
|
|
207
|
+
reassess the request; never silently redirect a historical request to it.
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -51,7 +51,8 @@ const USAGE = `bikeshed ${VERSION} — publish and revise documents
|
|
|
51
51
|
bikeshed update <id> <file> publish a new version
|
|
52
52
|
bikeshed fork <id> your own copy, which remembers its original
|
|
53
53
|
bikeshed lineage <id> what it was forked from; for its owner, its forks
|
|
54
|
-
bikeshed get <id>
|
|
54
|
+
bikeshed get <id> [--version <v>] print a published version's source
|
|
55
|
+
bikeshed versions <id> list published history (--all for every page)
|
|
55
56
|
bikeshed comments <id> unresolved feedback, with its anchored text
|
|
56
57
|
(--version <v> for an earlier version's)
|
|
57
58
|
bikeshed changes <id> --since <cursor|last|all>
|
|
@@ -83,7 +84,8 @@ const USAGE = `bikeshed ${VERSION} — publish and revise documents
|
|
|
83
84
|
|
|
84
85
|
Organising
|
|
85
86
|
bikeshed folders the folder tree, with ids and readers
|
|
86
|
-
bikeshed folder <id>
|
|
87
|
+
bikeshed folder <id|link> who can read a folder, and so what is in it;
|
|
88
|
+
with a folder's link, what the link opens
|
|
87
89
|
bikeshed mkdir <name> [--parent <id>] create a folder
|
|
88
90
|
bikeshed mv <id> <folder|root> move a document
|
|
89
91
|
bikeshed rename <id> <title> change a document's title; its address stays
|
|
@@ -95,6 +97,9 @@ Management
|
|
|
95
97
|
bikeshed visibility <id> [private|unlisted|public]
|
|
96
98
|
--public-edit true|false change open editing
|
|
97
99
|
--expires <ISO timestamp|none> set or clear expiry; omitted settings stay unchanged
|
|
100
|
+
bikeshed visibility folder <id> [private|unlisted|public] [--expires <ISO timestamp|none>]
|
|
101
|
+
a folder's link or public level opens EVERYTHING
|
|
102
|
+
beneath it to read; its expiry ends only that
|
|
98
103
|
bikeshed rename-folder <id> <name> rename a folder you can edit
|
|
99
104
|
bikeshed move-folder <id> <folder|root> move a folder and recompute inherited access
|
|
100
105
|
bikeshed delete-folder <id> owner-only; reparent contents, retain documents
|
|
@@ -122,9 +127,9 @@ Options
|
|
|
122
127
|
--title <text> title for publish or fork (fork default: the original's)
|
|
123
128
|
--id <id> with publish: the address, /d/<id> (default: from the title)
|
|
124
129
|
--folder <id> destination folder for publish or fork
|
|
125
|
-
--version <v>
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
--version <v> get/comments/fork/open: the published version to read
|
|
131
|
+
or copy; feedback commands: require that version to
|
|
132
|
+
still be current. On its own: the CLI version
|
|
128
133
|
--parent <id> parent folder for mkdir
|
|
129
134
|
--limit <n> with list: rows per page (default 20, max 50)
|
|
130
135
|
--cursor <id> with list: the page after the one that printed it
|
|
@@ -136,7 +141,7 @@ Options
|
|
|
136
141
|
60, at least 30)
|
|
137
142
|
--timeout <s> with changes --wait: give up after this long (default
|
|
138
143
|
3600), exit 3, and print the cursor to resume from
|
|
139
|
-
--base <v> with update: the version you revised; refused if
|
|
144
|
+
--base <v> with update/commit: the version you revised; refused if
|
|
140
145
|
somebody has published since
|
|
141
146
|
--all with list: every page; with comments: resolved too
|
|
142
147
|
--with <text> with suggest or revise: what the passage should say instead
|
|
@@ -175,18 +180,31 @@ function out(json, value, human) {
|
|
|
175
180
|
* for both — `…/d/<id>[/<version>]?k=<key>` — and the key goes along as the
|
|
176
181
|
* tools' `k` argument (SPEC §17).
|
|
177
182
|
*/
|
|
178
|
-
function docArgs(ref) {
|
|
179
|
-
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
fail(`not a document link: ${ref}`);
|
|
183
|
+
function docArgs(ref, flags) {
|
|
184
|
+
let id = ref;
|
|
185
|
+
let k;
|
|
186
|
+
let linkedVersion;
|
|
187
|
+
if (/^https?:\/\//i.test(ref)) {
|
|
188
|
+
let url;
|
|
189
|
+
try { url = new URL(ref); } catch { fail(`not a document link: ${ref}`); }
|
|
190
|
+
const match = url.pathname.match(/^\/d\/([^/]+)(?:\/([^/]+))?\/?$/);
|
|
191
|
+
if (!match) fail(`not a document link: ${ref}`);
|
|
192
|
+
id = decodeURIComponent(match[1]);
|
|
193
|
+
linkedVersion = match[2] ? decodeURIComponent(match[2]) : undefined;
|
|
194
|
+
k = url.searchParams.get("k");
|
|
185
195
|
}
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
const
|
|
189
|
-
|
|
196
|
+
// Document-wide operations deliberately use only the document id/key.
|
|
197
|
+
if (flags === undefined) return { id, ...(k ? { k } : {}) };
|
|
198
|
+
const explicit = flags.version;
|
|
199
|
+
if (explicit !== undefined && (typeof explicit !== "string" || !explicit || /[\/\\?#\s]/.test(explicit))) {
|
|
200
|
+
fail("invalid version");
|
|
201
|
+
}
|
|
202
|
+
if (explicit && linkedVersion && explicit !== linkedVersion) {
|
|
203
|
+
fail(`conflicting versions: the link names ${linkedVersion}, the flag names ${explicit}`);
|
|
204
|
+
}
|
|
205
|
+
const version = explicit ?? linkedVersion;
|
|
206
|
+
if (version && (version === "." || version === ".." || /[\/\\?#\s]/.test(version))) fail("invalid version");
|
|
207
|
+
return { id, ...(k ? { k } : {}), ...(version ? { version } : {}) };
|
|
190
208
|
}
|
|
191
209
|
|
|
192
210
|
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
|
|
@@ -496,7 +514,7 @@ async function update(flags, [ref, path]) {
|
|
|
496
514
|
if (!ref || !path) fail("usage: bikeshed update <id> <file>");
|
|
497
515
|
const { origin, token } = resolveAuth(flags);
|
|
498
516
|
const file = resolve(path);
|
|
499
|
-
const doc = docArgs(ref);
|
|
517
|
+
const { version: selectedBase, ...doc } = docArgs(ref, { version: flags.base });
|
|
500
518
|
const { id } = doc;
|
|
501
519
|
|
|
502
520
|
const once = async () => {
|
|
@@ -505,7 +523,7 @@ async function update(flags, [ref, path]) {
|
|
|
505
523
|
// text, every byte above 0x7f in it would become U+FFFD on the way.
|
|
506
524
|
// --base: the version this was revised from, so an update over somebody's
|
|
507
525
|
// newer save is refused instead of replacing it (SPEC §19).
|
|
508
|
-
const base =
|
|
526
|
+
const base = selectedBase;
|
|
509
527
|
const result = isArchive(file, bytes)
|
|
510
528
|
? await uploadFile(origin, token, file, { update: id, k: doc.k, base })
|
|
511
529
|
: await callTool(origin, token, "update_document", {
|
|
@@ -527,10 +545,30 @@ async function update(flags, [ref, path]) {
|
|
|
527
545
|
if (flags.watch) await watchFile(file, once);
|
|
528
546
|
}
|
|
529
547
|
|
|
548
|
+
/** The published history, paged by the deployment rather than guessed from ids. */
|
|
549
|
+
async function versions(flags, [ref, ...rest]) {
|
|
550
|
+
if (!ref || rest.length) fail("usage: bikeshed versions <id> [--limit N] [--cursor …] [--all] [--json]");
|
|
551
|
+
const { origin, token } = resolveAuth(flags);
|
|
552
|
+
const limit = flags.limit === undefined ? 20 : Number(flags.limit);
|
|
553
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) fail("--limit must be an integer from 1 to 50");
|
|
554
|
+
let cursor = flags.cursor;
|
|
555
|
+
const rows = [];
|
|
556
|
+
let result;
|
|
557
|
+
do {
|
|
558
|
+
result = await callTool(origin, token, "list_versions", { ...docArgs(ref), limit, ...(cursor ? { cursor } : {}) });
|
|
559
|
+
rows.push(...result.versions);
|
|
560
|
+
cursor = result.nextCursor;
|
|
561
|
+
} while (flags.all && cursor);
|
|
562
|
+
out(flags.json, { ...result, versions: rows }, () => {
|
|
563
|
+
for (const row of rows) console.log(`${row.isCurrent ? "*" : " "} ${row.version} ${row.publishedAt ?? ""} ${row.label} ${row.url ?? ""}`);
|
|
564
|
+
if (cursor) console.log(`… more: bikeshed versions ${ref} --cursor ${cursor}`);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
530
568
|
async function get(flags, [ref]) {
|
|
531
569
|
if (!ref) fail("usage: bikeshed get <id>");
|
|
532
570
|
const { origin, token } = resolveAuth(flags);
|
|
533
|
-
const doc = docArgs(ref);
|
|
571
|
+
const doc = docArgs(ref, flags);
|
|
534
572
|
const result = await callTool(origin, token, "get_document", doc);
|
|
535
573
|
// The tool cuts a long source at a fixed length. Printed as it came, the cut
|
|
536
574
|
// copy exited 0 and looked complete, and `update` with an edit of it
|
|
@@ -555,9 +593,7 @@ async function comments(flags, [ref]) {
|
|
|
555
593
|
if (!ref) fail("usage: bikeshed comments <id>");
|
|
556
594
|
const { origin, token } = resolveAuth(flags);
|
|
557
595
|
const result = await callTool(origin, token, "get_comments", {
|
|
558
|
-
...docArgs(ref),
|
|
559
|
-
// Threads belong to the version they were opened on; a version in a link
|
|
560
|
-
// is not taken as a request for one, only the flag is.
|
|
596
|
+
...docArgs(ref, flags),
|
|
561
597
|
...(typeof flags.version === "string" ? { version: flags.version } : {}),
|
|
562
598
|
includeResolved: Boolean(flags.all),
|
|
563
599
|
});
|
|
@@ -603,7 +639,7 @@ async function comment(flags, [ref, ...rest]) {
|
|
|
603
639
|
if (typeof flags.body !== "string" || !flags.body.trim()) fail(usage);
|
|
604
640
|
const { origin, token } = resolveAuth(flags);
|
|
605
641
|
const result = await callTool(origin, token, "add_comment", {
|
|
606
|
-
...docArgs(ref),
|
|
642
|
+
...docArgs(ref, flags),
|
|
607
643
|
exact,
|
|
608
644
|
body: flags.body,
|
|
609
645
|
...(typeof flags.prefix === "string" ? { prefix: flags.prefix } : {}),
|
|
@@ -617,7 +653,7 @@ async function reply(flags, [ref, threadId, ...rest]) {
|
|
|
617
653
|
if (!ref || !threadId || !body) fail("usage: bikeshed reply <id> <thread> <text>");
|
|
618
654
|
const { origin, token } = resolveAuth(flags);
|
|
619
655
|
const result = await callTool(origin, token, "reply_to_comment", {
|
|
620
|
-
...docArgs(ref),
|
|
656
|
+
...docArgs(ref, flags),
|
|
621
657
|
threadId,
|
|
622
658
|
body,
|
|
623
659
|
});
|
|
@@ -641,7 +677,7 @@ async function reopenThread(flags, args) {
|
|
|
641
677
|
async function setResolved(flags, [ref, threadId, ...rest], verb) {
|
|
642
678
|
if (!ref || !threadId || rest.length) fail(`usage: bikeshed ${verb} <id> <thread>`);
|
|
643
679
|
const { origin, token } = resolveAuth(flags);
|
|
644
|
-
const result = await callTool(origin, token, `${verb}_thread`, { ...docArgs(ref), threadId });
|
|
680
|
+
const result = await callTool(origin, token, `${verb}_thread`, { ...docArgs(ref, flags), threadId });
|
|
645
681
|
out(flags.json, result, () => console.log(`${verb === "resolve" ? "resolved" : "reopened"} ${threadId}`));
|
|
646
682
|
}
|
|
647
683
|
|
|
@@ -657,7 +693,7 @@ async function suggest(flags, [ref, ...rest]) {
|
|
|
657
693
|
const replacement = replacementFrom(flags, usage);
|
|
658
694
|
const { origin, token } = resolveAuth(flags);
|
|
659
695
|
const result = await callTool(origin, token, "suggest_edit", {
|
|
660
|
-
...docArgs(ref),
|
|
696
|
+
...docArgs(ref, flags),
|
|
661
697
|
exact,
|
|
662
698
|
replacement,
|
|
663
699
|
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
@@ -689,7 +725,7 @@ async function revise(flags, [ref, threadId, ...rest]) {
|
|
|
689
725
|
const replacement = replacementFrom(flags, usage);
|
|
690
726
|
const { origin, token } = resolveAuth(flags);
|
|
691
727
|
const result = await callTool(origin, token, "revise_suggestion", {
|
|
692
|
-
...docArgs(ref),
|
|
728
|
+
...docArgs(ref, flags),
|
|
693
729
|
threadId,
|
|
694
730
|
replacement,
|
|
695
731
|
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
@@ -705,8 +741,9 @@ async function revise(flags, [ref, threadId, ...rest]) {
|
|
|
705
741
|
async function accept(flags, [ref, threadId, ...rest]) {
|
|
706
742
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed accept <id> <thread>");
|
|
707
743
|
const { origin, token } = resolveAuth(flags);
|
|
708
|
-
|
|
709
|
-
|
|
744
|
+
const { id, version } = docArgs(ref, flags);
|
|
745
|
+
// Deciding still takes a real role; a version is only a precondition.
|
|
746
|
+
const result = await callTool(origin, token, "accept_suggestion", { id, ...(version ? { version } : {}), threadId });
|
|
710
747
|
out(flags.json, result, () => console.log(`accepted ${threadId}; the change is in the draft — \`bikeshed commit\` publishes it`));
|
|
711
748
|
}
|
|
712
749
|
|
|
@@ -714,7 +751,8 @@ async function accept(flags, [ref, threadId, ...rest]) {
|
|
|
714
751
|
async function commit(flags, [ref, ...rest]) {
|
|
715
752
|
if (!ref || rest.length) fail("usage: bikeshed commit <id>");
|
|
716
753
|
const { origin, token } = resolveAuth(flags);
|
|
717
|
-
const
|
|
754
|
+
const { id, version } = docArgs(ref, { version: flags.base });
|
|
755
|
+
const result = await callTool(origin, token, "commit_draft", { id, ...(version ? { baseVersion: version } : {}) });
|
|
718
756
|
out(flags.json, result, () =>
|
|
719
757
|
console.log(result.deduped ? `nothing new: the draft is ${result.versionId} already` : `published ${result.versionId}`),
|
|
720
758
|
);
|
|
@@ -728,9 +766,10 @@ async function commit(flags, [ref, ...rest]) {
|
|
|
728
766
|
async function reject(flags, [ref, threadId, ...rest]) {
|
|
729
767
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed reject <id> <thread> [--reason <why>]");
|
|
730
768
|
const { origin, token } = resolveAuth(flags);
|
|
731
|
-
|
|
769
|
+
const { id, version } = docArgs(ref, flags);
|
|
770
|
+
// Deciding still takes a real role; a version is only a precondition.
|
|
732
771
|
const result = await callTool(origin, token, "reject_suggestion", {
|
|
733
|
-
id:
|
|
772
|
+
id, ...(version ? { version } : {}),
|
|
734
773
|
threadId,
|
|
735
774
|
...(typeof flags.reason === "string" ? { reason: flags.reason } : {}),
|
|
736
775
|
});
|
|
@@ -741,7 +780,7 @@ async function reject(flags, [ref, threadId, ...rest]) {
|
|
|
741
780
|
async function withdraw(flags, [ref, threadId, ...rest]) {
|
|
742
781
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed withdraw <id> <thread>");
|
|
743
782
|
const { origin, token } = resolveAuth(flags);
|
|
744
|
-
const result = await callTool(origin, token, "withdraw_suggestion", { ...docArgs(ref), threadId });
|
|
783
|
+
const result = await callTool(origin, token, "withdraw_suggestion", { ...docArgs(ref, flags), threadId });
|
|
745
784
|
out(flags.json, result, () => console.log(`withdrew ${threadId}`));
|
|
746
785
|
}
|
|
747
786
|
|
|
@@ -781,20 +820,51 @@ function describeAccess(result) {
|
|
|
781
820
|
const where = result.filedIn ? `filed in ${result.filedIn.path}` : "at the root";
|
|
782
821
|
const who = access.readers === 1 ? "only you can read it" : `${access.readers} people can read it`;
|
|
783
822
|
const also = access.alsoReadableBy ? `, and ${access.alsoReadableBy}` : "";
|
|
784
|
-
|
|
823
|
+
// A folder's link or public level reaches what is filed beneath it; say which.
|
|
824
|
+
const through = access.throughFolders?.length
|
|
825
|
+
? ` (through ${access.throughFolders.map((f) => f.path).join(", ")})`
|
|
826
|
+
: "";
|
|
827
|
+
return `${where}: ${who}${also}${through}`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* A folder's id, and its key when what was pasted is the folder's link,
|
|
832
|
+
* `…/folder/<id>?k=<key>` (SPEC §17 "A folder's reach").
|
|
833
|
+
*/
|
|
834
|
+
function folderArgs(ref) {
|
|
835
|
+
if (!/^https?:\/\//i.test(ref)) return { id: ref };
|
|
836
|
+
let url;
|
|
837
|
+
try { url = new URL(ref); } catch { fail(`not a folder link: ${ref}`); }
|
|
838
|
+
const match = url.pathname.match(/^\/folder\/([^/]+)\/?$/);
|
|
839
|
+
if (!match) fail(`not a folder link: ${ref}`);
|
|
840
|
+
const k = url.searchParams.get("k");
|
|
841
|
+
return { id: decodeURIComponent(match[1]), ...(k ? { k } : {}) };
|
|
785
842
|
}
|
|
786
843
|
|
|
787
844
|
/** Who can read one folder: and so who can read anything filed in it. */
|
|
788
|
-
async function folder(flags, [
|
|
789
|
-
if (!
|
|
845
|
+
async function folder(flags, [ref]) {
|
|
846
|
+
if (!ref) fail("usage: bikeshed folder <id|link>");
|
|
790
847
|
const { origin, token } = resolveAuth(flags);
|
|
791
|
-
const result = await callTool(origin, token, "get_folder",
|
|
848
|
+
const result = await callTool(origin, token, "get_folder", folderArgs(ref));
|
|
849
|
+
// Opened by its link: what it holds, to read, and nothing about who reads it.
|
|
850
|
+
if (result.viaLink) {
|
|
851
|
+
out(flags.json, result, () => {
|
|
852
|
+
console.log(`${result.path.map((p) => p.name).join("/")} ${result.folderId} (read-only, through its link)`);
|
|
853
|
+
for (const f of result.folders) console.log(` ${f.name}/ ${f.folderId}`);
|
|
854
|
+
for (const d of result.documents) console.log(` ${d.title} ${d.url ?? d.docId}`);
|
|
855
|
+
if (result.truncated) console.log(" (more than can be listed at once)");
|
|
856
|
+
});
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
792
859
|
out(flags.json, result, () => {
|
|
793
860
|
console.log(`${result.path} ${result.id}${result.canWrite ? "" : " (read-only)"}`);
|
|
794
861
|
console.log(`${result.readers} can read it${result.readers === 1 ? " (only you)" : ""}:`);
|
|
795
862
|
for (const person of result.people) console.log(` ${person}`);
|
|
796
863
|
if (result.morePeople > 0) console.log(` and ${result.morePeople} more`);
|
|
797
864
|
if (result.teams.length) console.log(`teams: ${result.teams.join(", ")}`);
|
|
865
|
+
for (const f of result.throughFolders ?? []) {
|
|
866
|
+
console.log(`also ${f.visibility === "public" ? "anyone" : "anyone with the link"}, through ${f.path}${f.expiresAt === null ? "" : ` until ${new Date(f.expiresAt).toISOString()}`}`);
|
|
867
|
+
}
|
|
798
868
|
console.log(`can edit: ${[...result.editors, ...(result.moreEditors ? [`and ${result.moreEditors} more`] : [])].join(", ")}`);
|
|
799
869
|
});
|
|
800
870
|
}
|
|
@@ -805,11 +875,18 @@ function printManagedAccess(result) {
|
|
|
805
875
|
for (const [role, people] of Object.entries(result.direct)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
806
876
|
for (const grant of result.inherited) console.log(`inherited ${grant.role}: ${grant.principal} via ${grant.path}`);
|
|
807
877
|
for (const [role, people] of Object.entries(result.effective)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
878
|
+
for (const r of result.inheritedReach ?? []) {
|
|
879
|
+
console.log(`readable by ${r.visibility === "public" ? "anyone" : "anyone with the folder's link"} through ${r.path}${r.expiresAt === null ? "" : `, until ${new Date(r.expiresAt).toISOString()}`}`);
|
|
880
|
+
}
|
|
808
881
|
if (result.visibility !== undefined) printVisibility(result);
|
|
809
882
|
}
|
|
810
883
|
|
|
811
884
|
function printVisibility(result) {
|
|
812
|
-
|
|
885
|
+
const expires = result.expiresAt === null ? "never" : new Date(result.expiresAt).toISOString();
|
|
886
|
+
// A folder has no open editing, and its date ends only its link.
|
|
887
|
+
console.log(result.publicEdit === undefined
|
|
888
|
+
? `visibility: ${result.visibility}; link ends: ${expires}`
|
|
889
|
+
: `visibility: ${result.visibility}; open editing: ${result.publicEdit ? "on" : "off"}; expires: ${expires}`);
|
|
813
890
|
console.log(result.url);
|
|
814
891
|
}
|
|
815
892
|
|
|
@@ -832,9 +909,16 @@ async function access(flags, [kind, ref, ...rest]) {
|
|
|
832
909
|
out(flags.json, result, () => printManagedAccess(result));
|
|
833
910
|
}
|
|
834
911
|
|
|
835
|
-
async function visibility(flags,
|
|
836
|
-
|
|
837
|
-
|
|
912
|
+
async function visibility(flags, positionals) {
|
|
913
|
+
// `visibility folder <id> …` and `visibility document <id> …` name the kind;
|
|
914
|
+
// a bare `visibility <id>` is a document, as it always was.
|
|
915
|
+
const named = ["folder", "document"].includes(positionals[0]) && positionals.length > 1;
|
|
916
|
+
const kind = named ? positionals[0] : "document";
|
|
917
|
+
const [ref, level, ...rest] = named ? positionals.slice(1) : positionals;
|
|
918
|
+
const usage = "usage: bikeshed visibility [folder|document] <id> [private|unlisted|public] [--public-edit true|false] [--expires <ISO timestamp|none>]";
|
|
919
|
+
if (!ref || rest.length || (level !== undefined && !["private", "unlisted", "public"].includes(level))) fail(usage);
|
|
920
|
+
if (kind === "folder" && flags["public-edit"] !== undefined) fail("a folder has no open editing: what its link opens is read-only");
|
|
921
|
+
const args = { id: kind === "folder" ? folderArgs(ref).id : docArgs(ref).id };
|
|
838
922
|
if (level !== undefined) args.visibility = level;
|
|
839
923
|
if (flags["public-edit"] !== undefined) {
|
|
840
924
|
if (!["true", "false"].includes(flags["public-edit"])) fail("--public-edit must be true or false");
|
|
@@ -848,7 +932,11 @@ async function visibility(flags, [ref, level, ...rest]) {
|
|
|
848
932
|
}
|
|
849
933
|
}
|
|
850
934
|
const { origin, token } = resolveAuth(flags);
|
|
851
|
-
const
|
|
935
|
+
const changing = Object.keys(args).length > 1;
|
|
936
|
+
const tool = kind === "folder"
|
|
937
|
+
? (changing ? "set_folder_visibility" : "get_folder_access")
|
|
938
|
+
: (changing ? "set_document_visibility" : "get_document_access");
|
|
939
|
+
const result = await callTool(origin, token, tool, args);
|
|
852
940
|
out(flags.json, result, () => printVisibility(result));
|
|
853
941
|
}
|
|
854
942
|
|
|
@@ -942,7 +1030,7 @@ function printChanges(flags, result) {
|
|
|
942
1030
|
async function fork(flags, [ref]) {
|
|
943
1031
|
if (!ref) fail("usage: bikeshed fork <id> [--version <v>] [--folder <id>] [--title <text>]");
|
|
944
1032
|
const { origin, token } = resolveAuth(flags);
|
|
945
|
-
const args = docArgs(ref);
|
|
1033
|
+
const args = docArgs(ref, flags);
|
|
946
1034
|
if (typeof flags.version === "string") args.version = flags.version;
|
|
947
1035
|
if (typeof flags.folder === "string") args.folder = flags.folder;
|
|
948
1036
|
if (typeof flags.title === "string") args.title = flags.title;
|
|
@@ -1094,8 +1182,8 @@ async function open(flags, [ref]) {
|
|
|
1094
1182
|
const { origin } = resolveAuth(flags);
|
|
1095
1183
|
// An unlisted document opens only with its key, and the link is the one place
|
|
1096
1184
|
// the key lives, so a link keeps it.
|
|
1097
|
-
const { id, k } = docArgs(ref);
|
|
1098
|
-
const url = `${origin}/d/${encodeURIComponent(id)}${k ? `?k=${encodeURIComponent(k)}` : ""}`;
|
|
1185
|
+
const { id, k, version } = docArgs(ref, flags);
|
|
1186
|
+
const url = `${origin}/d/${encodeURIComponent(id)}${version ? `/${encodeURIComponent(version)}` : ""}${k ? `?k=${encodeURIComponent(k)}` : ""}`;
|
|
1099
1187
|
// Best effort, and the address is printed either way. This used to spawn
|
|
1100
1188
|
// `start`, which is a cmd.exe builtin rather than a program, so on Windows
|
|
1101
1189
|
// the spawn failed and, with nothing listening for the error, crashed.
|
|
@@ -1134,6 +1222,7 @@ const COMMANDS = {
|
|
|
1134
1222
|
fork,
|
|
1135
1223
|
lineage,
|
|
1136
1224
|
get,
|
|
1225
|
+
versions,
|
|
1137
1226
|
comments,
|
|
1138
1227
|
changes,
|
|
1139
1228
|
comment,
|
|
@@ -1218,6 +1307,10 @@ async function main() {
|
|
|
1218
1307
|
return;
|
|
1219
1308
|
}
|
|
1220
1309
|
|
|
1310
|
+
const versionCommands = new Set(["get", "comments", "fork", "open", "comment", "reply", "resolve", "reopen", "suggest", "revise", "withdraw", "accept", "reject"]);
|
|
1311
|
+
if (values.version !== undefined && !versionCommands.has(name)) fail(`--version is not supported by ${name}; update and commit use --base`);
|
|
1312
|
+
if (values.base !== undefined && !["update", "commit"].includes(name)) fail(`--base is not supported by ${name}`);
|
|
1313
|
+
|
|
1221
1314
|
const command = COMMANDS[name];
|
|
1222
1315
|
if (!command) fail(`unknown command: ${name}\n\n${USAGE}`);
|
|
1223
1316
|
|