Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge packages: roles, Actions access, source label, soft delete, API | 1 | import { ArrowUpRight, Box, Check, ChevronDown, Download, Lock, RotateCcw, Search, Trash2 } from "lucide-react"; |
| 2 | import { Form, Link, data, redirect } from "react-router"; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 3 | |
| Merge packages: roles, Actions access, source label, soft delete, API | 4 | import { ECOSYSTEMS, type Ecosystem, PACKAGE_RESTORE_DAYS, type PackageSummary } from "@g1t/contracts"; |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 5 | |
| 6 | import type { Route } from "./+types/packages"; | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 7 | import { PackageIcon } from "../../components/package-icon"; |
| Merge packages: roles, Actions access, source label, soft delete, API | 8 | import { CopyLine, EmptyState, ErrorText, SubmitButton, TimeAgo } from "../../components/ui"; |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 9 | import { Badge } from "../../components/ui/badge"; |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 10 | import { Hint } from "../../components/ui/hint"; |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 11 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu"; |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 12 | import { cn } from "../../lib/cn"; |
| 13 | import { page } from "../../lib/meta"; | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 14 | import { |
| 15 | ECOSYSTEM_LABEL, | |
| 16 | type PackageSort, | |
| 17 | REGISTRIES, | |
| 18 | SORT_LABEL, | |
| 19 | VISIBILITY_LABEL, | |
| 20 | type VisibilityFilter, | |
| 21 | arrange, | |
| 22 | formatBytes, | |
| 23 | shortCount, | |
| 24 | } from "../../lib/packages"; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 25 | import { packages } from "../../lib/services.server"; |
| Merge packages: roles, Actions access, source label, soft delete, API | 26 | import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server"; |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 27 | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 28 | const DOCS = "https://docs.g1t.sh"; |
| 29 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 30 | export function meta({ params, ...args }: Route.MetaArgs) { |
| 31 | return page(args, { title: `Packages · ${params.owner} · g1t` }); | |
| 32 | } | |
| 33 | ||
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 34 | function pick<T extends string>(value: string | null, options: readonly T[], fallback: T): T { |
| 35 | return value && (options as readonly string[]).includes(value) ? (value as T) : fallback; | |
| 36 | } | |
| 37 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 38 | export async function loader({ params, context, request }: Route.LoaderArgs) { |
| 39 | const viewer = getViewer(context); | |
| 40 | // Members only for now; a workspace's public packages are found through | |
| 41 | // their repositories and search. | |
| 42 | if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 }); | |
| 43 | const url = new URL(request.url); | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 44 | const type = url.searchParams.get("type"); |
| 45 | const ecosystem = type && (ECOSYSTEMS as readonly string[]).includes(type) ? (type as Ecosystem) : null; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 46 | const query = url.searchParams.get("q")?.trim() || null; |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 47 | const visibility = pick<VisibilityFilter>(url.searchParams.get("visibility"), ["all", "public", "private"], "all"); |
| 48 | const sort = pick<PackageSort>(url.searchParams.get("sort"), ["updated", "downloads", "name"], "updated"); | |
| Merge packages: roles, Actions access, source label, soft delete, API | 49 | const view = url.searchParams.get("view") === "deleted" ? "deleted" : "active"; |
| 50 | // Deleted packages are listed for those who administer them, and only | |
| 51 | // then is there a Deleted packages view. | |
| 52 | const [found, deletedFound] = await Promise.all([ | |
| 53 | packages.list(params.owner, viewer, { ecosystem, query }), | |
| 54 | packages.deleted(params.owner, viewer), | |
| 55 | ]); | |
| 56 | const listed = unwrap(found); | |
| 57 | const deleted = deletedFound.ok ? deletedFound.value : []; | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 58 | // Whether the workspace has any at all decides between a filtered-out |
| 59 | // list and the first-package page. | |
| Merge packages: roles, Actions access, source label, soft delete, API | 60 | const any = listed.length > 0 || deleted.length > 0 || ecosystem != null || query != null; |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 61 | return { |
| Merge packages: roles, Actions access, source label, soft delete, API | 62 | list: arrange(listed, visibility, sort), |
| 63 | deleted, | |
| 64 | view, | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 65 | any, |
| 66 | ecosystem, | |
| 67 | query, | |
| 68 | visibility, | |
| 69 | sort, | |
| 70 | workspace: params.owner.toLowerCase(), | |
| 71 | }; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 72 | } |
| 73 | ||
| Merge packages: roles, Actions access, source label, soft delete, API | 74 | type Outcome = { error: string | null; message: string | null }; |
| 75 | ||
| 76 | /** Restoring a deleted package, from the Deleted packages view. */ | |
| 77 | export async function action({ request, params, context }: Route.ActionArgs): Promise<Outcome | Response> { | |
| 78 | assertSameOrigin(request); | |
| 79 | const user = requireUser(context, request); | |
| 80 | const form = await request.formData(); | |
| 81 | if (form.get("intent") !== "restore") return { error: "That is not something this page does.", message: null }; | |
| 82 | const type = String(form.get("ecosystem") ?? ""); | |
| 83 | const name = String(form.get("name") ?? ""); | |
| 84 | if (!(ECOSYSTEMS as readonly string[]).includes(type) || !name) return { error: "Name the package to restore.", message: null }; | |
| 85 | const restored = await packages.restorePackage(user, params.owner, type as Ecosystem, name, "web"); | |
| 86 | if (!restored.ok) return { error: restored.error.message, message: null }; | |
| 87 | return redirect(`/${params.owner}/-/packages/${type}/${name}`); | |
| 88 | } | |
| 89 | ||
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 90 | type Filters = { type: string | null; q: string | null; visibility: VisibilityFilter; sort: PackageSort }; |
| 91 | ||
| 92 | /** The page's address with one filter changed, defaults left out. */ | |
| 93 | function hrefWith(workspace: string, current: Filters, change: Partial<Filters>): string { | |
| 94 | const next = { ...current, ...change }; | |
| 95 | const params = new URLSearchParams(); | |
| 96 | if (next.type) params.set("type", next.type); | |
| 97 | if (next.q) params.set("q", next.q); | |
| 98 | if (next.visibility !== "all") params.set("visibility", next.visibility); | |
| 99 | if (next.sort !== "updated") params.set("sort", next.sort); | |
| 100 | return `/${workspace}/-/packages${params.size ? `?${params}` : ""}`; | |
| 101 | } | |
| 102 | ||
| 103 | /** The packages a workspace publishes: filtered, sorted, or, with none yet, how to start. */ | |
| Merge packages: roles, Actions access, source label, soft delete, API | 104 | export default function Packages({ loaderData, actionData }: Route.ComponentProps) { |
| 105 | const { list, deleted, view, any, ecosystem, query, visibility, sort, workspace } = loaderData; | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 106 | if (!any) return <ChooseRegistry workspace={workspace} />; |
| Merge packages: roles, Actions access, source label, soft delete, API | 107 | if (view === "deleted") return <DeletedPackages workspace={workspace} deleted={deleted} outcome={actionData as Outcome | undefined} />; |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 108 | const filters: Filters = { type: ecosystem, q: query, visibility, sort }; |
| 109 | const filtered = ecosystem != null || query != null || visibility !== "all"; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 110 | return ( |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 111 | <div className="space-y-4"> |
| 112 | <div className="flex flex-wrap items-center gap-2"> | |
| 113 | <FilterMenu | |
| 114 | label="Type" | |
| 115 | value={ecosystem ? ECOSYSTEM_LABEL[ecosystem] : "All"} | |
| 116 | options={[null, ...ECOSYSTEMS].map((option) => ({ | |
| 117 | label: option ? ECOSYSTEM_LABEL[option] : "All", | |
| 118 | to: hrefWith(workspace, filters, { type: option }), | |
| 119 | current: ecosystem === option, | |
| 120 | }))} | |
| 121 | /> | |
| 122 | <Form method="get" className="relative min-w-48 grow"> | |
| 123 | <label className="sr-only" htmlFor="package-search"> | |
| 124 | Search packages | |
| 125 | </label> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 126 | <Search size={14} className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-faint" /> |
| 127 | <input | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 128 | id="package-search" |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 129 | name="q" |
| 130 | defaultValue={query ?? ""} | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 131 | placeholder="Search packages" |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 132 | className="h-9 w-full rounded-lg border border-line bg-surface pr-3 pl-8 text-sm placeholder:text-faint focus:border-line-strong focus:outline-none" |
| 133 | /> | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 134 | {ecosystem && <input type="hidden" name="type" value={ecosystem} />} |
| 135 | {visibility !== "all" && <input type="hidden" name="visibility" value={visibility} />} | |
| 136 | {sort !== "updated" && <input type="hidden" name="sort" value={sort} />} | |
| 137 | </Form> | |
| 138 | <FilterMenu | |
| 139 | label="Visibility" | |
| 140 | value={VISIBILITY_LABEL[visibility]} | |
| 141 | options={(["all", "public", "private"] as const).map((option) => ({ | |
| 142 | label: VISIBILITY_LABEL[option], | |
| 143 | to: hrefWith(workspace, filters, { visibility: option }), | |
| 144 | current: visibility === option, | |
| 145 | }))} | |
| 146 | /> | |
| 147 | <FilterMenu | |
| 148 | label="Sort" | |
| 149 | value={SORT_LABEL[sort]} | |
| 150 | options={(["updated", "downloads", "name"] as const).map((option) => ({ | |
| 151 | label: SORT_LABEL[option], | |
| 152 | to: hrefWith(workspace, filters, { sort: option }), | |
| 153 | current: sort === option, | |
| 154 | }))} | |
| 155 | /> | |
| 156 | </div> | |
| 157 | ||
| 158 | <section className="overflow-hidden rounded-xl border border-line bg-surface"> | |
| 159 | <header className="flex items-center justify-between gap-3 border-b border-line px-4 py-2.5 text-sm"> | |
| 160 | <span className="font-medium"> | |
| 161 | {list.length} {list.length === 1 ? "package" : "packages"} | |
| 162 | {filtered && ( | |
| 163 | <Link to={`/${workspace}/-/packages`} className="ml-3 text-xs font-normal text-muted hover:text-fg"> | |
| 164 | Clear filters | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 165 | </Link> |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 166 | )} |
| 167 | </span> | |
| Merge packages: roles, Actions access, source label, soft delete, API | 168 | <span className="flex items-center gap-4"> |
| 169 | {deleted.length > 0 && ( | |
| 170 | <Link to={`/${workspace}/-/packages?view=deleted`} className="inline-flex items-center gap-1 text-xs text-muted hover:text-fg"> | |
| 171 | <Trash2 size={12} /> | |
| 172 | Deleted packages <span className="rounded-full bg-raised px-1.5 text-faint tabular-nums">{deleted.length}</span> | |
| 173 | </Link> | |
| 174 | )} | |
| 175 | <a href={`${DOCS}/guides/packages/`} className="inline-flex items-center gap-1 text-xs text-muted hover:text-fg"> | |
| 176 | How packages work <ArrowUpRight size={12} /> | |
| 177 | </a> | |
| 178 | </span> | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 179 | </header> |
| 180 | {list.length === 0 ? ( | |
| 181 | <div className="p-4"> | |
| 182 | <EmptyState title="No packages match">Try another type or visibility, or clear the search.</EmptyState> | |
| 183 | </div> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 184 | ) : ( |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 185 | <ul className="divide-y divide-line"> |
| 186 | {list.map((pkg) => ( | |
| 187 | <PackageRow key={pkg.id} pkg={pkg} /> | |
| 188 | ))} | |
| 189 | </ul> | |
| 190 | )} | |
| 191 | </section> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 192 | </div> |
| 193 | ); | |
| 194 | } | |
| 195 | ||
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 196 | /** A filter as a button that opens its choices; each choice is a link, so the address says the view. */ |
| 197 | function FilterMenu({ label, value, options }: { label: string; value: string; options: { label: string; to: string; current: boolean }[] }) { | |
| 198 | return ( | |
| 199 | <DropdownMenu> | |
| 200 | <DropdownMenuTrigger className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-line bg-surface px-3 text-sm transition-colors hover:border-line-strong data-[state=open]:border-line-strong"> | |
| 201 | <span className="text-muted">{label}:</span> | |
| 202 | <span className="font-medium">{value}</span> | |
| 203 | <ChevronDown size={13} className="text-faint" /> | |
| 204 | </DropdownMenuTrigger> | |
| 205 | <DropdownMenuContent align="start" className="min-w-44"> | |
| 206 | {options.map((option) => ( | |
| 207 | <DropdownMenuItem key={option.label} asChild> | |
| 208 | <Link to={option.to} preventScrollReset> | |
| 209 | <Check size={14} className={cn(!option.current && "invisible")} /> | |
| 210 | {option.label} | |
| 211 | </Link> | |
| 212 | </DropdownMenuItem> | |
| 213 | ))} | |
| 214 | </DropdownMenuContent> | |
| 215 | </DropdownMenu> | |
| 216 | ); | |
| 217 | } | |
| 218 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 219 | function PackageRow({ pkg }: { pkg: PackageSummary }) { |
| 220 | return ( | |
| 221 | <li> | |
| 222 | <Link | |
| 223 | to={`/${pkg.workspace}/-/packages/${pkg.ecosystem}/${pkg.name}`} | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 224 | className="flex items-center gap-3.5 px-4 py-3.5 transition-colors hover:bg-raised/50" |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 225 | > |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 226 | <PackageIcon ecosystem={pkg.ecosystem} /> |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 227 | <span className="min-w-0 grow"> |
| 228 | <span className="flex flex-wrap items-center gap-2"> | |
| 229 | <span className="truncate font-medium">{pkg.name}</span> | |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 230 | {pkg.visibility === "private" && ( |
| 231 | <Badge> | |
| 232 | <Lock size={10} /> | |
| 233 | Private | |
| 234 | </Badge> | |
| 235 | )} | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 236 | </span> |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 237 | <span className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted"> |
| 238 | <span>{ECOSYSTEM_LABEL[pkg.ecosystem]}</span> | |
| 239 | <span className="text-faint">·</span> | |
| 240 | <span> | |
| 241 | Updated <TimeAgo at={pkg.updated_at} /> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 242 | </span> |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 243 | {pkg.repo && ( |
| 244 | <> | |
| 245 | <span className="text-faint">·</span> | |
| 246 | <span className="inline-flex items-center gap-1"> | |
| 247 | <Box size={11} /> | |
| 248 | {pkg.repo.name} | |
| 249 | </span> | |
| 250 | </> | |
| 251 | )} | |
| 252 | {pkg.description && ( | |
| 253 | <> | |
| 254 | <span className="text-faint">·</span> | |
| 255 | <span className="max-w-md truncate">{pkg.description}</span> | |
| 256 | </> | |
| 257 | )} | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 258 | </span> |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 259 | </span> |
| 260 | <span className="hidden shrink-0 items-center gap-5 text-xs text-muted tabular-nums sm:flex"> | |
| 261 | {pkg.latest && <span className="max-w-32 truncate font-mono text-fg-soft">{pkg.latest}</span>} | |
| 262 | <span className="w-16 text-right">{formatBytes(pkg.size)}</span> | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 263 | <Hint label={`${pkg.downloads.toLocaleString("en-US")} downloads`}> |
| 264 | <span className="inline-flex w-14 items-center justify-end gap-1"> | |
| 265 | <Download size={12} /> | |
| 266 | {shortCount(pkg.downloads)} | |
| 267 | <span className="sr-only"> downloads</span> | |
| 268 | </span> | |
| 269 | </Hint> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 270 | </span> |
| 271 | </Link> | |
| 272 | </li> | |
| 273 | ); | |
| 274 | } | |
| 275 | ||
| Merge packages: roles, Actions access, source label, soft delete, API | 276 | /** Deleted packages that can still be restored, for those who administer them. */ |
| 277 | function DeletedPackages({ workspace, deleted, outcome }: { workspace: string; deleted: PackageSummary[]; outcome: Outcome | undefined }) { | |
| 278 | return ( | |
| 279 | <div className="space-y-4"> | |
| 280 | <Link to={`/${workspace}/-/packages`} className="text-sm text-muted hover:text-fg"> | |
| 281 | Packages | |
| 282 | </Link> | |
| 283 | <div className="max-w-2xl"> | |
| 284 | <h2 className="text-lg font-semibold tracking-tight">Deleted packages</h2> | |
| 285 | <p className="mt-1 text-sm text-muted"> | |
| 286 | Packages deleted in the last {PACKAGE_RESTORE_DAYS} days, with every version they had. Restore one to bring it back as it was; | |
| 287 | until it is purged, nobody else can publish a package of its name. | |
| 288 | </p> | |
| 289 | </div> | |
| 290 | {outcome?.error && <ErrorText>{outcome.error}</ErrorText>} | |
| 291 | {deleted.length === 0 ? ( | |
| 292 | <EmptyState title="No deleted packages">Packages you delete stay here, restorable, for {PACKAGE_RESTORE_DAYS} days.</EmptyState> | |
| 293 | ) : ( | |
| 294 | <ul className="divide-y divide-line overflow-hidden rounded-xl border border-line bg-surface"> | |
| 295 | {deleted.map((pkg) => ( | |
| 296 | <li key={pkg.id} className="flex flex-wrap items-center gap-x-3.5 gap-y-2 px-4 py-3.5"> | |
| 297 | <PackageIcon ecosystem={pkg.ecosystem} /> | |
| 298 | <span className="min-w-0 grow basis-48"> | |
| 299 | <span className="block truncate font-medium">{pkg.name}</span> | |
| 300 | <span className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted"> | |
| 301 | <span>{ECOSYSTEM_LABEL[pkg.ecosystem]}</span> | |
| 302 | <span className="text-faint">·</span> | |
| 303 | <span> | |
| 304 | {pkg.versions} {pkg.versions === 1 ? "version" : "versions"} | |
| 305 | </span> | |
| 306 | {pkg.deleted_at && ( | |
| 307 | <> | |
| 308 | <span className="text-faint">·</span> | |
| 309 | <span> | |
| 310 | Deleted {pkg.deleted_by ? `by ${pkg.deleted_by} ` : ""} | |
| 311 | <TimeAgo at={pkg.deleted_at} /> | |
| 312 | </span> | |
| 313 | </> | |
| 314 | )} | |
| 315 | {pkg.purge_at && ( | |
| 316 | <> | |
| 317 | <span className="text-faint">·</span> | |
| 318 | <span>Purged {new Date(pkg.purge_at).toLocaleDateString("en-US", { month: "short", day: "numeric" })}</span> | |
| 319 | </> | |
| 320 | )} | |
| 321 | </span> | |
| 322 | </span> | |
| 323 | <Form method="post"> | |
| 324 | <input type="hidden" name="intent" value="restore" /> | |
| 325 | <input type="hidden" name="ecosystem" value={pkg.ecosystem} /> | |
| 326 | <input type="hidden" name="name" value={pkg.name} /> | |
| 327 | <SubmitButton variant="quiet" match={{ intent: "restore", name: pkg.name }} pending="Restoring…"> | |
| 328 | <RotateCcw size={14} /> | |
| 329 | Restore | |
| 330 | </SubmitButton> | |
| 331 | </Form> | |
| 332 | </li> | |
| 333 | ))} | |
| 334 | </ul> | |
| 335 | )} | |
| 336 | </div> | |
| 337 | ); | |
| 338 | } | |
| 339 | ||
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 340 | /** With no packages yet: every registry, what it is for, and the first step. */ |
| 341 | function ChooseRegistry({ workspace }: { workspace: string }) { | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 342 | return ( |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 343 | <div className="space-y-6"> |
| 344 | <div className="max-w-2xl"> | |
| 345 | <h2 className="text-lg font-semibold tracking-tight">Choose a registry</h2> | |
| 346 | <p className="mt-1 text-sm text-muted"> | |
| 347 | Packages live beside the code that makes them, with the same members, roles and tokens. Workflows publish with{" "} | |
| 348 | <code className="font-mono text-xs">G1T_TOKEN</code>. | |
| 349 | </p> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 350 | </div> |
| Packages lists like a registry: Type, search, Visibility and Sort in one bar, richer rows, and a registry to choose from to start | 351 | <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> |
| 352 | {REGISTRIES.map((registry) => ( | |
| 353 | <li key={registry.ecosystem} className="flex flex-col rounded-xl border border-line bg-surface p-4"> | |
| 354 | <div className="flex items-center gap-2.5"> | |
| 355 | <PackageIcon ecosystem={registry.ecosystem} /> | |
| 356 | <span className="font-medium">{ECOSYSTEM_LABEL[registry.ecosystem]}</span> | |
| 357 | {!registry.ready && <Badge>Soon</Badge>} | |
| 358 | </div> | |
| 359 | <p className="mt-2.5 grow text-sm text-muted">{registry.blurb}</p> | |
| 360 | {registry.ready && ( | |
| 361 | <div className="mt-3"> | |
| 362 | <CopyLine prompt text={registry.start.replaceAll("<workspace>", workspace)} /> | |
| 363 | </div> | |
| 364 | )} | |
| 365 | <a | |
| 366 | href={`${DOCS}${registry.guide}`} | |
| 367 | className="mt-3 inline-flex items-center gap-1 self-start text-sm text-fg-soft hover:text-fg" | |
| 368 | > | |
| 369 | {registry.ready ? "Set it up" : "What's planned"} <ArrowUpRight size={13} /> | |
| 370 | </a> | |
| 371 | </li> | |
| 372 | ))} | |
| 373 | </ul> | |
| 374 | </div> | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 375 | ); |
| 376 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.