Say which packages a client without a token actually gets

a2f8319 · Gabriel Morell · 2026-09-13 03:53

9 files +43 -3
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud_web/live/org_live/packages.ex
+24 −2
@@ -73,6 +73,12 @@ defmodule GitGudWeb.OrgLive.Packages do
73 73 |> String.trim_trailing("/")
74 74 end
75 75
76 + # What an anonymous client would actually be offered. The rest need a
77 + # token, and a page that doesn't say so is how "it lists nine and my
78 + # phone shows five" happens.
79 + defp public_count(entry),
80 + do: Enum.count(entry.packages, &(&1.repository.visibility == "public"))
81 +
76 82 defp namespace_url(root, handle, format),
77 83 do: "#{root}/pkg/#{format}/#{handle}/#{Format.namespace_marker()}"
78 84
@@ -151,8 +157,10 @@ defmodule GitGudWeb.OrgLive.Packages do
151 157 <h2 class="font-semibold">What consumers see</h2>
152 158 <p class="text-xs opacity-70">
153 159 Each registry's namespace-wide view — one URL per format, covering every
154 − repository @{@org.handle} publishes. Filtered to what <strong>you</strong>
155 − can read, so someone with less access sees less than this.
160 + repository @{@org.handle} publishes. This is <strong>your</strong>
161 + view: a client with no credentials is offered only packages from public
162 + repositories, so anything badged below needs a registry token configured
163 + in the client.
156 164 </p>
157 165 </div>
158 166
@@ -171,6 +179,13 @@ defmodule GitGudWeb.OrgLive.Packages do
171 179 <span class="text-xs opacity-70">
172 180 {length(entry.packages)} packages · {Enum.count(entry.repositories)} repositories
173 181 </span>
182 + <span
183 + :if={public_count(entry) < length(entry.packages)}
184 + class="text-xs opacity-70"
185 + title="A client with no credentials only gets the public ones"
186 + >
187 + · <strong>{public_count(entry)}</strong> visible without a token
188 + </span>
174 189 </div>
175 190 <p :if={Usage.summary(format)} class="text-xs opacity-60">{Usage.summary(format)}</p>
176 191 <pre class="text-[11px] bg-base-100 rounded px-2 py-1 overflow-x-auto select-all"><code>{consumer_url(@registry_root, @org.handle, format)}</code></pre>
@@ -194,6 +209,13 @@ defmodule GitGudWeb.OrgLive.Packages do
194 209 >
195 210 {row.repository.name}
196 211 </.link>
212 + <span
213 + :if={row.repository.visibility != "public"}
214 + class="badge badge-xs badge-ghost ml-1"
215 + title={"#{row.repository.visibility} — an anonymous client won't be offered this"}
216 + >
217 + {row.repository.visibility}
218 + </span>
197 219 </td>
198 220 <td class="text-right text-xs tabular-nums">{row.versions}</td>
199 221 </tr>
modified test/git_gud_web/live/org_packages_catalog_test.exs
+19 −1
@@ -132,7 +132,25 @@ defmodule GitGudWeb.OrgPackagesCatalogTest do
132 132
133 133 # Without this the listing reads as "what exists", and someone
134 134 # sharing a screenshot promises access they can't grant.
135 − assert html =~ "someone with less access sees less"
135 + # Short fragments: HEEx reflows prose across lines, so a long
136 + # literal is an assertion about the formatter.
137 + assert html =~ "no credentials"
138 + assert html =~ "registry token"
139 + end
140 +
141 + test "marks which packages an anonymous client won't be offered", %{
142 + conn: conn,
143 + owner: owner,
144 + org: org
145 + } do
146 + {:ok, _view, html} = live(log_in_user(conn, owner), ~p"/orgs/#{org.handle}/packages")
147 +
148 + # Two deb packages, one of them from a private repository. An
149 + # owner seeing both with nothing distinguishing them is how "it
150 + # lists nine and my phone shows five" happens.
151 + assert html =~ "1</strong> visible without a token"
152 + assert html =~ "badge-xs badge-ghost"
153 + assert html =~ "private"
136 154 end
137 155 end
138 156

Parents: b82e0a0