import { useQuery } from '@tanstack/react-query'; import { CheckIcon, CopyIcon, PackageIcon } from 'lucide-react'; import { useState } from 'react'; import { Badge } from '#/components/atoms/badge.jsx'; import { Notice } from '#/components/atoms/notice.jsx'; import { RowIcon } from '#/components/atoms/row-icon.jsx'; import { EmptyState } from '#/components/molecules/empty-state.jsx'; import { ListRow } from '#/components/molecules/list-row.jsx'; import { TextLink } from '#/components/molecules/text-link.jsx'; import { get } from '#/lib/api.js'; import { bytes, timeAgo } from '#/lib/format.js'; import { queryKeys } from '#/lib/query.js'; /** One line summarizing a package's shape. */ function packageSummary(pkg) { if (pkg.versions.length === 0) return 'nothing published yet'; const parts = [ `${pkg.versions.length} ${pkg.versions.length === 1 ? 'version' : 'versions'}`, ]; parts.push(bytes(pkg.size)); if (pkg.updatedAt) parts.push(`published ${timeAgo(pkg.updatedAt)}`); return parts.join(' · '); } /** A command with a copy button beside it, shown in full where room allows. */ function CopyCommand({ command, compact }) { const [copied, setCopied] = useState(false); const copy = (event) => { event.preventDefault(); event.stopPropagation(); navigator.clipboard.writeText(command).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); }; return ( ); } /** Every version of one package: number, size, when, its install command. */ function VersionList({ pkg, registryUrl }) { const rows = [...pkg.versions].reverse(); return (
{rows.map((entry) => (
{entry.version} {bytes(entry.size)} {entry.publishedAt ? ` · published ${timeAgo(entry.publishedAt)}` : ''}
))}
); } /** * @param {{data: Object}} props */ export function PackagesPage({ data: account }) { const [openVersions, setOpenVersions] = useState( /** @type {string|null} */ (null), ); const { data, error } = useQuery({ queryKey: queryKeys.npmPackages, queryFn: () => get('/npm/packages'), }); if (error) { return {error.message}; } if (!data) return null; if (!data.packages.length) { return ( } title="No packages yet" description={ <> Your account is an npm registry. Point npm at{' '} https://{account.hostname}/npm/ , sign in with{' '} npm login using an app password, and{' '} npm publish. Tarballs are stored in your repo as blobs, so packages ride along in backups and migrate with your account. Anyone can install. } /> ); } return (
{data.packages.map((pkg) => { const latest = pkg.distTags.find((t) => t.tag === 'latest'); const otherTags = pkg.distTags.filter((t) => t.tag !== 'latest'); return (
} title={
{pkg.name} {latest && ( v{latest.version} )} {otherTags.slice(0, 3).map((tag) => ( {tag.tag} ))}
} description={packageSummary(pkg)} actions={
{pkg.versions.length > 0 && ( setOpenVersions( openVersions === pkg.name ? null : pkg.name, ) } > {openVersions === pkg.name ? 'Hide versions' : 'Versions'} )}
} /> {openVersions === pkg.name && ( )}
); })}
); }