Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
585e202
install npmeta
gameroman Oct 30, 2026
875dd37
chore: use `isBinaryOnlyPackage` from `npmeta`
gameroman Oct 30, 2026
a9702aa
feat: use `parsePackageSpec` from `npmeta`
gameroman Oct 30, 2026
c37a574
feat: use `splitPackageName` from `npmeta`
gameroman Oct 30, 2026
701eebd
feat: use `encodePackageName` from `npmeta`
gameroman Oct 30, 2026
6a07180
feat: use `normalizeLicense` from `npmeta`
gameroman Oct 30, 2026
d597329
feat: use `isCreatePackage` from `npmeta`
gameroman Oct 30, 2026
4be8d80
feat: use `getTypesPackageName` from `npmeta`
gameroman Oct 30, 2026
2358b6e
feat: use `parseDependencyVersion` from `npmeta`
gameroman Oct 30, 2026
5c70ed0
feat: use `getCreateShortName` and `getCreatePackageName` from `npmeta`
gameroman Oct 30, 2026
caaff3f
feat: use `hasBuiltInTypes` from `npmeta`
gameroman Oct 30, 2026
a5adefa
fix: use `isStable` from `verkit`
gameroman Oct 30, 2026
45de414
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 30, 2026
013c081
feat: use `detectModuleFormat` from `npmeta`
gameroman Oct 30, 2026
231b73c
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 30, 2026
2bef559
feat: use `hasBuiltInTypes` from `npmeta`
gameroman Oct 30, 2026
97b171e
fix
gameroman Oct 30, 2026
1b303f8
Merge branch 'main' into roman/use-npmeta
gameroman Oct 30, 2026
c861b77
fix: resolve merge conflict
gameroman Oct 30, 2026
21e3d48
chore: use `parsePackageSpec`
gameroman Oct 30, 2026
d7af9a8
chore: reuse `splitPackageName`
gameroman Oct 30, 2026
3778ee4
update `npmeta` to v0.2.0
gameroman Oct 30, 2026
7047b8e
fix
gameroman Oct 30, 2026
3a99daa
feat: reuse `splitPackageName`
gameroman Oct 30, 2026
f18bc2a
fix
gameroman Oct 30, 2026
3bd87d3
feat: use `parsePackageSpec`
gameroman Oct 30, 2026
98d484c
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 30, 2026
54016ef
import `ModuleFormat` analysis from `npmeta`
gameroman Oct 31, 2026
ee77296
feat: use `splitPackageName` in `isKnownDevDependencyPackage`
gameroman Oct 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions app/components/Compare/FacetCard.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { parsePackageSpec } from 'npmeta'

