diff --git a/src/install/lockfile/Tree.rs b/src/install/lockfile/Tree.rs index 66d3f9597a28..45696237fb26 100644 --- a/src/install/lockfile/Tree.rs +++ b/src/install/lockfile/Tree.rs @@ -649,6 +649,24 @@ impl Tree { return Ok(()); } + // Below another copy of itself, a package's dependencies are already placed; laying + // them out again only re-nests what the levels in between shadow, which for a cycle + // through two versions (a@1 -> b@1 -> a@2 -> b@2 -> a@1 ...) never ends. npm stops + // here as well (linking to the copy above); bun.lock.rs skips these copies to match. + { + let trees = builder.list.items_tree(); + let mut ancestor_id = self.id; + while ancestor_id != INVALID_ID { + let ancestor = &trees[ancestor_id as usize]; + if (ancestor.dependency_id as usize) < builder.resolutions.len() + && builder.resolutions[ancestor.dependency_id as usize] == parent_pkg_id + { + return Ok(()); + } + ancestor_id = ancestor.parent; + } + } + builder.list.append(BuilderEntry { tree: Tree { parent: self.id, @@ -1106,6 +1124,16 @@ impl Tree { } } + // A bundled root's walk ends below the folder holding the bundling package, which is + // resolvable from inside it; a bundled dependency depending back on it would otherwise + // copy it into itself, and the copy's bundled dependencies bring it back, forever. + if (this.dependency_id as usize) < deps.len() + && builder.resolutions[this.dependency_id as usize] == package_id + && deps[this.dependency_id as usize].name_hash == target_name_hash + { + return Ok(HoistDependencyResult::Hoisted); // 1 + } + // place the dependency in the current tree Ok(HoistDependencyResult::Placement(Placement { id: this.id, diff --git a/src/install/lockfile/bun.lock.rs b/src/install/lockfile/bun.lock.rs index 2efb8de91097..ff2ffc856d7b 100644 --- a/src/install/lockfile/bun.lock.rs +++ b/src/install/lockfile/bun.lock.rs @@ -1817,6 +1817,22 @@ impl PkgMap { } } +impl PkgMap { + /// Whether a folder on the way down to `pkg_path` holds `pkg_id` too. Each of those + /// folders is a key of its own; a prefix ending inside a scoped name matches nothing. + fn is_below_copy_of(&self, pkg_path: &[u8], pkg_id: PackageID) -> bool { + let mut end: usize = 0; + while let Some(i) = strings::index_of_char_usize(&pkg_path[end..], b'/') { + end += i; + if self.get(&pkg_path[..end]) == Some(&pkg_id) { + return true; + } + end += 1; + } + false + } +} + // const PkgMap = struct {}; fn object_rows(expr: &Expr) -> &[JSON::E::PropertyJSON] { @@ -3230,6 +3246,13 @@ pub(crate) fn parse_into_binary_lockfile( continue; } + // A copy below another copy of its package has no node_modules of its own + // (`Tree::process_subtree`), so walking up from it finds the versions it sits + // under rather than the package's resolutions; the copy above binds those. + if pkg_map.is_below_copy_of(pkg_path, pkg_id) { + continue; + } + // find resolutions. iterate up to root through the pkg path. let deps = pkg_deps[pkg_id as usize]; 'deps: for _dep_id in deps.begin()..deps.end() { diff --git a/test/cli/install/hoist.test.ts b/test/cli/install/hoist.test.ts index 12919c36b139..b5b480b60ab7 100644 --- a/test/cli/install/hoist.test.ts +++ b/test/cli/install/hoist.test.ts @@ -1,5 +1,6 @@ -import { afterAll, beforeAll, test } from "bun:test"; -import { VerdaccioRegistry, bunEnv, runBunInstall } from "harness"; +import { afterAll, beforeAll, expect, test } from "bun:test"; +import { VerdaccioRegistry, bunEnv, bunExe, runBunInstall } from "harness"; +import { join } from "node:path"; const registry = new VerdaccioRegistry(); @@ -28,3 +29,189 @@ test("should handle resolving optional peer from multiple instances of same pack // this shouldn't hit an assertion await runBunInstall(bunEnv, packageDir); }); + +// The hoist-*-cycle-* fixtures are described in registry/packages/create-hoist-cycle-packages.ts. +// +// These graphs can only be laid out by nesting: every package in the cycle conflicts with the +// version of its name one level up, so each copy used to get another copy nested below it and +// `bun install` never finished. The tree now ends at the first copy of a package that is nested +// below another copy of itself; that copy gets no node_modules of its own, so its dependency in +// the cycle resolves to the conflicting version next to it (no finite layout of these graphs avoids +// that). The trees below are what that produces, keyed the way bun.lock keys `packages`. +// +// bun.lock has to load back into the same tree. That copy's row resolves its dependencies to the +// wrong versions by path, so the loader has to bind the package from the copy above it instead; the +// reload half of each test fails if it does not, without --frozen-lockfile noticing. + +type Linker = "hoisted" | "isolated"; + +async function install(cwd: string, ...args: string[]) { + await using proc = Bun.spawn({ + cmd: [bunExe(), "install", ...args], + cwd, + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ args, stdout, stderr, exitCode }).toMatchObject({ + stderr: expect.not.stringContaining("error:"), + exitCode: 0, + }); +} + +async function lockfileTree(dir: string) { + const { packages } = Bun.JSONC.parse(await Bun.file(join(dir, "bun.lock")).text()) as { + packages: Record; + }; + return Object.fromEntries(Object.entries(packages).map(([path, [resolution]]) => [path, resolution])); +} + +async function installedPackageJsons(dir: string) { + const nodeModules = join(dir, "node_modules"); + const paths = await Array.fromAsync(new Bun.Glob("**/package.json").scan({ cwd: nodeModules, dot: true })); + return paths.map(path => path.replaceAll("\\", "/")).sort(); +} + +// Installs `files` from scratch, then again in a new directory from the bun.lock that produced, +// and returns the first install's directory. +async function installFreshAndFromLockfile( + files: Record, + linker: Linker, + expectedTree: Record, +) { + const { packageDir } = await registry.createTestDir({ bunfigOpts: { linker }, files }); + await install(packageDir); + const lockfile = await Bun.file(join(packageDir, "bun.lock")).text(); + expect(await lockfileTree(packageDir)).toEqual(expectedTree); + + const { packageDir: reloadDir } = await registry.createTestDir({ + bunfigOpts: { linker }, + files: { ...files, "bun.lock": lockfile }, + }); + await install(reloadDir, "--frozen-lockfile"); + expect(await installedPackageJsons(reloadDir)).toEqual(await installedPackageJsons(packageDir)); + + // --lockfile-only always writes, so this is the tree a reload builds, printed back. + await install(reloadDir, "--lockfile-only"); + expect(await Bun.file(join(reloadDir, "bun.lock")).text()).toBe(lockfile); + + return packageDir; +} + +test.each(["hoisted", "isolated"] as const)( + "a dependency cycle through two versions of the same packages is installed (%s linker)", + async linker => { + const packageDir = await installFreshAndFromLockfile( + { "package.json": JSON.stringify({ name: "pkg", dependencies: { "hoist-cycle-x": "1.0.0" } }) }, + linker, + { + "hoist-cycle-x": "hoist-cycle-x@1.0.0", + "hoist-cycle-y": "hoist-cycle-y@1.0.0", + "hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@2.0.0", + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@2.0.0", + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@1.0.0", + // This y@1.0.0 is below the y@1.0.0 at the root, so it gets no node_modules of its own. + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@1.0.0", + }, + ); + + if (linker === "hoisted") { + expect(await installedPackageJsons(packageDir)).toEqual([ + "hoist-cycle-x/package.json", + "hoist-cycle-y/node_modules/hoist-cycle-x/node_modules/hoist-cycle-y/node_modules/hoist-cycle-x/node_modules/hoist-cycle-y/package.json", + "hoist-cycle-y/node_modules/hoist-cycle-x/node_modules/hoist-cycle-y/node_modules/hoist-cycle-x/package.json", + "hoist-cycle-y/node_modules/hoist-cycle-x/node_modules/hoist-cycle-y/package.json", + "hoist-cycle-y/node_modules/hoist-cycle-x/package.json", + "hoist-cycle-y/package.json", + ]); + } + }, +); + +test("a cycle entered at both of its versions, from the root and from a workspace, is installed", async () => { + await installFreshAndFromLockfile( + { + "package.json": JSON.stringify({ + name: "pkg", + workspaces: ["packages/*"], + dependencies: { "hoist-cycle-x": "1.0.0" }, + }), + "packages/app/package.json": JSON.stringify({ + name: "app", + version: "1.0.0", + dependencies: { "hoist-cycle-x": "2.0.0" }, + }), + }, + "hoisted", + { + "app": "app@workspace:packages/app", + "hoist-cycle-x": "hoist-cycle-x@1.0.0", + "hoist-cycle-y": "hoist-cycle-y@1.0.0", + "hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@2.0.0", + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@2.0.0", + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@1.0.0", + "hoist-cycle-y/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@1.0.0", + // The workspace's branch enters the cycle at x@2.0.0, so it is x@2.0.0's copy that ends it. + "app/hoist-cycle-x": "hoist-cycle-x@2.0.0", + "app/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@2.0.0", + "app/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@1.0.0", + "app/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x/hoist-cycle-y": "hoist-cycle-y@1.0.0", + "app/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x/hoist-cycle-y/hoist-cycle-x": "hoist-cycle-x@2.0.0", + }, + ); +}); + +test.each(["hoisted", "isolated"] as const)( + "a bundled dependency with a peer on the package bundling it is installed (%s linker)", + async linker => { + // The plugin's peer is satisfied by the host it is bundled in. The host used to be copied into + // its own node_modules instead, and the copy bundled the plugin again below it, and so on. + await installFreshAndFromLockfile( + { "package.json": JSON.stringify({ name: "pkg", dependencies: { "hoist-bundled-cycle-host": "1.0.0" } }) }, + linker, + { + "hoist-bundled-cycle-host": "hoist-bundled-cycle-host@1.0.0", + "hoist-bundled-cycle-host/hoist-bundled-cycle-plugin": "hoist-bundled-cycle-plugin@1.0.0", + }, + ); + }, +); + +test("a cycle closed through an optional peer is installed", async () => { + await installFreshAndFromLockfile( + { + "package.json": JSON.stringify({ + name: "pkg", + dependencies: { "hoist-optional-peer-cycle-x": "2.0.0" }, + devDependencies: { "hoist-optional-peer-cycle-entry": "1.0.0" }, + }), + }, + "hoisted", + { + "hoist-optional-peer-cycle-entry": "hoist-optional-peer-cycle-entry@1.0.0", + "hoist-optional-peer-cycle-x": "hoist-optional-peer-cycle-x@2.0.0", + "hoist-optional-peer-cycle-y": "hoist-optional-peer-cycle-y@2.0.0", + "hoist-optional-peer-cycle-z": "hoist-optional-peer-cycle-z@2.0.0", + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x": "hoist-optional-peer-cycle-x@1.0.0", + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y": + "hoist-optional-peer-cycle-y@1.0.0", + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-z": + "hoist-optional-peer-cycle-z@1.0.0", + // y@1.0.0's peer on x@2.0.0, nested because x@1.0.0 is above it. + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-x": + "hoist-optional-peer-cycle-x@2.0.0", + // z@1.0.0's y@2.0.0, whose optional peer binds to the x@1.0.0 above it here. + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-y": + "hoist-optional-peer-cycle-y@2.0.0", + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y": + "hoist-optional-peer-cycle-y@2.0.0", + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-z": + "hoist-optional-peer-cycle-z@2.0.0", + // That y@2.0.0 sits below x@2.0.0, so the bound x@1.0.0 is nested under it; this x@1.0.0 is + // below the x@1.0.0 under `entry` and is where the tree ends. + "hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-x": + "hoist-optional-peer-cycle-x@1.0.0", + }, + ); +}); diff --git a/test/cli/install/registry/packages/create-hoist-cycle-packages.ts b/test/cli/install/registry/packages/create-hoist-cycle-packages.ts new file mode 100644 index 000000000000..2ab5f295b71c --- /dev/null +++ b/test/cli/install/registry/packages/create-hoist-cycle-packages.ts @@ -0,0 +1,129 @@ +#!/usr/bin/env bun +/** + * Generates the `hoist-*-cycle-*` fixtures used by hoist.test.ts. + * + * Each shape is a dependency cycle the hoister can only lay out by nesting: + * every package in the cycle conflicts with the version of its name one level + * up, so without a cutoff the tree builder nests copies forever. + * + * hoist-cycle (plain dependencies, two versions of each name): + * + * - hoist-cycle-x@1.0.0 -> hoist-cycle-y@1.0.0 + * - hoist-cycle-y@1.0.0 -> hoist-cycle-x@2.0.0 + * - hoist-cycle-x@2.0.0 -> hoist-cycle-y@2.0.0 + * - hoist-cycle-y@2.0.0 -> hoist-cycle-x@1.0.0 + * + * hoist-bundled-cycle: a package bundling a plugin that has a peer dependency + * back on the package bundling it. + * + * - hoist-bundled-cycle-host@1.0.0 -> plugin@1.0.0 (bundled) + * - hoist-bundled-cycle-plugin@1.0.0 peer on host@1.0.0 + * + * hoist-optional-peer-cycle: the cycle is closed through an optional peer. + * An optional peer is bound while the tree is built, to whichever version of + * its name is closest to the copy being processed, and that one binding is + * shared by every copy of the package. With x@2.0.0 at the root and x@1.0.0 + * nested under `entry`, y@2.0.0 is reached both next to x@1.0.0 (through z, + * which binds the peer to it) and below a nested x@2.0.0, where that binding + * conflicts and x@1.0.0 is nested again, which starts the cycle over. + * + * - hoist-optional-peer-cycle-entry@1.0.0 -> x@1.0.0 + * - hoist-optional-peer-cycle-x@1.0.0 -> y@1.0.0, z@1.0.0 + * - hoist-optional-peer-cycle-x@2.0.0 -> y@2.0.0, z@2.0.0 + * - hoist-optional-peer-cycle-y@1.0.0 peer on x@2.0.0 + * - hoist-optional-peer-cycle-y@2.0.0 optional peer on x@1.0.0 + * - hoist-optional-peer-cycle-z@1.0.0 -> y@2.0.0 + * - hoist-optional-peer-cycle-z@2.0.0 no dependencies + */ + +import { mkdir, writeFile } from "fs/promises"; +import { join } from "path"; + +const packagesDir = import.meta.dir; + +type Manifest = { + version: string; + dependencies?: Record; + peerDependencies?: Record; + peerDependenciesMeta?: Record; + bundleDependencies?: string[]; +}; + +const cycle = "hoist-cycle-"; +const bundled = "hoist-bundled-cycle-"; +const optional = "hoist-optional-peer-cycle-"; + +const packages: Record = { + [`${cycle}x`]: [ + { version: "1.0.0", dependencies: { [`${cycle}y`]: "1.0.0" } }, + { version: "2.0.0", dependencies: { [`${cycle}y`]: "2.0.0" } }, + ], + [`${cycle}y`]: [ + { version: "1.0.0", dependencies: { [`${cycle}x`]: "2.0.0" } }, + { version: "2.0.0", dependencies: { [`${cycle}x`]: "1.0.0" } }, + ], + + [`${bundled}host`]: [ + { + version: "1.0.0", + dependencies: { [`${bundled}plugin`]: "1.0.0" }, + bundleDependencies: [`${bundled}plugin`], + }, + ], + [`${bundled}plugin`]: [{ version: "1.0.0", peerDependencies: { [`${bundled}host`]: "1.0.0" } }], + + [`${optional}entry`]: [{ version: "1.0.0", dependencies: { [`${optional}x`]: "1.0.0" } }], + [`${optional}x`]: [ + { version: "1.0.0", dependencies: { [`${optional}y`]: "1.0.0", [`${optional}z`]: "1.0.0" } }, + { version: "2.0.0", dependencies: { [`${optional}y`]: "2.0.0", [`${optional}z`]: "2.0.0" } }, + ], + [`${optional}y`]: [ + { version: "1.0.0", peerDependencies: { [`${optional}x`]: "2.0.0" } }, + { + version: "2.0.0", + peerDependencies: { [`${optional}x`]: "1.0.0" }, + peerDependenciesMeta: { [`${optional}x`]: { optional: true } }, + }, + ], + [`${optional}z`]: [{ version: "1.0.0", dependencies: { [`${optional}y`]: "2.0.0" } }, { version: "2.0.0" }], +}; + +for (const [name, manifests] of Object.entries(packages)) { + const dir = join(packagesDir, name); + await mkdir(dir, { recursive: true }); + + const versions: Record = {}; + let latest = ""; + for (const manifest of manifests) { + const pkgJson = { name, ...manifest }; + const files: Record = { "package/package.json": JSON.stringify(pkgJson, null, 2) }; + for (const dep of manifest.bundleDependencies ?? []) { + files[`package/node_modules/${dep}/package.json`] = JSON.stringify( + { name: dep, version: manifest.dependencies![dep] }, + null, + 2, + ); + } + const tarball = join(dir, `${name}-${manifest.version}.tgz`); + await Bun.Archive.write(tarball, files, { compress: "gzip" }); + + const bytes = await Bun.file(tarball).bytes(); + versions[manifest.version] = { + ...pkgJson, + _id: `${name}@${manifest.version}`, + dist: { + integrity: `sha512-${Buffer.from(new Bun.CryptoHasher("sha512").update(bytes).digest()).toString("base64")}`, + shasum: new Bun.CryptoHasher("sha1").update(bytes).digest("hex"), + tarball: `http://localhost:4873/${name}/-/${name}-${manifest.version}.tgz`, + }, + }; + latest = manifest.version; + } + + await writeFile( + join(dir, "package.json"), + JSON.stringify({ _id: name, name, "dist-tags": { latest }, versions }, null, 2), + ); +} + +console.log("Created hoist cycle test packages"); diff --git a/test/cli/install/registry/packages/hoist-bundled-cycle-host/hoist-bundled-cycle-host-1.0.0.tgz b/test/cli/install/registry/packages/hoist-bundled-cycle-host/hoist-bundled-cycle-host-1.0.0.tgz new file mode 100644 index 000000000000..37507eb9689e Binary files /dev/null and b/test/cli/install/registry/packages/hoist-bundled-cycle-host/hoist-bundled-cycle-host-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-bundled-cycle-host/package.json b/test/cli/install/registry/packages/hoist-bundled-cycle-host/package.json new file mode 100644 index 000000000000..3ab9a86e8308 --- /dev/null +++ b/test/cli/install/registry/packages/hoist-bundled-cycle-host/package.json @@ -0,0 +1,25 @@ +{ + "_id": "hoist-bundled-cycle-host", + "name": "hoist-bundled-cycle-host", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-bundled-cycle-host", + "version": "1.0.0", + "dependencies": { + "hoist-bundled-cycle-plugin": "1.0.0" + }, + "bundleDependencies": [ + "hoist-bundled-cycle-plugin" + ], + "_id": "hoist-bundled-cycle-host@1.0.0", + "dist": { + "integrity": "sha512-SFFfaFA5g9jnrCbqHjFhQ2jfd+GFqWQWrwJw3RboD8z/Pn091rfOCiVfyoYwCoByZuCUyCiHojzoXIXYFlSYXA==", + "shasum": "51b0aeed7e396f12316b46e763fa6cb1972ecb63", + "tarball": "http://localhost:4873/hoist-bundled-cycle-host/-/hoist-bundled-cycle-host-1.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/hoist-bundled-cycle-plugin-1.0.0.tgz b/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/hoist-bundled-cycle-plugin-1.0.0.tgz new file mode 100644 index 000000000000..722a07a00cc6 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/hoist-bundled-cycle-plugin-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/package.json b/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/package.json new file mode 100644 index 000000000000..d7ee81ad39b1 --- /dev/null +++ b/test/cli/install/registry/packages/hoist-bundled-cycle-plugin/package.json @@ -0,0 +1,22 @@ +{ + "_id": "hoist-bundled-cycle-plugin", + "name": "hoist-bundled-cycle-plugin", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-bundled-cycle-plugin", + "version": "1.0.0", + "peerDependencies": { + "hoist-bundled-cycle-host": "1.0.0" + }, + "_id": "hoist-bundled-cycle-plugin@1.0.0", + "dist": { + "integrity": "sha512-Ln5B/R9XfmUnYRRzx4BXwV8ncQ+xWCWkga1eiuX+HwgYsiCYZlsZfFmz0i+R86eYv/oKuQo5Cy8qxGr1vqp8BQ==", + "shasum": "5de566ac4690ed53cf7d3e5a6590c5bef47c6ef0", + "tarball": "http://localhost:4873/hoist-bundled-cycle-plugin/-/hoist-bundled-cycle-plugin-1.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-1.0.0.tgz b/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-1.0.0.tgz new file mode 100644 index 000000000000..4fbb67e76b27 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-2.0.0.tgz b/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-2.0.0.tgz new file mode 100644 index 000000000000..72032921d3a8 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-cycle-x/hoist-cycle-x-2.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-cycle-x/package.json b/test/cli/install/registry/packages/hoist-cycle-x/package.json new file mode 100644 index 000000000000..a710c107d8cf --- /dev/null +++ b/test/cli/install/registry/packages/hoist-cycle-x/package.json @@ -0,0 +1,35 @@ +{ + "_id": "hoist-cycle-x", + "name": "hoist-cycle-x", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-cycle-x", + "version": "1.0.0", + "dependencies": { + "hoist-cycle-y": "1.0.0" + }, + "_id": "hoist-cycle-x@1.0.0", + "dist": { + "integrity": "sha512-XxY8WzFQ5Bl1KRxuXCjsm+fI5ByOVVRT0c3oTzC8OFZ/ya+bXJwAgbx8ZNmLmfMJVZYPdJbKgLmtmQ7YumXOqw==", + "shasum": "8c8c12769463f25bb4bbeacd0d257aa78c23e7fd", + "tarball": "http://localhost:4873/hoist-cycle-x/-/hoist-cycle-x-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "hoist-cycle-x", + "version": "2.0.0", + "dependencies": { + "hoist-cycle-y": "2.0.0" + }, + "_id": "hoist-cycle-x@2.0.0", + "dist": { + "integrity": "sha512-ekGNL6MZSdsQ84pld6v1kcvZNSlR29owcDnPIamnrkuKAIj//DOnvKllLSKpW3ICKtXdvRac65hCt/v7hjQ2Iw==", + "shasum": "5fb9f771e881c4f89abc18f0dd75732b44b37380", + "tarball": "http://localhost:4873/hoist-cycle-x/-/hoist-cycle-x-2.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-1.0.0.tgz b/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-1.0.0.tgz new file mode 100644 index 000000000000..0c7b414d7b4e Binary files /dev/null and b/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-2.0.0.tgz b/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-2.0.0.tgz new file mode 100644 index 000000000000..752a17fcfc9e Binary files /dev/null and b/test/cli/install/registry/packages/hoist-cycle-y/hoist-cycle-y-2.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-cycle-y/package.json b/test/cli/install/registry/packages/hoist-cycle-y/package.json new file mode 100644 index 000000000000..108a59aaffc5 --- /dev/null +++ b/test/cli/install/registry/packages/hoist-cycle-y/package.json @@ -0,0 +1,35 @@ +{ + "_id": "hoist-cycle-y", + "name": "hoist-cycle-y", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-cycle-y", + "version": "1.0.0", + "dependencies": { + "hoist-cycle-x": "2.0.0" + }, + "_id": "hoist-cycle-y@1.0.0", + "dist": { + "integrity": "sha512-6wncHD+Md00Ib56bEGFfUOu92s/KY8359DUdCF+P2c3QJvmQ1tKf8P/vR+SRvM0Vk759pKe2EHlhUOgWPOFVWg==", + "shasum": "08a70c02642754a082346211eca18ed5b1041729", + "tarball": "http://localhost:4873/hoist-cycle-y/-/hoist-cycle-y-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "hoist-cycle-y", + "version": "2.0.0", + "dependencies": { + "hoist-cycle-x": "1.0.0" + }, + "_id": "hoist-cycle-y@2.0.0", + "dist": { + "integrity": "sha512-Q8oaz58QXz8CB6hh0kGLkTBsNvkooYaZN4cTVSweoefw1VcymRVMBoDmi/ku+4BZDYDKI5idwdQ6+oDqK09Wjw==", + "shasum": "8b01a417c5f2156e68fa9342966bcd50ee319b91", + "tarball": "http://localhost:4873/hoist-cycle-y/-/hoist-cycle-y-2.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-entry-1.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-entry-1.0.0.tgz new file mode 100644 index 000000000000..d9593a54544c Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/hoist-optional-peer-cycle-entry-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/package.json b/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/package.json new file mode 100644 index 000000000000..604e9a659ecc --- /dev/null +++ b/test/cli/install/registry/packages/hoist-optional-peer-cycle-entry/package.json @@ -0,0 +1,22 @@ +{ + "_id": "hoist-optional-peer-cycle-entry", + "name": "hoist-optional-peer-cycle-entry", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-optional-peer-cycle-entry", + "version": "1.0.0", + "dependencies": { + "hoist-optional-peer-cycle-x": "1.0.0" + }, + "_id": "hoist-optional-peer-cycle-entry@1.0.0", + "dist": { + "integrity": "sha512-FDfiSd8eCpyJ3n324Hk8KdEoNLkB/go8tQvT9txL1qt/ULRhuzYfCjT0StP5a3pYykhvjQFYA17VzF1ZMPsEqw==", + "shasum": "be52741a45e564b667febacfdcfcd2433ba474ef", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-entry/-/hoist-optional-peer-cycle-entry-1.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-1.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-1.0.0.tgz new file mode 100644 index 000000000000..9c948777879f Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-2.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-2.0.0.tgz new file mode 100644 index 000000000000..e194805325bc Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/hoist-optional-peer-cycle-x-2.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/package.json b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/package.json new file mode 100644 index 000000000000..d0860938ccb0 --- /dev/null +++ b/test/cli/install/registry/packages/hoist-optional-peer-cycle-x/package.json @@ -0,0 +1,37 @@ +{ + "_id": "hoist-optional-peer-cycle-x", + "name": "hoist-optional-peer-cycle-x", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-optional-peer-cycle-x", + "version": "1.0.0", + "dependencies": { + "hoist-optional-peer-cycle-y": "1.0.0", + "hoist-optional-peer-cycle-z": "1.0.0" + }, + "_id": "hoist-optional-peer-cycle-x@1.0.0", + "dist": { + "integrity": "sha512-KlprcCPzErpXf5tACt0XAxTEYf7fk+72MSCcBs7j6xnIKikqIK1h3LxEHkcNqT4dB4fAIhsfJK88T9OWhvCgPA==", + "shasum": "3f31db5e7f100aa1061349e8e4926067cb71b54f", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-x/-/hoist-optional-peer-cycle-x-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "hoist-optional-peer-cycle-x", + "version": "2.0.0", + "dependencies": { + "hoist-optional-peer-cycle-y": "2.0.0", + "hoist-optional-peer-cycle-z": "2.0.0" + }, + "_id": "hoist-optional-peer-cycle-x@2.0.0", + "dist": { + "integrity": "sha512-inK8dVXwSw1OCxE7iULIpMNMnpPYt3duWJtY2TnizQ1nBopIeDbYOESvN7iWZ63qzH3x8xkpBz7Aw0Lnyerqag==", + "shasum": "174af505b598ac64b79d11045e01ba1bf8ef5dd8", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-x/-/hoist-optional-peer-cycle-x-2.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-1.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-1.0.0.tgz new file mode 100644 index 000000000000..7f46eef56823 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-2.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-2.0.0.tgz new file mode 100644 index 000000000000..d489f89c7b6e Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/hoist-optional-peer-cycle-y-2.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/package.json b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/package.json new file mode 100644 index 000000000000..86abf8665e9c --- /dev/null +++ b/test/cli/install/registry/packages/hoist-optional-peer-cycle-y/package.json @@ -0,0 +1,40 @@ +{ + "_id": "hoist-optional-peer-cycle-y", + "name": "hoist-optional-peer-cycle-y", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-optional-peer-cycle-y", + "version": "1.0.0", + "peerDependencies": { + "hoist-optional-peer-cycle-x": "2.0.0" + }, + "_id": "hoist-optional-peer-cycle-y@1.0.0", + "dist": { + "integrity": "sha512-K6r6RYiWvhsjN0UMpVoBXWRvE7tUUXGIiS1ZxMf+HW4dsgMNXB+hfWMoQ8DlwNHZuM1kTdSIkc5u4rDIrf1x0A==", + "shasum": "c450c4030181e5c778268eab31bffa98d595cfbf", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-y/-/hoist-optional-peer-cycle-y-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "hoist-optional-peer-cycle-y", + "version": "2.0.0", + "peerDependencies": { + "hoist-optional-peer-cycle-x": "1.0.0" + }, + "peerDependenciesMeta": { + "hoist-optional-peer-cycle-x": { + "optional": true + } + }, + "_id": "hoist-optional-peer-cycle-y@2.0.0", + "dist": { + "integrity": "sha512-ycp0N508WdyGz/ECodwmYUWevdQrSeCSKvjjUFL+oEkJyz0l8lKn7cHcD7Png+oQ5heJbJUkje91bIZKhyeLaA==", + "shasum": "d2befacfc7eb260db2369e59d2d3ce0755af5d23", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-y/-/hoist-optional-peer-cycle-y-2.0.0.tgz" + } + } + } +} \ No newline at end of file diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-1.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-1.0.0.tgz new file mode 100644 index 000000000000..292f583dca19 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-1.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-2.0.0.tgz b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-2.0.0.tgz new file mode 100644 index 000000000000..147d280f1957 Binary files /dev/null and b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/hoist-optional-peer-cycle-z-2.0.0.tgz differ diff --git a/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/package.json b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/package.json new file mode 100644 index 000000000000..afec4abf2a8a --- /dev/null +++ b/test/cli/install/registry/packages/hoist-optional-peer-cycle-z/package.json @@ -0,0 +1,32 @@ +{ + "_id": "hoist-optional-peer-cycle-z", + "name": "hoist-optional-peer-cycle-z", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "hoist-optional-peer-cycle-z", + "version": "1.0.0", + "dependencies": { + "hoist-optional-peer-cycle-y": "2.0.0" + }, + "_id": "hoist-optional-peer-cycle-z@1.0.0", + "dist": { + "integrity": "sha512-CVxdzowZDlVIXkbYwdTBboHzoZj37q6BMsbliVhhb1jUc9oD5ekzEW/X7x697DVKQbVGekQ5Sbq005ATD1kcRQ==", + "shasum": "cf47fc0ae10f5d732cc60124a6475e92c65eba35", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-z/-/hoist-optional-peer-cycle-z-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "hoist-optional-peer-cycle-z", + "version": "2.0.0", + "_id": "hoist-optional-peer-cycle-z@2.0.0", + "dist": { + "integrity": "sha512-uHhXNwS4Q2LxIJp2KcJ1LLs/5QOyggw6QQvoXqp5GM6JS1yO1tG4d7n+t4gZM+GyQT6mtP8Vtz0YZe9aaEwZHw==", + "shasum": "a875d069566a9b97a2cffa171dba2826934bfb2c", + "tarball": "http://localhost:4873/hoist-optional-peer-cycle-z/-/hoist-optional-peer-cycle-z-2.0.0.tgz" + } + } + } +} \ No newline at end of file