mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-30 04:52:04 -04:00
feat: improve dep path of local deps
This commit is contained in:
6
.changeset/ninety-lizards-reply.md
Normal file
6
.changeset/ninety-lizards-reply.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"dependency-path": major
|
||||
"pnpm": major
|
||||
---
|
||||
|
||||
Use a nicer path for saving local dependencies in the virtual store.
|
||||
@@ -250,7 +250,6 @@ export default async function linkPackages (
|
||||
newHoistedDependencies = await hoist({
|
||||
lockfile: hoistLockfile,
|
||||
importerIds: projectIds,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
privateHoistedModulesDir: opts.hoistedModulesDir,
|
||||
privateHoistPattern: opts.hoistPattern ?? [],
|
||||
publicHoistedModulesDir: opts.rootModulesDir,
|
||||
|
||||
@@ -1149,7 +1149,7 @@ test('local tarball dependency with peer dependency', async () => {
|
||||
], await testDefaults({ reporter }))
|
||||
|
||||
const integrityLocalPkgDirs = (await fs.readdir('node_modules/.pnpm'))
|
||||
.filter((dir) => dir.startsWith('local+'))
|
||||
.filter((dir) => dir.startsWith('file+'))
|
||||
|
||||
expect(integrityLocalPkgDirs.length).toBe(1)
|
||||
|
||||
@@ -1166,7 +1166,7 @@ test('local tarball dependency with peer dependency', async () => {
|
||||
|
||||
{
|
||||
const updatedLocalPkgDirs = (await fs.readdir('node_modules/.pnpm'))
|
||||
.filter((dir) => dir.startsWith('local+'))
|
||||
.filter((dir) => dir.startsWith('file+'))
|
||||
expect(updatedLocalPkgDirs).toStrictEqual(integrityLocalPkgDirs)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -395,7 +395,7 @@ function getPkgInfo (
|
||||
isPeer: Boolean(opts.peers?.has(opts.alias)),
|
||||
isSkipped,
|
||||
name,
|
||||
path: depPath ? path.join(opts.modulesDir, '.pnpm', depPathToFilename(depPath, opts.lockfileDir)) : path.join(opts.modulesDir, '..', opts.ref.substr(5)),
|
||||
path: depPath ? path.join(opts.modulesDir, '.pnpm', depPathToFilename(depPath)) : path.join(opts.modulesDir, '..', opts.ref.substr(5)),
|
||||
version,
|
||||
}
|
||||
if (resolved) {
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"dependencies": {
|
||||
"@pnpm/types": "workspace:7.10.0",
|
||||
"encode-registry": "^3.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"semver": "^7.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import crypto from 'crypto'
|
||||
import path from 'path'
|
||||
import { Registries } from '@pnpm/types'
|
||||
import encodeRegistry from 'encode-registry'
|
||||
import normalize from 'normalize-path'
|
||||
import semver from 'semver'
|
||||
|
||||
export function isAbsolute (dependencyPath: string) {
|
||||
@@ -130,15 +128,15 @@ export function parse (dependencyPath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function depPathToFilename (depPath: string, lockfileDir: string) {
|
||||
const filename = depPathToFilenameUnescaped(depPath, lockfileDir).replace(/\//g, '+')
|
||||
if (filename.length > 120 || filename !== filename.toLowerCase() && !filename.startsWith('local+')) {
|
||||
export function depPathToFilename (depPath: string) {
|
||||
const filename = depPathToFilenameUnescaped(depPath).replace(/\//g, '+')
|
||||
if (filename.length > 120 || filename !== filename.toLowerCase() && !filename.startsWith('file+')) {
|
||||
return `${filename.substring(0, 50)}_${crypto.createHash('md5').update(filename).digest('hex')}`
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
function depPathToFilenameUnescaped (depPath: string, lockfileDir: string) {
|
||||
function depPathToFilenameUnescaped (depPath: string) {
|
||||
if (depPath.indexOf('file:') !== 0) {
|
||||
if (depPath.startsWith('/')) {
|
||||
depPath = depPath.substring(1)
|
||||
@@ -146,7 +144,5 @@ function depPathToFilenameUnescaped (depPath: string, lockfileDir: string) {
|
||||
const index = depPath.lastIndexOf('/')
|
||||
return `${depPath.substring(0, index)}@${depPath.substr(index + 1)}`
|
||||
}
|
||||
|
||||
const absolutePath = normalize(path.join(lockfileDir, depPath.slice(5)))
|
||||
return `local+${absolutePath.replace(':', '+')}`
|
||||
return depPath.replace(':', '+')
|
||||
}
|
||||
|
||||
@@ -128,16 +128,16 @@ test('resolve()', () => {
|
||||
})
|
||||
|
||||
test('depPathToFilename()', () => {
|
||||
expect(depPathToFilename('/foo/1.0.0', process.cwd())).toBe('foo@1.0.0')
|
||||
expect(depPathToFilename('/@foo/bar/1.0.0', process.cwd())).toBe('@foo+bar@1.0.0')
|
||||
expect(depPathToFilename('github.com/something/foo/0000', process.cwd())).toBe('github.com+something+foo@0000')
|
||||
expect(depPathToFilename('/foo/1.0.0')).toBe('foo@1.0.0')
|
||||
expect(depPathToFilename('/@foo/bar/1.0.0')).toBe('@foo+bar@1.0.0')
|
||||
expect(depPathToFilename('github.com/something/foo/0000')).toBe('github.com+something+foo@0000')
|
||||
|
||||
const filename = depPathToFilename('file:./test/foo-1.0.0.tgz_foo@2.0.0', process.cwd())
|
||||
expect(filename).toMatch(/^local\+.*\+foo-1\.0\.0\.tgz_foo@2\.0\.0$/)
|
||||
const filename = depPathToFilename('file:test/foo-1.0.0.tgz_foo@2.0.0')
|
||||
expect(filename).toBe('file+test+foo-1.0.0.tgz_foo@2.0.0')
|
||||
expect(filename).not.toContain(':')
|
||||
|
||||
expect(depPathToFilename('abcd/'.repeat(200), process.cwd())).toBe('abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+_27524303f1ddd808db67f175ff83606e')
|
||||
expect(depPathToFilename('/JSONSteam/1.0.0', process.cwd())).toBe('JSONSteam@1.0.0_4b2567ab922fbdf01171f59fab8f6fef')
|
||||
expect(depPathToFilename('abcd/'.repeat(200))).toBe('abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+_27524303f1ddd808db67f175ff83606e')
|
||||
expect(depPathToFilename('/JSONSteam/1.0.0')).toBe('JSONSteam@1.0.0_4b2567ab922fbdf01171f59fab8f6fef')
|
||||
})
|
||||
|
||||
test('tryGetPackageId', () => {
|
||||
|
||||
@@ -343,7 +343,6 @@ export default async (opts: HeadlessOptions) => {
|
||||
newHoistedDependencies = await hoist({
|
||||
lockfile: hoistLockfile,
|
||||
importerIds,
|
||||
lockfileDir,
|
||||
privateHoistedModulesDir: hoistedModulesDir,
|
||||
privateHoistPattern: opts.hoistPattern ?? [],
|
||||
publicHoistedModulesDir,
|
||||
@@ -429,7 +428,6 @@ export default async (opts: HeadlessOptions) => {
|
||||
}
|
||||
|
||||
const projectsToBeBuilt = extendProjectsWithTargetDirs(opts.projects, wantedLockfile, {
|
||||
lockfileDir: opts.lockfileDir,
|
||||
pkgLocationByDepPath,
|
||||
virtualStoreDir,
|
||||
})
|
||||
|
||||
@@ -99,7 +99,7 @@ export default async function lockfileToDepGraph (
|
||||
const pkgSnapshot = lockfile.packages![depPath]
|
||||
// TODO: optimize. This info can be already returned by pkgSnapshotToResolution()
|
||||
const { name: pkgName, version: pkgVersion } = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
|
||||
const modules = path.join(opts.virtualStoreDir, dp.depPathToFilename(depPath, opts.lockfileDir), 'node_modules')
|
||||
const modules = path.join(opts.virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules')
|
||||
const packageId = packageIdFromSnapshot(depPath, pkgSnapshot, opts.registries)
|
||||
|
||||
const pkg = {
|
||||
@@ -244,7 +244,7 @@ async function getChildrenPaths (
|
||||
} else if (childPkgSnapshot) {
|
||||
if (ctx.skipped.has(childRelDepPath)) continue
|
||||
const pkgName = nameVerFromPkgSnapshot(childRelDepPath, childPkgSnapshot).name
|
||||
children[alias] = path.join(ctx.virtualStoreDir, dp.depPathToFilename(childRelDepPath, ctx.lockfileDir), 'node_modules', pkgName)
|
||||
children[alias] = path.join(ctx.virtualStoreDir, dp.depPathToFilename(childRelDepPath), 'node_modules', pkgName)
|
||||
} else if (allDeps[alias].indexOf('file:') === 0) {
|
||||
children[alias] = path.resolve(ctx.lockfileDir, allDeps[alias].substr(5))
|
||||
} else if (!ctx.skipped.has(childRelDepPath) && ((peerDeps == null) || !peerDeps.has(alias))) {
|
||||
|
||||
@@ -17,7 +17,6 @@ const hoistLogger = logger('hoist')
|
||||
export default async function hoistByLockfile (
|
||||
opts: {
|
||||
lockfile: Lockfile
|
||||
lockfileDir: string
|
||||
importerIds?: string[]
|
||||
privateHoistPattern: string[]
|
||||
privateHoistedModulesDir: string
|
||||
@@ -55,7 +54,6 @@ export default async function hoistByLockfile (
|
||||
|
||||
await symlinkHoistedDependencies(hoistedDependencies, {
|
||||
lockfile: opts.lockfile,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
privateHoistedModulesDir: opts.privateHoistedModulesDir,
|
||||
publicHoistedModulesDir: opts.publicHoistedModulesDir,
|
||||
virtualStoreDir: opts.virtualStoreDir,
|
||||
@@ -185,7 +183,6 @@ async function symlinkHoistedDependencies (
|
||||
hoistedDependencies: HoistedDependencies,
|
||||
opts: {
|
||||
lockfile: Lockfile
|
||||
lockfileDir: string
|
||||
privateHoistedModulesDir: string
|
||||
publicHoistedModulesDir: string
|
||||
virtualStoreDir: string
|
||||
@@ -201,7 +198,7 @@ async function symlinkHoistedDependencies (
|
||||
return
|
||||
}
|
||||
const pkgName = nameVerFromPkgSnapshot(depPath, pkgSnapshot).name
|
||||
const modules = path.join(opts.virtualStoreDir, dp.depPathToFilename(depPath, opts.lockfileDir), 'node_modules')
|
||||
const modules = path.join(opts.virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules')
|
||||
const depLocation = path.join(modules, pkgName)
|
||||
await Promise.all(Object.entries(pkgAliases).map(async ([pkgAlias, hoistType]) => {
|
||||
const targetDir = hoistType === 'public'
|
||||
|
||||
@@ -114,7 +114,7 @@ export function lockfileToPackageRegistry (
|
||||
// Seems like this field should always contain a relative path
|
||||
let packageLocation = normalizePath(path.relative(opts.lockfileDir, path.join(
|
||||
opts.virtualStoreDir,
|
||||
depPathToFilename(relDepPath, opts.lockfileDir),
|
||||
depPathToFilename(relDepPath),
|
||||
'node_modules',
|
||||
name
|
||||
)))
|
||||
|
||||
@@ -7,14 +7,13 @@ export default function extendProjectsWithTargetDirs<T> (
|
||||
projects: Array<T & { id: string }>,
|
||||
lockfile: Lockfile,
|
||||
ctx: {
|
||||
lockfileDir: string
|
||||
virtualStoreDir: string
|
||||
pkgLocationByDepPath?: Record<string, string>
|
||||
}
|
||||
): Array<T & { id: string, stages: string[], targetDirs: string[] }> {
|
||||
const getLocalLocation = ctx.pkgLocationByDepPath != null
|
||||
? (depPath: string) => ctx.pkgLocationByDepPath![depPath]
|
||||
: (depPath: string, pkgName: string) => path.join(ctx.virtualStoreDir, depPathToFilename(depPath, ctx.lockfileDir), 'node_modules', pkgName)
|
||||
: (depPath: string, pkgName: string) => path.join(ctx.virtualStoreDir, depPathToFilename(depPath), 'node_modules', pkgName)
|
||||
const projectsById: Record<string, T & { id: string, targetDirs: string[], stages?: string[] }> =
|
||||
fromPairs(projects.map((project) => [project.id, { ...project, targetDirs: [] as string[] }]))
|
||||
Object.entries(lockfile.packages ?? {})
|
||||
|
||||
@@ -152,13 +152,13 @@ export default async function prune (
|
||||
const _tryRemovePkg = tryRemovePkg.bind(null, opts.lockfileDir, opts.virtualStoreDir)
|
||||
await Promise.all(
|
||||
orphanDepPaths
|
||||
.map((orphanDepPath) => depPathToFilename(orphanDepPath, opts.lockfileDir))
|
||||
.map((orphanDepPath) => depPathToFilename(orphanDepPath))
|
||||
.map(async (orphanDepPath) => _tryRemovePkg(orphanDepPath))
|
||||
)
|
||||
const neededPkgs: Set<string> = new Set()
|
||||
for (const depPath of Object.keys(opts.wantedLockfile.packages ?? {})) {
|
||||
if (opts.skipped.has(depPath)) continue
|
||||
neededPkgs.add(depPathToFilename(depPath, opts.lockfileDir))
|
||||
neededPkgs.add(depPathToFilename(depPath))
|
||||
}
|
||||
const availablePkgs = await readVirtualStoreDir(opts.virtualStoreDir, opts.lockfileDir)
|
||||
await Promise.all(
|
||||
|
||||
@@ -23,13 +23,13 @@ const STAT_DEFAULT = {
|
||||
export default async function createFuseHandlers (lockfileDir: string, cafsDir: string) {
|
||||
const lockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: true })
|
||||
if (lockfile == null) throw new Error('Cannot generate a .pnp.cjs without a lockfile')
|
||||
return createFuseHandlersFromLockfile(lockfile, lockfileDir, cafsDir)
|
||||
return createFuseHandlersFromLockfile(lockfile, cafsDir)
|
||||
}
|
||||
|
||||
/* eslint-disable node/no-callback-literal */
|
||||
export function createFuseHandlersFromLockfile (lockfile: Lockfile, lockfileDir: string, cafsDir: string) {
|
||||
export function createFuseHandlersFromLockfile (lockfile: Lockfile, cafsDir: string) {
|
||||
const pkgSnapshotCache = new Map<string, { name: string, version: string, pkgSnapshot: PackageSnapshot, index: PackageFilesIndex }>()
|
||||
const virtualNodeModules = makeVirtualNodeModules(lockfile, lockfileDir)
|
||||
const virtualNodeModules = makeVirtualNodeModules(lockfile)
|
||||
return {
|
||||
open (p: string, flags: string | number, cb: (exitCode: number, fd?: number) => void) {
|
||||
const dirEnt = getDirEnt(p)
|
||||
|
||||
@@ -16,18 +16,18 @@ type DirEntry = {
|
||||
entries: Record<string, DirEntry>
|
||||
}
|
||||
|
||||
export default function (lockfile: Lockfile, lockfileDir: string): DirEntry {
|
||||
export default function (lockfile: Lockfile): DirEntry {
|
||||
const entries: Record<string, DirEntry> = {
|
||||
'.pnpm': {
|
||||
entryType: 'directory',
|
||||
entries: createVirtualStoreDir(lockfile, lockfileDir),
|
||||
entries: createVirtualStoreDir(lockfile),
|
||||
},
|
||||
}
|
||||
for (const depType of DEPENDENCIES_FIELDS) {
|
||||
for (const [depName, ref] of Object.entries(lockfile.importers['.'][depType] ?? {})) {
|
||||
const symlink: DirEntry = {
|
||||
entryType: 'symlink',
|
||||
target: `./.pnpm/${dp.depPathToFilename(dp.refToRelative(ref, depName)!, lockfileDir)}/node_modules/${depName}`,
|
||||
target: `./.pnpm/${dp.depPathToFilename(dp.refToRelative(ref, depName)!)}/node_modules/${depName}`,
|
||||
}
|
||||
addDirEntry(entries, depName, symlink)
|
||||
}
|
||||
@@ -38,12 +38,12 @@ export default function (lockfile: Lockfile, lockfileDir: string): DirEntry {
|
||||
}
|
||||
}
|
||||
|
||||
function createVirtualStoreDir (lockfile: Lockfile, lockfileDir: string) {
|
||||
function createVirtualStoreDir (lockfile: Lockfile) {
|
||||
const rootDir = {} as Record<string, DirEntry>
|
||||
for (const [depPath, pkgSnapshot] of Object.entries(lockfile.packages ?? {})) {
|
||||
const { name } = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
|
||||
const pkgNodeModules = {} as Record<string, DirEntry>
|
||||
const currentPath = dp.depPathToFilename(depPath, lockfileDir)
|
||||
const currentPath = dp.depPathToFilename(depPath)
|
||||
const pkgDir: DirEntry = {
|
||||
entryType: 'index',
|
||||
depPath,
|
||||
@@ -52,7 +52,7 @@ function createVirtualStoreDir (lockfile: Lockfile, lockfileDir: string) {
|
||||
for (const [depName, ref] of Object.entries({ ...pkgSnapshot.dependencies, ...pkgSnapshot.optionalDependencies })) {
|
||||
const symlink: DirEntry = {
|
||||
entryType: 'symlink',
|
||||
target: normalize(path.relative(`${currentPath}/node_modules/`, `${dp.depPathToFilename(dp.refToRelative(ref, depName)!, lockfileDir)}/node_modules/${depName}`)),
|
||||
target: normalize(path.relative(`${currentPath}/node_modules/`, `${dp.depPathToFilename(dp.refToRelative(ref, depName)!)}/node_modules/${depName}`)),
|
||||
}
|
||||
addDirEntry(pkgNodeModules, depName, symlink)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,5 @@ import makeVirtualNodeModules from '../src/makeVirtualNodeModules'
|
||||
|
||||
test('makeVirtualNodeModules', async () => {
|
||||
const lockfile = await readWantedLockfile(path.join(__dirname, '__fixtures__/simple'), { ignoreIncompatible: true })
|
||||
const cafsDir = path.join(__dirname, '__fixtures__/simple/store/v3/files')
|
||||
expect(makeVirtualNodeModules(lockfile!, cafsDir)).toMatchSnapshot()
|
||||
expect(makeVirtualNodeModules(lockfile!)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -311,7 +311,7 @@ function fetchToStore (
|
||||
if (!opts.pkg.name) {
|
||||
opts.fetchRawManifest = true
|
||||
}
|
||||
const targetRelative = depPathToFilename(opts.pkg.id, opts.lockfileDir)
|
||||
const targetRelative = depPathToFilename(opts.pkg.id)
|
||||
const target = path.join(ctx.storeDir, targetRelative)
|
||||
|
||||
if (!ctx.fetchingLocker.has(opts.pkg.id)) {
|
||||
|
||||
@@ -269,9 +269,9 @@ async function _rebuild (
|
||||
async () => {
|
||||
const pkgSnapshot = pkgSnapshots[depPath]
|
||||
const pkgInfo = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
|
||||
const pkgRoot = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath, opts.lockfileDir), 'node_modules', pkgInfo.name)
|
||||
const pkgRoot = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules', pkgInfo.name)
|
||||
try {
|
||||
const modules = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath, opts.lockfileDir), 'node_modules')
|
||||
const modules = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules')
|
||||
const binPath = path.join(pkgRoot, 'node_modules', '.bin')
|
||||
await linkBins(modules, binPath, { warn })
|
||||
await runPostinstallHooks({
|
||||
@@ -316,7 +316,7 @@ async function _rebuild (
|
||||
.map(async (depPath) => limitLinking(async () => {
|
||||
const pkgSnapshot = pkgSnapshots[depPath]
|
||||
const pkgInfo = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
|
||||
const modules = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath, opts.lockfileDir), 'node_modules')
|
||||
const modules = path.join(ctx.virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules')
|
||||
const binPath = path.join(modules, pkgInfo.name, 'node_modules', '.bin')
|
||||
return linkBins(modules, binPath, { warn })
|
||||
}))
|
||||
|
||||
@@ -50,9 +50,9 @@ export default async function (maybeOpts: StoreStatusOptions) {
|
||||
const modified = await pFilter(pkgs, async ({ id, integrity, depPath, name }) => {
|
||||
const pkgIndexFilePath = integrity
|
||||
? getFilePathInCafs(cafsDir, integrity, 'index')
|
||||
: path.join(storeDir, dp.depPathToFilename(id, opts.dir), 'integrity.json')
|
||||
: path.join(storeDir, dp.depPathToFilename(id), 'integrity.json')
|
||||
const { files } = await loadJsonFile<PackageFilesIndex>(pkgIndexFilePath)
|
||||
return (await dint.check(path.join(virtualStoreDir, dp.depPathToFilename(depPath, opts.dir), 'node_modules', name), files)) === false
|
||||
return (await dint.check(path.join(virtualStoreDir, dp.depPathToFilename(depPath), 'node_modules', name), files)) === false
|
||||
}, { concurrency: 8 })
|
||||
|
||||
if ((reporter != null) && typeof reporter === 'function') {
|
||||
|
||||
@@ -585,7 +585,7 @@ async function resolveDependency (
|
||||
await exists(
|
||||
path.join(
|
||||
ctx.virtualStoreDir,
|
||||
dp.depPathToFilename(currentPkg.depPath, ctx.prefix),
|
||||
dp.depPathToFilename(currentPkg.depPath),
|
||||
'node_modules',
|
||||
currentPkg.name!,
|
||||
'package.json'
|
||||
|
||||
@@ -259,7 +259,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
|
||||
.map(({ name, version }) => ({ name, version })))
|
||||
depPath = `${resolvedPackage.depPath}${peersFolderSuffix}`
|
||||
}
|
||||
const localLocation = path.join(ctx.virtualStoreDir, depPathToFilename(depPath, ctx.lockfileDir))
|
||||
const localLocation = path.join(ctx.virtualStoreDir, depPathToFilename(depPath))
|
||||
const modules = path.join(localLocation, 'node_modules')
|
||||
const isPure = isEmpty(allResolvedPeers) && allMissingPeers.length === 0
|
||||
if (isPure) {
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -684,12 +684,10 @@ importers:
|
||||
'@types/semver': ^7.3.4
|
||||
dependency-path: workspace:8.0.11
|
||||
encode-registry: ^3.0.0
|
||||
normalize-path: ^3.0.0
|
||||
semver: ^7.3.4
|
||||
dependencies:
|
||||
'@pnpm/types': link:../types
|
||||
encode-registry: 3.0.0
|
||||
normalize-path: 3.0.0
|
||||
semver: 7.3.5
|
||||
devDependencies:
|
||||
'@types/semver': 7.3.9
|
||||
|
||||
Reference in New Issue
Block a user