Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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