Skip to content

Commit 048eee7

Browse files
authored
refactor: migrate to fast-npm-meta (#18)
* refactor: migrate to `fast-npm-meta` # Conflicts: # src/providers/completion-item/version.ts * chore: reuse `PackageInfo` * update * fix: filter jsr packages (404 in npm registry) * fix: handle error * upgrade `fast-npm-meta` * fix: use correct version * chore: update * fix: handle undefined
1 parent a7111ff commit 048eee7

13 files changed

Lines changed: 93 additions & 89 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@
104104
"*": "eslint --fix"
105105
},
106106
"devDependencies": {
107-
"@npm/types": "^2.1.0",
108107
"@types/node": "^25.1.0",
109108
"@types/vscode": "1.101.0",
110109
"@vida0905/eslint-config": "^2.9.0",
111110
"@vscode/vsce": "^3.7.1",
112111
"eslint": "^9.39.2",
112+
"fast-npm-meta": "^1.2.0",
113113
"husky": "^9.1.7",
114114
"jsonc-parser": "^3.3.1",
115115
"module-replacements": "^2.11.0",

pnpm-lock.yaml

Lines changed: 19 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export const VERSION_TRIGGER_CHARACTERS = ['.', '^', '~', ...Array.from({ length
88

99
export const CACHE_TTL_ONE_DAY = 1000 * 60 * 60 * 24
1010

11+
export const NPMJS_COM = 'https://npmjs.com'
1112
export const NPMX_DEV = 'https://npmx.dev'
12-
13-
export const NPM_REGISTRY = 'https://registry.npmjs.org'
1413
export const NPMX_DEV_API = `${NPMX_DEV}/api`

src/providers/completion-item/version.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,27 @@ export class VersionCompletionItemProvider<T extends Extractor> implements Compl
3434

3535
const prefix = extractVersionPrefix(version)
3636

37-
let versionsKV = Object.values(pkg.versions)
37+
const items: CompletionItem[] = []
3838

39-
if (config.completion.version === 'provenance-only')
40-
versionsKV = versionsKV.filter(({ hasProvenance }) => hasProvenance)
39+
for (const version in pkg.versionsMeta) {
40+
const meta = pkg.versionsMeta[version]
41+
42+
if (config.completion.version === 'provenance-only' && !meta.provenance)
43+
continue
4144

42-
return versionsKV.map(({ version, tag }) => {
4345
const text = `${prefix}${version}`
4446
const item = new CompletionItem(text, CompletionItemKind.Value)
4547

4648
item.range = this.extractor.getNodeRange(document, versionNode)
4749
item.insertText = text
50+
51+
const tag = pkg.versionToTag.get(version)
4852
if (tag)
4953
item.detail = tag
5054

51-
return item
52-
})
55+
items.push(item)
56+
}
57+
58+
return items
5359
}
5460
}

src/providers/diagnostics/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DependencyInfo, Extractor, ValidNode } from '#types/extractor'
2-
import type { ResolvedPackument } from '#utils/api/package'
2+
import type { PackageInfo } from '#utils/api/package'
33
import type { Awaitable } from 'reactive-vscode'
44
import type { Diagnostic, TextDocument } from 'vscode'
55
import { basename } from 'node:path'
@@ -15,7 +15,7 @@ import { checkVulnerability } from './rules/vulnerability'
1515
export interface NodeDiagnosticInfo extends Pick<Diagnostic, 'message' | 'severity' | 'code'> {
1616
node: ValidNode
1717
}
18-
export type DiagnosticRule = (dep: DependencyInfo, pkg: ResolvedPackument) => Awaitable<NodeDiagnosticInfo | undefined>
18+
export type DiagnosticRule = (dep: DependencyInfo, pkg: PackageInfo) => Awaitable<NodeDiagnosticInfo | undefined>
1919

2020
const enabledRules = computed<DiagnosticRule[]>(() => {
2121
const rules: DiagnosticRule[] = []

src/providers/diagnostics/rules/deprecation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { DiagnosticSeverity, Uri } from 'vscode'
55

66
export const checkDeprecation: DiagnosticRule = (dep, pkg) => {
77
const exactVersion = extractVersion(dep.version)
8-
const versionInfo = pkg.versions[exactVersion]
8+
const versionInfo = pkg.versionsMeta[exactVersion]
99

1010
if (!versionInfo?.deprecated)
1111
return

src/providers/diagnostics/rules/replacement.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ModuleReplacement } from '#utils/api/replacement'
1+
import type { ModuleReplacement } from 'module-replacements'
22
import type { DiagnosticRule } from '..'
33
import { getReplacement } from '#utils/api/replacement'
44
import { DiagnosticSeverity, Uri } from 'vscode'
@@ -11,7 +11,11 @@ function getReplacementsDocUrl(path: string): string {
1111
return `https://github.com/es-tooling/module-replacements/blob/main/docs/modules/${path}.md`
1212
}
1313

14-
// https://github.com/npmx-dev/npmx.dev/blob/main/app/components/PackageReplacement.vue#L8-L30
14+
/**
15+
* Keep messages in sync with npmx.dev wording.
16+
*
17+
* https://github.com/npmx-dev/npmx.dev/blob/main/app/components/PackageReplacement.vue#L8-L30
18+
*/
1519
function getReplacementInfo(replacement: ModuleReplacement) {
1620
switch (replacement.type) {
1721
case 'native':

src/providers/diagnostics/rules/vulnerability.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const DIAGNOSTIC_MAPPING: Record<Exclude<OsvSeverityLevel, 'unknown'>, Diagnosti
1414

1515
export const checkVulnerability: DiagnosticRule = async (dep, pkg) => {
1616
const exactVersion = extractVersion(dep.version)
17-
const versionInfo = pkg.versions[exactVersion]
17+
const versionInfo = pkg.versionsMeta[exactVersion]
1818

1919
if (!versionInfo)
2020
return

src/providers/hover/npmx.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Extractor } from '#types/extractor'
22
import type { HoverProvider, Position, TextDocument } from 'vscode'
33
import { getPackageInfo } from '#utils/api/package'
4+
import { npmPacakgeUrl, npmxDocsUrl, npmxPackageUrl } from '#utils/links'
45
import { extractVersion } from '#utils/package'
56
import { Hover, MarkdownString } from 'vscode'
67

@@ -30,15 +31,15 @@ export class NpmxHoverProvider<T extends Extractor> implements HoverProvider {
3031
if (!pkg)
3132
return
3233

33-
const currentVersion = pkg.versions[coercedVersion]
34+
const currentVersion = pkg.versionsMeta[coercedVersion]
3435
if (currentVersion) {
35-
if (currentVersion.hasProvenance)
36-
md.appendMarkdown(`[$(verified) Verified provenance](https://www.npmjs.com/package/${name}/v/${currentVersion.version}#provenance)\n\n`)
36+
if (currentVersion.provenance)
37+
md.appendMarkdown(`[$(verified) Verified provenance](${npmPacakgeUrl(name, coercedVersion)}#provenance)\n\n`)
3738
}
3839

3940
const footer = [
40-
`**[View on npmx](https://npmx.dev/package/${name})**`,
41-
`**[View docs on npmx](https://npmx.dev/docs/${name}/v/${coercedVersion})**`,
41+
`[View on npmx](${npmxPackageUrl(name)})`,
42+
`[View docs on npmx](${npmxDocsUrl(name, coercedVersion)})`,
4243
]
4344

4445
md.appendMarkdown(`${footer.join(' | ')}\n`)

src/utils/api/package.ts

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,43 @@
1-
import type { Packument, PackumentVersion } from '@npm/types'
2-
import { NPM_REGISTRY } from '#constants'
1+
import type { PackageVersionsInfoWithMetadata } from 'fast-npm-meta'
32
import { logger } from '#state'
4-
import { encodePackageName } from '#utils/package'
5-
import { ofetch } from 'ofetch'
3+
import { getVersions } from 'fast-npm-meta'
64
import { memoize } from '../memoize'
75

8-
interface ResolvedPackumentVersion extends Pick<PackumentVersion, 'version'> {
9-
tag?: string
10-
hasProvenance: boolean
11-
deprecated?: string
6+
export interface PackageInfo extends PackageVersionsInfoWithMetadata {
7+
versionToTag: Map<string, string>
128
}
139

14-
export interface ResolvedPackument {
15-
versions: Record<string, ResolvedPackumentVersion>
16-
}
17-
18-
export const getPackageInfo = memoize<string, Promise<ResolvedPackument>>(async (name) => {
10+
/**
11+
* Fetch npm package versions and build a version-to-tag lookup map.
12+
*
13+
* @see https://github.com/antfu/fast-npm-meta
14+
*/
15+
export const getPackageInfo = memoize<string, Promise<PackageInfo | null>>(async (name) => {
1916
logger.info(`Fetching package info for ${name}`)
20-
const encodedName = encodePackageName(name)
21-
22-
const pkg = await ofetch<Packument>(`${NPM_REGISTRY}/${encodedName}`)
23-
logger.info(`Fetched package info for ${name}`)
2417

25-
const resolvedVersions = Object.fromEntries(
26-
Object.keys(pkg.versions)
27-
.filter((v) => pkg.time[v])
28-
.map<[string, ResolvedPackumentVersion]>((v) => [
29-
v,
30-
{
31-
version: v,
32-
// @ts-expect-error present if published with provenance
33-
hasProvenance: !!pkg.versions[v].dist.attestations,
34-
deprecated: pkg.versions[v].deprecated,
35-
},
36-
]),
37-
)
38-
39-
Object.entries(pkg['dist-tags']).forEach(([tag, version]) => {
40-
resolvedVersions[version].tag = tag
18+
const pkg = await getVersions(name, {
19+
metadata: true,
20+
throw: false,
4121
})
4222

43-
return {
44-
versions: resolvedVersions,
23+
if ('error' in pkg) {
24+
logger.warn(`Fetching package info for ${name} error: ${JSON.stringify(pkg)}`)
25+
26+
// Return null to trigger a cache hit
27+
if (pkg.status === 404)
28+
return null
29+
30+
throw pkg
4531
}
32+
33+
logger.info(`Fetched package info for ${name}`)
34+
35+
const versionToTag = new Map<string, string>()
36+
if (pkg.distTags) {
37+
for (const [tag, ver] of Object.entries(pkg.distTags)) {
38+
versionToTag.set(ver, tag)
39+
}
40+
}
41+
42+
return { ...pkg, versionToTag }
4643
})

0 commit comments

Comments
 (0)