bikeshed-now 0.17.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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/package.json +1 -1
  3. package/src/index.mjs +63 -10
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bikeshed-now",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Publish and revise documents on a Bikeshed deployment from the terminal.",
package/src/index.mjs CHANGED
@@ -84,7 +84,8 @@ const USAGE = `bikeshed ${VERSION} — publish and revise documents
84
84
 
85
85
  Organising
86
86
  bikeshed folders the folder tree, with ids and readers
87
- bikeshed folder <id> who can read a folder, and so what is in it
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
88
89
  bikeshed mkdir <name> [--parent <id>] create a folder
89
90
  bikeshed mv <id> <folder|root> move a document
90
91
  bikeshed rename <id> <title> change a document's title; its address stays
@@ -96,6 +97,9 @@ Management
96
97
  bikeshed visibility <id> [private|unlisted|public]
97
98
  --public-edit true|false change open editing
98
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
99
103
  bikeshed rename-folder <id> <name> rename a folder you can edit
100
104
  bikeshed move-folder <id> <folder|root> move a folder and recompute inherited access
101
105
  bikeshed delete-folder <id> owner-only; reparent contents, retain documents
@@ -816,20 +820,51 @@ function describeAccess(result) {
816
820
  const where = result.filedIn ? `filed in ${result.filedIn.path}` : "at the root";
817
821
  const who = access.readers === 1 ? "only you can read it" : `${access.readers} people can read it`;
818
822
  const also = access.alsoReadableBy ? `, and ${access.alsoReadableBy}` : "";
819
- return `${where}: ${who}${also}`;
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 } : {}) };
820
842
  }
821
843
 
822
844
  /** Who can read one folder: and so who can read anything filed in it. */
823
- async function folder(flags, [id]) {
824
- if (!id) fail("usage: bikeshed folder <id>");
845
+ async function folder(flags, [ref]) {
846
+ if (!ref) fail("usage: bikeshed folder <id|link>");
825
847
  const { origin, token } = resolveAuth(flags);
826
- const result = await callTool(origin, token, "get_folder", { id });
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
+ }
827
859
  out(flags.json, result, () => {
828
860
  console.log(`${result.path} ${result.id}${result.canWrite ? "" : " (read-only)"}`);
829
861
  console.log(`${result.readers} can read it${result.readers === 1 ? " (only you)" : ""}:`);
830
862
  for (const person of result.people) console.log(` ${person}`);
831
863
  if (result.morePeople > 0) console.log(` and ${result.morePeople} more`);
832
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
+ }
833
868
  console.log(`can edit: ${[...result.editors, ...(result.moreEditors ? [`and ${result.moreEditors} more`] : [])].join(", ")}`);
834
869
  });
835
870
  }
@@ -840,11 +875,18 @@ function printManagedAccess(result) {
840
875
  for (const [role, people] of Object.entries(result.direct)) console.log(`${role}: ${people.join(", ") || "none"}`);
841
876
  for (const grant of result.inherited) console.log(`inherited ${grant.role}: ${grant.principal} via ${grant.path}`);
842
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
+ }
843
881
  if (result.visibility !== undefined) printVisibility(result);
844
882
  }
845
883
 
846
884
  function printVisibility(result) {
847
- console.log(`visibility: ${result.visibility}; open editing: ${result.publicEdit ? "on" : "off"}; expires: ${result.expiresAt === null ? "never" : new Date(result.expiresAt).toISOString()}`);
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}`);
848
890
  console.log(result.url);
849
891
  }
850
892
 
@@ -867,9 +909,16 @@ async function access(flags, [kind, ref, ...rest]) {
867
909
  out(flags.json, result, () => printManagedAccess(result));
868
910
  }
869
911
 
870
- async function visibility(flags, [ref, level, ...rest]) {
871
- if (!ref || rest.length || (level !== undefined && !["private", "unlisted", "public"].includes(level))) fail("usage: bikeshed visibility <id> [private|unlisted|public] [--public-edit true|false] [--expires <ISO timestamp|none>]");
872
- const args = { id: docArgs(ref).id };
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 };
873
922
  if (level !== undefined) args.visibility = level;
874
923
  if (flags["public-edit"] !== undefined) {
875
924
  if (!["true", "false"].includes(flags["public-edit"])) fail("--public-edit must be true or false");
@@ -883,7 +932,11 @@ async function visibility(flags, [ref, level, ...rest]) {
883
932
  }
884
933
  }
885
934
  const { origin, token } = resolveAuth(flags);
886
- const result = await callTool(origin, token, Object.keys(args).length > 1 ? "set_document_visibility" : "get_document_access", args);
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);
887
940
  out(flags.json, result, () => printVisibility(result));
888
941
  }
889
942