Skip to content

Commit 1b8b7fe

Browse files
committed
2 parents 39c49b6 + 17b24c5 commit 1b8b7fe

File tree

17 files changed

+343
-82
lines changed

17 files changed

+343
-82
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ We welcome contributions – please do feel free to explore the project and
156156
- [npm-alt](https://npm.willow.sh/) – An alternative npm package browser
157157
- [npkg.lorypelli.dev](https://npkg.lorypelli.dev/) – An alternative frontend to npm made with as little client-side JavaScript as possible
158158
- [vscode-npmx](https://github.com/npmx-dev/vscode-npmx) – VSCode extension for npmx
159+
- [nxjt](https://nxjt.netlify.app) – npmx Jump To: Quickly navigate to npmx common webpages.
160+
- [npmx-digest](https://npmx-digest.trueberryless.org/) – An automated news aggregation website that summarizes npmx activity from GitHub and Bluesky every 8 hours.
159161

160162
If you're building something cool, let us know! 🙏
161163

app/components/Package/Card.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const pkgDescription = useMarkdown(() => ({
160160
<div
161161
v-if="result.package.keywords?.length"
162162
:aria-label="$t('package.card.keywords')"
163-
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0 pointer-events-none"
163+
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0 pointer-events-none items-center"
164164
>
165165
<TagButton
166166
v-for="keyword in result.package.keywords.slice(0, 5)"

app/components/Package/MetricsBadges.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const typesHref = computed(() => {
5959
<component
6060
:is="typesHref ? NuxtLink : 'span'"
6161
:to="typesHref"
62+
:tabindex="!typesHref ? 0 : undefined"
6263
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
6364
:class="[
6465
hasTypes
@@ -83,6 +84,7 @@ const typesHref = computed(() => {
8384
<li>
8485
<TooltipApp :text="hasEsm ? $t('package.metrics.esm') : $t('package.metrics.no_esm')">
8586
<span
87+
tabindex="0"
8688
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
8789
:class="
8890
hasEsm
@@ -104,6 +106,7 @@ const typesHref = computed(() => {
104106
<li v-if="hasCjs">
105107
<TooltipApp :text="$t('package.metrics.cjs')">
106108
<span
109+
tabindex="0"
107110
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200"
108111
>
109112
<span class="i-carbon-checkmark w-3 h-3" aria-hidden="true" />

app/components/Readme.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function handleClick(event: MouseEvent) {
8888
color: var(--fg);
8989
@apply font-mono;
9090
font-weight: 500;
91-
margin-top: 2rem;
91+
margin-top: 1rem;
9292
margin-bottom: 1rem;
9393
line-height: 1.3;
9494

app/components/Terminal/Install.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,12 @@ const copyCreateCommand = () => copyCreate(getFullCreateCommand())
203203
<NuxtLink
204204
:to="`/package/${createPackageInfo.packageName}`"
205205
class="text-fg-muted hover:text-fg text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
206-
:title="$t('package.create.view', { packageName: createPackageInfo.packageName })"
207206
>
208-
<span class="i-carbon:information w-3 h-3 mt-1" aria-hidden="true" />
207+
<TooltipApp
208+
:text="$t('package.create.view', { packageName: createPackageInfo.packageName })"
209+
>
210+
<span class="i-carbon:information w-3 h-3 mt-1" aria-hidden="true" />
211+
</TooltipApp>
209212
<span class="sr-only">{{
210213
$t('package.create.view', { packageName: createPackageInfo.packageName })
211214
}}</span>

app/components/Tooltip/Base.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const { floatingStyles } = useFloating(triggerRef, tooltipRef, {
4040
<div
4141
v-if="props.isVisible"
4242
ref="tooltipRef"
43-
class="px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-nowrap z-[100] pointer-events-none"
43+
class="px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-pre-line z-[100] pointer-events-none"
4444
:style="floatingStyles"
4545
v-bind="tooltipAttr"
4646
>

app/components/VersionSelector.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ watch(
546546
<span
547547
v-else
548548
class="w-3 h-3 transition-transform duration-200 rtl-flip"
549-
:class="group.isExpanded ? 'i:carbon:chevron-down' : 'i-carbon:chevron-right'"
549+
:class="group.isExpanded ? 'i-carbon:chevron-down' : 'i-carbon:chevron-right'"
550550
aria-hidden="true"
551551
/>
552552
</button>

app/composables/usePackageComparison.ts

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ import type {
88
import { encodePackageName } from '#shared/utils/npm'
99
import type { PackageAnalysisResponse } from './usePackageAnalysis'
1010
import { isBinaryOnlyPackage } from '#shared/utils/binary-detection'
11+
import { formatBytes } from '~/utils/formatters'
12+
import { getDependencyCount } from '~/utils/npm/dependency-count'
1113

1214
export interface PackageComparisonData {
1315
package: ComparisonPackage
1416
downloads?: number
1517
/** Package's own unpacked size (from dist.unpackedSize) */
1618
packageSize?: number
19+
/** Number of direct dependencies */
20+
directDeps: number | null
1721
/** Install size data (fetched lazily) */
1822
installSize?: {
1923
selfSize: number
2024
totalSize: number
25+
/** Total dependency count */
2126
dependencyCount: number
2227
}
2328
analysis?: PackageAnalysisResponse
@@ -139,6 +144,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
139144
},
140145
downloads: downloads?.downloads,
141146
packageSize,
147+
directDeps: versionData ? getDependencyCount(versionData) : null,
142148
installSize: undefined, // Will be filled in second pass
143149
analysis: analysis ?? undefined,
144150
vulnerabilities: {
@@ -230,7 +236,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
230236
function isFacetLoading(facet: ComparisonFacet): boolean {
231237
if (!installSizeLoading.value) return false
232238
// These facets depend on install-size API
233-
return facet === 'installSize' || facet === 'dependencies'
239+
return facet === 'installSize' || facet === 'totalDependencies'
234240
}
235241

236242
// Check if a specific column (package) is loading
@@ -255,40 +261,40 @@ function computeFacetValue(
255261
t: (key: string, params?: Record<string, unknown>) => string,
256262
): FacetValue | null {
257263
switch (facet) {
258-
case 'downloads':
264+
case 'downloads': {
259265
if (data.downloads === undefined) return null
260266
return {
261267
raw: data.downloads,
262268
display: formatCompactNumber(data.downloads),
263269
status: 'neutral',
264270
}
265-
266-
case 'packageSize':
271+
}
272+
case 'packageSize': {
267273
if (!data.packageSize) return null
268274
return {
269275
raw: data.packageSize,
270276
display: formatBytes(data.packageSize),
271277
status: data.packageSize > 5 * 1024 * 1024 ? 'warning' : 'neutral',
272278
}
273-
274-
case 'installSize':
279+
}
280+
case 'installSize': {
275281
if (!data.installSize) return null
276282
return {
277283
raw: data.installSize.totalSize,
278284
display: formatBytes(data.installSize.totalSize),
279285
status: data.installSize.totalSize > 50 * 1024 * 1024 ? 'warning' : 'neutral',
280286
}
281-
282-
case 'moduleFormat':
287+
}
288+
case 'moduleFormat': {
283289
if (!data.analysis) return null
284290
const format = data.analysis.moduleFormat
285291
return {
286292
raw: format,
287293
display: format === 'dual' ? 'ESM + CJS' : format.toUpperCase(),
288294
status: format === 'esm' || format === 'dual' ? 'good' : 'neutral',
289295
}
290-
291-
case 'types':
296+
}
297+
case 'types': {
292298
if (data.isBinaryOnly) {
293299
return {
294300
raw: 'binary',
@@ -309,8 +315,8 @@ function computeFacetValue(
309315
: t('compare.facets.values.types_none'),
310316
status: types.kind === 'included' ? 'good' : types.kind === '@types' ? 'info' : 'bad',
311317
}
312-
313-
case 'engines':
318+
}
319+
case 'engines': {
314320
const engines = data.metadata?.engines
315321
if (!engines?.node) {
316322
return { raw: null, display: t('compare.facets.values.any'), status: 'neutral' }
@@ -320,8 +326,8 @@ function computeFacetValue(
320326
display: `Node ${engines.node}`,
321327
status: 'neutral',
322328
}
323-
324-
case 'vulnerabilities':
329+
}
330+
case 'vulnerabilities': {
325331
if (!data.vulnerabilities) return null
326332
const count = data.vulnerabilities.count
327333
const sev = data.vulnerabilities.severity
@@ -337,8 +343,8 @@ function computeFacetValue(
337343
}),
338344
status: count === 0 ? 'good' : sev.critical > 0 || sev.high > 0 ? 'bad' : 'warning',
339345
}
340-
341-
case 'lastUpdated':
346+
}
347+
case 'lastUpdated': {
342348
if (!data.metadata?.lastUpdated) return null
343349
const date = new Date(data.metadata.lastUpdated)
344350
return {
@@ -347,8 +353,8 @@ function computeFacetValue(
347353
status: isStale(date) ? 'warning' : 'neutral',
348354
type: 'date',
349355
}
350-
351-
case 'license':
356+
}
357+
case 'license': {
352358
const license = data.metadata?.license
353359
if (!license) {
354360
return { raw: null, display: t('compare.facets.values.unknown'), status: 'warning' }
@@ -358,17 +364,17 @@ function computeFacetValue(
358364
display: license,
359365
status: 'neutral',
360366
}
361-
362-
case 'dependencies':
363-
if (!data.installSize) return null
364-
const depCount = data.installSize.dependencyCount
367+
}
368+
case 'dependencies': {
369+
const depCount = data.directDeps
370+
if (depCount === null) return null
365371
return {
366372
raw: depCount,
367373
display: String(depCount),
368-
status: depCount > 50 ? 'warning' : 'neutral',
374+
status: depCount > 10 ? 'warning' : 'neutral',
369375
}
370-
371-
case 'deprecated':
376+
}
377+
case 'deprecated': {
372378
const isDeprecated = !!data.metadata?.deprecated
373379
return {
374380
raw: isDeprecated,
@@ -377,22 +383,23 @@ function computeFacetValue(
377383
: t('compare.facets.values.not_deprecated'),
378384
status: isDeprecated ? 'bad' : 'good',
379385
}
380-
386+
}
381387
// Coming soon facets
382-
case 'totalDependencies':
383-
return null
384-
385-
default:
388+
case 'totalDependencies': {
389+
if (!data.installSize) return null
390+
const totalDepCount = data.installSize.dependencyCount
391+
return {
392+
raw: totalDepCount,
393+
display: String(totalDepCount),
394+
status: totalDepCount > 50 ? 'warning' : 'neutral',
395+
}
396+
}
397+
default: {
386398
return null
399+
}
387400
}
388401
}
389402

390-
function formatBytes(bytes: number): string {
391-
if (bytes < 1024) return `${bytes} B`
392-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`
393-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
394-
}
395-
396403
function isStale(date: Date): boolean {
397404
const now = new Date()
398405
const diffMs = now.getTime() - date.getTime()

app/pages/package/[...package].vue

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { joinURL } from 'ufo'
1111
import { areUrlsEquivalent } from '#shared/utils/url'
1212
import { isEditableElement } from '~/utils/input'
1313
import { formatBytes } from '~/utils/formatters'
14+
import { getDependencyCount } from '~/utils/npm/dependency-count'
1415
import { NuxtLink } from '#components'
1516
import { useModal } from '~/composables/useModal'
1617
import { useAtproto } from '~/composables/atproto/useAtproto'
@@ -300,11 +301,6 @@ function normalizeGitUrl(url: string): string {
300301
.replace(/^git@github\.com:/, 'https://github.com/')
301302
}
302303
303-
function getDependencyCount(version: PackumentVersion | null): number {
304-
if (!version?.dependencies) return 0
305-
return Object.keys(version.dependencies).length
306-
}
307-
308304
// Check if a version has provenance/attestations
309305
// The dist object may have attestations that aren't in the base type
310306
function hasProvenance(version: PackumentVersion | null): boolean {
@@ -861,11 +857,9 @@ defineOgImageComponent('Package', {
861857
<div class="space-y-1 sm:col-span-3">
862858
<dt class="text-xs text-fg-subtle uppercase tracking-wider flex items-center gap-1">
863859
{{ $t('package.stats.install_size') }}
864-
<span
865-
class="i-carbon:information w-3 h-3 text-fg-subtle"
866-
aria-hidden="true"
867-
:title="sizeTooltip"
868-
/>
860+
<TooltipApp :text="sizeTooltip">
861+
<span class="i-carbon:information w-3 h-3 text-fg-subtle" aria-hidden="true" />
862+
</TooltipApp>
869863
</dt>
870864
<dd class="font-mono text-sm text-fg">
871865
<!-- Package size (greyed out) -->
@@ -1042,11 +1036,11 @@ defineOgImageComponent('Package', {
10421036

10431037
<!-- README -->
10441038
<section id="readme" class="area-readme min-w-0 scroll-mt-20">
1045-
<div class="flex flex-wrap items-center justify-between mb-4">
1039+
<div class="flex flex-wrap items-center justify-between mb-3">
10461040
<h2 id="readme-heading" class="group text-xs text-fg-subtle uppercase tracking-wider">
10471041
<a
10481042
href="#readme"
1049-
class="inline-flex py-4 px-2 items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
1043+
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
10501044
>
10511045
{{ $t('package.readme.title') }}
10521046
<span

app/utils/npm/dependency-count.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { PackumentVersion } from '#shared/types'
2+
3+
export function getDependencyCount(version: PackumentVersion | null): number {
4+
if (!version?.dependencies) return 0
5+
return Object.keys(version.dependencies).length
6+
}

0 commit comments

Comments
 (0)