const props = defineProps<{
/** Facet label */
label: string
Expand Down Expand Up @@ -57,15 +59,6 @@ function getStatusClass(status?: FacetValue['status']): string {
function isCellLoading(index: number): boolean {
return props.facetLoading || (props.columnLoading?.[index] ?? false)
}

// Get short package name (without version) for mobile display
function getShortName(header: string): string {
const atIndex = header.lastIndexOf('@')
if (atIndex > 0) {
return header.substring(0, atIndex)
}
return header
}
</script>

<template>
Expand Down Expand Up @@ -102,7 +95,7 @@ function getShortName(header: string): string {
class="relative font-mono text-xs text-fg-muted truncate flex-shrink-0"
:title="headers[index]"
>
{{ getShortName(headers[index] ?? '') }}
{{ parsePackageSpec(headers[index] ?? '').name }}
</span>

<!-- Value -->
Expand Down
2 changes: 2 additions & 0 deletions app/components/Compare/PackageSelector.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { parsePackageSpec } from 'npmeta'

const packages = defineModel<string[]>({ required: true })

const props = defineProps<{
Expand Down
15 changes: 2 additions & 13 deletions app/components/DependencyPathPopup.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { onKeyDown } from '@vueuse/core'
import { UseFocusTrap } from '@vueuse/integrations/useFocusTrap/component'
import { parsePackageSpec } from 'npmeta'

defineProps<{
/** Dependency path from root to vulnerable package (readonly from VulnerabilityTreeResult) */
Expand Down Expand Up @@ -52,15 +53,6 @@ function getPopupStyle(): Record<string, string> {
left: `${popupPosition.value.left}px`,
}
}

// Parse package string "name@version" into { name, version }
function parsePackageString(pkg: string): { name: string; version: string } {
const atIndex = pkg.lastIndexOf('@')
if (atIndex > 0) {
return { name: pkg.slice(0, atIndex), version: pkg.slice(atIndex + 1) }
}
return { name: pkg, version: '' }
}
</script>

<template>
Expand Down Expand Up @@ -94,10 +86,7 @@ function parsePackageString(pkg: string): { name: string; version: string } {
<span v-if="idx > 0" class="text-fg-subtle me-1">└─</span>
<NuxtLink
:to="
packageRoute(
parsePackageString(pathItem).name,
parsePackageString(pathItem).version,
)
packageRoute(parsePackageSpec(pathItem).name, parsePackageSpec(pathItem).version)
"
class="hover:underline"
:class="idx === path.length - 1 ? 'text-fg font-medium' : 'text-fg-muted'"
Expand Down
2 changes: 1 addition & 1 deletion app/components/OgImage/Compare.takumi.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { encodePackageName } from '#shared/utils/npm'
import { encodePackageName } from 'npmeta'

const { packages = [], emptyDescription = 'Compare npm packages side-by-side' } = defineProps<{
packages?: string | string[]
Expand Down
9 changes: 3 additions & 6 deletions app/components/Package/AccessControls.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { splitPackageName } from 'npmeta'
import type { NewOperation } from '~/composables/useConnector'
import { buildScopeTeam } from '~/utils/npm/common'

Expand All @@ -15,12 +16,8 @@ const {
error: connectorError,
} = useConnector()

// Extract org name from scoped package (e.g., "@nuxt/kit" -> "nuxt")
const orgName = computed(() => {
if (!props.packageName.startsWith('@')) return null
const match = props.packageName.match(/^@([^/]+)\//)
return match ? match[1] : null
})
// Extract org name from scoped package
const orgName = computed(() => splitPackageName(props.packageName).scope)

// Data
const collaborators = shallowRef<Record<string, 'read-only' | 'read-write'>>({})
Expand Down
2 changes: 1 addition & 1 deletion app/components/Package/Dependencies.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { parseDependencyVersion } from '#shared/utils/npm'
import { parseDependencyVersion } from 'npmeta'
import { SEVERITY_TEXT_COLORS, getHighestSeverity } from '#shared/utils/severity'
import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-dependencies'

Expand Down
7 changes: 2 additions & 5 deletions app/components/Package/Maintainers.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { splitPackageName } from 'npmeta'
import type { NewOperation } from '~/composables/useConnector'

const props = defineProps<{
Expand Down Expand Up @@ -38,11 +39,7 @@ const hiddenMaintainersCount = computed(() =>
)

// Extract org name from scoped package
const orgName = computed(() => {
if (!props.packageName.startsWith('@')) return null
const match = props.packageName.match(/^@([^/]+)\//)
return match ? match[1] : null
})
const orgName = computed(() => splitPackageName(props.packageName).scope)

// Access data: who has access and via what
const collaborators = shallowRef<Record<string, 'read-only' | 'read-write'>>({})
Expand Down
1 change: 1 addition & 0 deletions app/composables/npm/useNpmSearch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { encodePackageName } from 'npmeta'
import { emptySearchResponse, metaToSearchResult } from './search-utils'

interface NpmSearchOptions {
Expand Down
1 change: 1 addition & 0 deletions app/composables/npm/useOrgPackages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NpmSearchResponse, NpmSearchResult, PackageMetaResponse } from '#shared/types'
import { encodePackageName } from 'npmeta'
import { emptySearchResponse, metaToSearchResult } from './search-utils'
import { mapWithConcurrency } from '#shared/utils/async'

Expand Down
2 changes: 1 addition & 1 deletion app/composables/npm/useOutdatedDependencies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { PackageVersionsInfo } from 'fast-npm-meta'
import { getVersionsBatch } from 'fast-npm-meta'
import { parseDependencyVersion } from 'npmeta'
import { difference, findMaxSatisfying, getMajor, getMinor, isGreater, isStable } from 'verkit'
import { parseDependencyVersion } from '#shared/utils/npm'
import {
type OutdatedDependencyInfo,
constraintIncludesPrerelease,
Expand Down
2 changes: 2 additions & 0 deletions app/composables/npm/usePackage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { encodePackageName } from 'npmeta'

export function usePackage(
name: MaybeRefOrGetter<string>,
requestedVersion?: MaybeRefOrGetter<string | null>,
Expand Down
2 changes: 2 additions & 0 deletions app/composables/npm/usePackageDownloads.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { encodePackageName } from 'npmeta'

export function usePackageDownloads(
name: MaybeRefOrGetter<string>,
period: MaybeRefOrGetter<'last-day' | 'last-week' | 'last-month' | 'last-year'> = 'last-week',
Expand Down
2 changes: 1 addition & 1 deletion app/composables/npm/useReplacementDependencies.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ModuleReplacement, ModuleReplacementMapping } from 'module-replacements'
import { parseDependencyVersion } from '#shared/utils/npm'
import { parseDependencyVersion } from 'npmeta'

async function fetchReplacements(
deps: Record<string, string>,
Expand Down
1 change: 1 addition & 0 deletions app/composables/useCharts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import { mapWithConcurrency } from '#shared/utils/async'
import { splitIsoRangeIntoChunksInclusive, mergeDailyPoints } from '#shared/utils/download-ranges'
import { fetchNpmDownloadsRange } from '~/utils/npm/api'
import { encodePackageName } from 'npmeta'
import { toValue } from 'vue'
import { addDays, toIsoDate, parseIsoDate } from '~/utils/date'

Expand Down
1 change: 1 addition & 0 deletions app/composables/useCommandPalettePackageCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
CommandPaletteContextCommandInput,
CommandPalettePackageContext,
} from '~/types/command-palette'
import { splitPackageName } from 'npmeta'
import { downloadPackageTarball } from '~/utils/package-download'

function activeLabel(isCurrentRoute: boolean, label: string) {
Expand Down
2 changes: 1 addition & 1 deletion app/composables/useCompareReplacements.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ModuleReplacement, ModuleReplacementMapping } from 'module-replacements'
import { parsePackageSpec } from 'npmeta'
import { NO_DEPENDENCY_ID } from '#shared/utils/compare'
import { parsePackageSpec } from '#shared/utils/parse-package-param'

export interface ReplacementSuggestion {
forPackage: string
Expand Down
2 changes: 2 additions & 0 deletions app/composables/useDependencyAnalysis.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { encodePackageName } from 'npmeta'

/**
* Shared composable for dependency analysis data (vulnerabilities, deprecated packages).
* Fetches once and caches the result so multiple components can use it.
Expand Down
3 changes: 2 additions & 1 deletion app/composables/usePackageAnalysis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ModuleFormat, TypesStatus, CreatePackageInfo } from '#shared/utils/package-analysis'
import type { ModuleFormat } from 'npmeta'
import type { TypesStatus, CreatePackageInfo } from '#shared/utils/package-analysis'
import type { DevDependencySuggestion } from '#shared/utils/dev-dependency'

export interface PackageAnalysisResponse {
Expand Down
2 changes: 1 addition & 1 deletion app/composables/usePackageComparison.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { encodePackageName, isBinaryOnlyPackage, parsePackageSpec } from 'npmeta'
import { NO_DEPENDENCY_ID } from '#shared/utils/compare'
import { parsePackageSpec } from '#shared/utils/parse-package-param'
import { getDependencyCount } from '~/utils/npm/dependency-count'

/**
Expand Down
2 changes: 2 additions & 0 deletions app/composables/usePackageRoute.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { splitPackageName } from 'npmeta'

/**
* Parse package name and optional version from the current route URL.
*
Expand Down
1 change: 1 addition & 0 deletions app/pages/compare.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { useRouteQuery } from '@vueuse/router'
import { parsePackageSpec } from 'npmeta'
import FacetBarChart from '~/components/Compare/FacetBarChart.vue'
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'
import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue'
Expand Down
1 change: 1 addition & 0 deletions app/pages/package/[[org]]/[name].vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { isBinaryOnlyPackage, isCreatePackage } from 'npmeta'
import { assertValidPackageName } from '#shared/utils/npm'
import { getDependencyCount } from '~/utils/npm/dependency-count'

Expand Down
4 changes: 2 additions & 2 deletions app/utils/install-command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JsrPackageInfo } from '#shared/types/jsr'
import { getCreateShortName } from '#shared/utils/package-analysis'
import { getCreateShortName } from 'npmeta'

// @unocss-include
export const packageManagers = [
Expand Down Expand Up @@ -182,7 +182,7 @@ function getCreatePackageSpecifier(options: ExecuteCommandOptions): string | nul
}

const shortName = getCreateShortName(options.packageName)
if (shortName === options.packageName) {
if (shortName === null) {
return null
}

Expand Down
1 change: 1 addition & 0 deletions app/utils/npm/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getVersions } from 'fast-npm-meta'
import { encodePackageName } from 'npmeta'
import { compare } from 'verkit'

type NpmDownloadsRangeResponse = {
Expand Down
11 changes: 2 additions & 9 deletions app/utils/package-name.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { encodePackageName, splitPackageName } from 'npmeta'
import validatePackageName from 'validate-npm-package-name'
import { NPM_REGISTRY } from '#shared/utils/constants'
import { encodePackageName } from '#shared/utils/npm'

export function splitPackageName(packageName: string): { org: string; name: string } {
const [org = '', name = ''] = packageName.startsWith('@')
? packageName.split('/')
: ['', packageName]
return { org, name }
}

/**
* Normalize a package name for comparison by removing common variations.
* This aims to mirror npm's typosquatting detection algorithm.
*/
export function normalizePackageName(name: string): string {
// Remove scope if present
const unscoped = name.startsWith('@') ? name.split('/')[1] || name : name
const unscoped = splitPackageName(name).name

// Normalize: lowercase, remove punctuation (.-_), remove 'js' and 'node' suffixes/prefixes
return (
Expand Down
2 changes: 1 addition & 1 deletion app/utils/parse-package-json-deps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parsePackageSpec } from '#shared/utils/parse-package-param'
import { parsePackageSpec } from 'npmeta'

export type DependencyCategory =
| 'dependencies'
Expand Down
4 changes: 3 additions & 1 deletion app/utils/platform-packages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { splitPackageName } from 'npmeta'

/**
* Detects if a package name is a platform-specific native binary package.
* These are typically optional dependencies that contain native binaries
Expand Down Expand Up @@ -44,7 +46,7 @@ const ABI_SUFFIXES = new Set(['gnu', 'musl', 'msvc', 'gnueabihf'])
* @returns true if the package appears to be a platform-specific binary
*/
export function isPlatformSpecificPackage(name: string): boolean {
const unscopedName = name.startsWith('@') ? (name.split('/')[1] ?? '') : name
const unscopedName = splitPackageName(name).name
if (!unscopedName) return false

const parts = unscopedName.split('-')
Expand Down
2 changes: 1 addition & 1 deletion app/utils/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RouteLocationRaw } from 'vue-router'
import { splitPackageName } from '~/utils/package-name'
import { splitPackageName } from 'npmeta'

export function packageRoute(
packageName: string,
Expand Down
12 changes: 3 additions & 9 deletions app/utils/run-command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { JsrPackageInfo } from '#shared/types/jsr'
import { splitPackageName } from 'npmeta'
import { getPackageSpecifier, packageManagers } from './install-command'
import type { PackageManagerId } from './install-command'

Expand All @@ -14,13 +15,6 @@ export interface ExecutableInfo {
hasExecutable: boolean
}

/**
* Extract basename from package
*/
function getPackageBaseName(packageName: string): string | undefined {
return packageName.startsWith('@') ? packageName.split('/')[1] : packageName
}

/**
* Extract executable command information from a package's bin field.
* Handles both string format ("bin": "./cli.js") and object format ("bin": { "cmd": "./cli.js" }).
Expand Down Expand Up @@ -50,7 +44,7 @@ export function getExecutableInfo(
}

// Prefer command matching package name if it exists; otherwise, use first
const baseName = getPackageBaseName(packageName)
const baseName = splitPackageName(packageName).name
const primaryCommand = baseName && commands.includes(baseName) ? baseName : firstCommand

return {
Expand Down Expand Up @@ -94,7 +88,7 @@ export function getRunCommandParts(options: RunCommandOptions): string[] {
// For local execute with specific command name different from package name
// e.g., `pnpm exec tsc` for typescript package
if (options.command && options.command !== options.packageName) {
const baseName = getPackageBaseName(options.packageName)
const baseName = splitPackageName(options.packageName).name
// If command matches base package name, use the package spec
if (options.command === baseName) {
return [...executeParts, spec]
Expand Down
Loading
Loading