feat!: only allow lockfile v6 (#6240)

This commit is contained in:
Zoltan Kochan
2023-03-19 04:23:51 +02:00
committed by GitHub
parent 9a908927f4
commit 158d8cf22f
22 changed files with 75 additions and 136 deletions

View File

@@ -0,0 +1,9 @@
---
"@pnpm/resolve-dependencies": major
"@pnpm/get-context": major
"@pnpm/lockfile-file": major
"@pnpm/core": major
"@pnpm/config": major
---
`useLockfileV6` field is deleted. Lockfile v5 cannot be written anymore, only transformed to the new format.

View File

@@ -0,0 +1 @@
lockfileVersion: '6.0'

View File

@@ -199,7 +199,7 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/rename-overwrite/1.0.3:
/rename-overwrite@1.0.3:
resolution: {integrity: sha512-hehcDzwprE09J1NkKbL8IEqKDB1g8yF0xKZcBN7uzGQKYfiHgY7pmQ66H7BzDeMcvfHDixPxDr4cDIAnVWYIeA==}
engines: {node: '>=4'}
dependencies:

View File

@@ -19,8 +19,6 @@ importers:
packages:
/is-positive@1.0.0:
resolution: {integrity: sha1-iACYVrZKLx632LsBeUGEJK4EUss=}
engines: {node: '>=0.10.0'}
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-iACYVrZKLx632LsBeUGEJK4EUss=

View File

@@ -15,8 +15,6 @@ importers:
packages:
/is-positive@3.1.0:
resolution: {integrity: sha1-hX21hKG6XRyymAUn/DtsQ103sP0=}
engines: {node: '>=0.10.0'}
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-hX21hKG6XRyymAUn/DtsQ103sP0=

View File

@@ -88,7 +88,6 @@ export interface Config {
registrySupportsTimeField?: boolean
failedToLoadBuiltInConfig: boolean
resolvePeersFromWorkspaceRoot?: boolean
useLockfileV6?: boolean
deployAllFiles?: boolean
// proxy
@@ -168,9 +167,6 @@ export interface Config {
changedFilesIgnorePattern?: string[]
rootProjectManifest?: ProjectManifest
userConfig: Record<string, string>
// feature flags for experimental testing
useInlineSpecifiersLockfileFormat?: boolean // For https://github.com/pnpm/pnpm/issues/4725
}
export interface ConfigWithDeprecatedSettings extends Config {

View File

@@ -114,8 +114,6 @@ export const types = Object.assign({
stream: Boolean,
'strict-peer-dependencies': Boolean,
'use-beta-cli': Boolean,
'use-inline-specifiers-lockfile-format': Boolean,
'use-lockfile-v6': Boolean,
'use-node-version': String,
'use-running-store-server': Boolean,
'use-store-server': Boolean,
@@ -235,8 +233,6 @@ export async function getConfig (
'strict-peer-dependencies': false,
'unsafe-perm': npmDefaults['unsafe-perm'],
'use-beta-cli': false,
'use-lockfile-v6': true,
'use-inline-specifiers-lockfile-format': false,
userconfig: npmDefaults.userconfig,
'verify-store-integrity': true,
'virtual-store-dir': 'node_modules/.pnpm',

View File

@@ -37,7 +37,6 @@ export async function writeWantedLockfile (
wantedLockfile: Lockfile,
opts?: {
forceSharedFormat?: boolean
useInlineSpecifiersFormat?: boolean
useGitBranchLockfile?: boolean
mergeGitBranchLockfiles?: boolean
}
@@ -64,7 +63,6 @@ export async function writeCurrentLockfile (
interface LockfileFormatOptions {
forceSharedFormat?: boolean
useInlineSpecifiersFormat?: boolean
}
async function writeLockfile (
@@ -76,13 +74,13 @@ async function writeLockfile (
const lockfilePath = path.join(pkgPath, lockfileFilename)
const isLockfileV6 = wantedLockfile['lockfileVersion'].toString().startsWith('6.')
const lockfileToStringify = (Boolean(opts?.useInlineSpecifiersFormat) || isLockfileV6)
const lockfileToStringify = isLockfileV6
? convertToInlineSpecifiersFormat(wantedLockfile) as unknown as Lockfile
: wantedLockfile
const yamlDoc = yamlStringify(lockfileToStringify, {
forceSharedFormat: opts?.forceSharedFormat === true,
includeEmptySpecifiersField: !opts?.useInlineSpecifiersFormat && !isLockfileV6,
includeEmptySpecifiersField: !isLockfileV6,
})
return writeFileAtomic(lockfilePath, yamlDoc)
@@ -227,7 +225,6 @@ function pruneTime (time: Record<string, string>, importers: Record<string, Proj
export async function writeLockfiles (
opts: {
forceSharedFormat?: boolean
useInlineSpecifiersFormat?: boolean
wantedLockfile: Lockfile
wantedLockfileDir: string
currentLockfile: Lockfile
@@ -242,12 +239,12 @@ export async function writeLockfiles (
const forceSharedFormat = opts?.forceSharedFormat === true
const isLockfileV6 = opts.wantedLockfile.lockfileVersion.toString().startsWith('6.')
const wantedLockfileToStringify = (Boolean(opts.useInlineSpecifiersFormat) || isLockfileV6)
const wantedLockfileToStringify = isLockfileV6
? convertToInlineSpecifiersFormat(opts.wantedLockfile) as unknown as Lockfile
: opts.wantedLockfile
const normalizeOpts = {
forceSharedFormat,
includeEmptySpecifiersField: !opts.useInlineSpecifiersFormat && !isLockfileV6,
includeEmptySpecifiersField: !isLockfileV6,
}
const yamlDoc = yamlStringify(wantedLockfileToStringify, normalizeOpts)
@@ -274,7 +271,7 @@ export async function writeLockfiles (
prefix: opts.wantedLockfileDir,
})
const currentLockfileToStringify = (Boolean(opts.useInlineSpecifiersFormat) || opts.wantedLockfile.lockfileVersion.toString().startsWith('6.'))
const currentLockfileToStringify = opts.wantedLockfile.lockfileVersion.toString().startsWith('6.')
? convertToInlineSpecifiersFormat(opts.currentLockfile) as unknown as Lockfile
: opts.currentLockfile
const currentYamlDoc = yamlStringify(currentLockfileToStringify, normalizeOpts)

View File

@@ -29,18 +29,3 @@ packages:
resolution: {integrity: sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=}
"
`;
exports[`writeLockfiles() when useInlineSpecifiersFormat 1`] = `
"lockfileVersion: 5.4-inlineSpecifiers
dependencies:
foo:
specifier: ^1.0.0
version: 1.0.0
packages:
/foo/1.0.0:
resolution: {integrity: sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=}
"
`;

View File

@@ -231,39 +231,3 @@ test('writeLockfiles() when useGitBranchLockfile', async () => {
expect(fs.existsSync(path.join(projectPath, WANTED_LOCKFILE))).toBeFalsy()
expect(fs.existsSync(path.join(projectPath, `pnpm-lock.${branchName}.yaml`))).toBeTruthy()
})
test('writeLockfiles() when useInlineSpecifiersFormat', async () => {
const projectPath = tempy.directory()
const wantedLockfile = {
importers: {
'.': {
dependencies: {
foo: '1.0.0',
},
specifiers: {
foo: '^1.0.0',
},
},
},
lockfileVersion: LOCKFILE_VERSION,
packages: {
'/foo/1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
},
}
await writeLockfiles({
currentLockfile: wantedLockfile,
currentLockfileDir: projectPath,
wantedLockfile,
wantedLockfileDir: projectPath,
useInlineSpecifiersFormat: true,
})
expect(await readCurrentLockfile(projectPath, { ignoreIncompatible: false })).toEqual(wantedLockfile)
expect(await readWantedLockfile(projectPath, { ignoreIncompatible: false })).toEqual(wantedLockfile)
expect(fs.readFileSync(path.join(projectPath, WANTED_LOCKFILE), 'utf8')).toMatchSnapshot()
})

View File

@@ -33,7 +33,6 @@ export interface StrictInstallOptions {
saveLockfile: boolean
useGitBranchLockfile: boolean
mergeGitBranchLockfiles: boolean
useInlineSpecifiersLockfileFormat: boolean
linkWorkspacePackagesDepth: number
lockfileOnly: boolean
fixLockfile: boolean
@@ -119,7 +118,6 @@ export interface StrictInstallOptions {
resolveSymlinksInInjectedDirs: boolean
dedupeDirectDeps: boolean
dedupePeerDependents: boolean
useLockfileV6?: boolean
extendNodePath: boolean
}
@@ -194,10 +192,8 @@ const defaults = async (opts: InstallOptions) => {
!process.setgid ||
process.getuid() !== 0,
useLockfile: true,
useLockfileV6: true,
saveLockfile: true,
useGitBranchLockfile: false,
useInlineSpecifiersLockfileFormat: false,
mergeGitBranchLockfiles: false,
userAgent: `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`,
verifyStoreIntegrity: true,

View File

@@ -307,9 +307,6 @@ export async function mutateModules (
path: path.join(opts.lockfileDir, patchFile.path),
}), patchedDependencies)
: undefined
if (opts.useLockfileV6 == null) {
opts.useLockfileV6 = ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.')
}
let needsFullResolution = !maybeOpts.ignorePackageManifest &&
lockfileIsNotUpToDate(ctx.wantedLockfile, {
overrides: opts.overrides,
@@ -319,7 +316,7 @@ export async function mutateModules (
patchedDependencies,
}) ||
opts.fixLockfile ||
opts.useLockfileV6 && !ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.')
!ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.')
if (needsFullResolution) {
ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies
@@ -396,7 +393,6 @@ export async function mutateModules (
wantedLockfile: ctx.wantedLockfile,
wantedLockfileDir: ctx.lockfileDir,
forceSharedFormat: opts.forceSharedLockfile,
useInlineSpecifiersFormat: opts.useInlineSpecifiersLockfileFormat || opts.useLockfileV6,
useGitBranchLockfile: opts.useGitBranchLockfile,
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
})
@@ -911,7 +907,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
patchedDependencies: opts.patchedDependencies,
lockfileIncludeTarballUrl: opts.lockfileIncludeTarballUrl,
resolvePeersFromWorkspaceRoot: opts.resolvePeersFromWorkspaceRoot,
useLockfileV6: opts.useLockfileV6,
}
)
if (!opts.include.optionalDependencies || !opts.include.devDependencies || !opts.include.dependencies) {
@@ -949,17 +944,13 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
: newLockfile
if (opts.updateLockfileMinorVersion) {
if (opts.useLockfileV6) {
newLockfile.lockfileVersion = LOCKFILE_VERSION_V6
} else {
newLockfile.lockfileVersion = LOCKFILE_VERSION
}
newLockfile.lockfileVersion = LOCKFILE_VERSION_V6
}
const depsStateCache: DepsStateCache = {}
const lockfileOpts = {
forceSharedFormat: opts.forceSharedLockfile,
useInlineSpecifiersFormat: opts.useInlineSpecifiersLockfileFormat || opts.useLockfileV6,
useInlineSpecifiersFormat: true,
useGitBranchLockfile: opts.useGitBranchLockfile,
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
}

View File

@@ -0,0 +1,19 @@
lockfileVersion: 5.4
specifiers:
'@pnpm.e2e/pkg-with-1-dep': 100.0.0
dependencies:
'@pnpm.e2e/pkg-with-1-dep': 100.0.0
packages:
/@pnpm.e2e/dep-of-pkg-with-1-dep/100.1.0:
resolution: {integrity: sha512-CAF68U5SjOQOT2dubJxAxauvQwm0G2IWS+Si7xP6Za79ZAt2VmCG5LMoSfv7GkbKk0RvSm/EMpT4BQVGy1yCpg==}
dev: false
/@pnpm.e2e/pkg-with-1-dep/100.0.0:
resolution: {integrity: sha512-IGrkh3wu1jOzTFzi/S+XOkuhEwhV03qRaH7tcwPoPJ+AQo2cAuhgOBwczq2y8DPfob+BlzSmZXjEvEqoXKio7A==}
dependencies:
'@pnpm.e2e/dep-of-pkg-with-1-dep': 100.1.0
dev: false

View File

@@ -35,11 +35,16 @@ test('relative link', async () => {
await project.isExecutable('.bin/hello-world-js-bin')
const wantedLockfile = await project.readLockfile()
expect(wantedLockfile.dependencies['@pnpm.e2e/hello-world-js-bin']).toBe('link:../hello-world-js-bin')
expect(wantedLockfile.specifiers['@pnpm.e2e/hello-world-js-bin']).toBe('*')
expect(wantedLockfile.dependencies['@pnpm.e2e/hello-world-js-bin']).toStrictEqual({
version: 'link:../hello-world-js-bin',
specifier: '*',
})
const currentLockfile = await project.readCurrentLockfile()
expect(currentLockfile.dependencies['@pnpm.e2e/hello-world-js-bin']).toBe('link:../hello-world-js-bin')
expect(currentLockfile.dependencies['@pnpm.e2e/hello-world-js-bin']).toStrictEqual({
version: 'link:../hello-world-js-bin',
specifier: '*',
})
})
test('relative link is linked by the name of the alias', async () => {
@@ -233,7 +238,10 @@ test('link should not change the type of the dependency', async () => {
const wantedLockfile = await project.readLockfile()
expect(wantedLockfile.devDependencies).toStrictEqual({
'@pnpm.e2e/hello-world-js-bin': 'link:../hello-world-js-bin',
'@pnpm.e2e/hello-world-js-bin': {
version: 'link:../hello-world-js-bin',
specifier: '*',
},
})
})

View File

@@ -3,9 +3,10 @@ import path from 'path'
import { LOCKFILE_VERSION_V6 as LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
import { type RootLog } from '@pnpm/core-loggers'
import { type PnpmError } from '@pnpm/error'
import { fixtures } from '@pnpm/test-fixtures'
import { type Lockfile, type TarballResolution } from '@pnpm/lockfile-file'
import { type LockfileV6 } from '@pnpm/lockfile-types'
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
import { tempDir, prepareEmpty, preparePackages } from '@pnpm/prepare'
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
import { addDistTag, getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { type ProjectManifest } from '@pnpm/types'
@@ -26,6 +27,8 @@ import sinon from 'sinon'
import writeYamlFile from 'write-yaml-file'
import { testDefaults } from './utils'
const f = fixtures(__dirname)
const LOCKFILE_WARN_LOG = {
level: 'warn',
message: `A ${WANTED_LOCKFILE} file exists. The current configuration prohibits to read or write a lockfile`,
@@ -1471,23 +1474,15 @@ test('lockfile v6', async () => {
})
test('lockfile v5 is converted to lockfile v6', async () => {
const tmp = tempDir()
f.copy('lockfile-v5', tmp)
prepareEmpty()
const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep@100.0.0'], await testDefaults({ useLockfileV6: false }))
await install({ dependencies: { '@pnpm.e2e/pkg-with-1-dep': '100.0.0' } }, await testDefaults())
{
const lockfile = await readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
expect(lockfile.lockfileVersion).toBe(5.4)
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-1-dep/100.0.0'])
}
await install(manifest, await testDefaults({ useLockfileV6: true }))
{
const lockfile = await readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
expect(lockfile.lockfileVersion).toBe('6.0')
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-1-dep@100.0.0'])
}
const lockfile = await readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
expect(lockfile.lockfileVersion).toBe('6.0')
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/pkg-with-1-dep@100.0.0'])
})
test('update the lockfile when a new project is added to the workspace', async () => {

View File

@@ -92,8 +92,6 @@ export interface GetContextOptions {
publicHoistPattern?: string[] | undefined
forcePublicHoistPattern?: boolean
useLockfileV6?: boolean
}
export async function getContext (
@@ -183,7 +181,6 @@ export async function getContext (
useGitBranchLockfile: opts.useGitBranchLockfile,
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
virtualStoreDir,
useLockfileV6: opts.useLockfileV6,
}),
}
contextLogger.debug({
@@ -391,7 +388,6 @@ export async function getContextForSingleImporter (
publicHoistPattern?: string[] | undefined
forcePublicHoistPattern?: boolean
useLockfileV6?: boolean
},
alreadyPurged: boolean = false
): Promise<PnpmSingleContext> {
@@ -491,7 +487,6 @@ export async function getContextForSingleImporter (
useGitBranchLockfile: opts.useGitBranchLockfile,
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
virtualStoreDir,
useLockfileV6: opts.useLockfileV6,
}),
}
packageManifestLogger.debug({

View File

@@ -39,7 +39,6 @@ export async function readLockfiles (
useGitBranchLockfile?: boolean
mergeGitBranchLockfiles?: boolean
virtualStoreDir: string
useLockfileV6?: boolean
}
): Promise<{
currentLockfile: Lockfile
@@ -49,7 +48,7 @@ export async function readLockfiles (
wantedLockfile: Lockfile
lockfileHadConflicts: boolean
}> {
const wantedLockfileVersion = opts.useLockfileV6 ? LOCKFILE_VERSION_V6 : LOCKFILE_VERSION
const wantedLockfileVersion = LOCKFILE_VERSION_V6
// ignore `pnpm-lock.yaml` on CI servers
// a latest pnpm should not break all the builds
const lockfileOpts = {

View File

@@ -217,7 +217,6 @@ export async function resolveDependencies (
projects: projectsToLink,
virtualStoreDir: opts.virtualStoreDir,
resolvePeersFromWorkspaceRoot: Boolean(opts.resolvePeersFromWorkspaceRoot),
useLockfileV6: Boolean(opts.useLockfileV6),
})
for (const { id, manifest } of projectsToLink) {

View File

@@ -159,7 +159,6 @@ export interface ResolutionContext {
registries: Registries
resolutionMode?: 'highest' | 'time-based' | 'lowest-direct'
virtualStoreDir: string
useLockfileV6?: boolean
workspacePackages?: WorkspacePackages
missingPeersOfChildrenByPkgId: Record<string, { parentImporterId: string, missingPeersOfChildren: MissingPeersOfChildren }>
}
@@ -1175,7 +1174,7 @@ async function resolveDependency (
const patchFile = ctx.patchedDependencies?.[nameAndVersion]
if (patchFile) {
ctx.appliedPatches.add(nameAndVersion)
depPath += ctx.useLockfileV6 ? `(patch_hash=${patchFile.hash})` : `_${patchFile.hash}`
depPath += `(patch_hash=${patchFile.hash})`
}
// We are building the dependency tree only until there are new packages

View File

@@ -87,7 +87,6 @@ export interface ResolveDependenciesOptions {
virtualStoreDir: string
wantedLockfile: Lockfile
workspacePackages: WorkspacePackages
useLockfileV6?: boolean
}
export async function resolveDependencyTree<T> (
@@ -128,7 +127,6 @@ export async function resolveDependencyTree<T> (
updatedSet: new Set<string>(),
workspacePackages: opts.workspacePackages,
missingPeersOfChildrenByPkgId: {},
useLockfileV6: opts.useLockfileV6,
}
const resolveArgs: ImporterToResolve[] = importers.map((importer) => {

View File

@@ -6,7 +6,7 @@ import {
type PeerDependencyIssues,
type PeerDependencyIssuesByProjects,
} from '@pnpm/types'
import { depPathToFilename, createPeersFolderSuffix, createPeersFolderSuffixNewFormat } from '@pnpm/dependency-path'
import { depPathToFilename, createPeersFolderSuffixNewFormat } from '@pnpm/dependency-path'
import { type KeyValuePair } from 'ramda'
import isEmpty from 'ramda/src/isEmpty'
import mapValues from 'ramda/src/map'
@@ -63,7 +63,6 @@ export function resolvePeers<T extends PartialResolvedPackage> (
virtualStoreDir: string
lockfileDir: string
resolvePeersFromWorkspaceRoot?: boolean
useLockfileV6?: boolean
dedupePeerDependents?: boolean
}
): {
@@ -96,7 +95,6 @@ export function resolvePeers<T extends PartialResolvedPackage> (
purePkgs: new Set(),
rootDir,
virtualStoreDir: opts.virtualStoreDir,
useLockfileV6: opts.useLockfileV6,
})
if (!isEmpty(peerDependencyIssues.bad) || !isEmpty(peerDependencyIssues.missing)) {
peerDependencyIssuesByProjects[id] = {
@@ -240,7 +238,6 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
purePkgs: Set<string> // pure packages are those that don't rely on externally resolved peers
rootDir: string
lockfileDir: string
useLockfileV6?: boolean
}
): PeersResolution {
const node = ctx.dependenciesTree[nodeId]
@@ -321,7 +318,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
if (isEmpty(allResolvedPeers)) {
depPath = resolvedPackage.depPath
} else {
const peersFolderSuffix = (ctx.useLockfileV6 ? createPeersFolderSuffixNewFormat : createPeersFolderSuffix)(
const peersFolderSuffix = createPeersFolderSuffixNewFormat(
Object.entries(allResolvedPeers)
.map(([alias, nodeId]) => {
if (nodeId.startsWith('link:')) {
@@ -445,7 +442,6 @@ function resolvePeersOfChildren<T extends PartialResolvedPackage> (
dependenciesTree: DependenciesTree<T>
rootDir: string
lockfileDir: string
useLockfileV6?: boolean
}
): PeersResolution {
const allResolvedPeers: Record<string, string> = {}

View File

@@ -97,11 +97,11 @@ test('resolve peer dependencies of cyclic dependencies', () => {
lockfileDir: '',
})
expect(Object.keys(dependenciesGraph)).toStrictEqual([
'foo/1.0.0_qar@1.0.0+zoo@1.0.0',
'bar/1.0.0_foo@1.0.0+zoo@1.0.0',
'zoo/1.0.0_qar@1.0.0',
'qar/1.0.0_bar@1.0.0+foo@1.0.0',
'bar/1.0.0_foo@1.0.0',
'foo/1.0.0(qar@1.0.0)(zoo@1.0.0)',
'bar/1.0.0(foo@1.0.0)(zoo@1.0.0)',
'zoo/1.0.0(qar@1.0.0)',
'qar/1.0.0(bar@1.0.0)(foo@1.0.0)',
'bar/1.0.0(foo@1.0.0)',
'foo/1.0.0',
])
})
@@ -195,8 +195,8 @@ test('when a package is referenced twice in the dependencies graph and one of th
expect(Object.keys(dependenciesGraph)).toStrictEqual([
'foo/1.0.0',
'zoo/1.0.0',
'foo/1.0.0_qar@1.0.0',
'zoo/1.0.0_qar@1.0.0',
'foo/1.0.0(qar@1.0.0)',
'zoo/1.0.0(qar@1.0.0)',
'qar/1.0.0',
'bar/1.0.0',
])