diff --git a/src/install/yarn.zig b/src/install/yarn.zig index febc70c413..41cc733aa5 100644 --- a/src/install/yarn.zig +++ b/src/install/yarn.zig @@ -475,81 +475,79 @@ const DependencyType = enum { optional, peer, }; -const processDeps = struct { - fn process( - deps: bun.StringHashMap(string), - dep_type: DependencyType, - yarn_lock_: *YarnLock, - string_buf_: *Semver.String.Buf, - deps_buf: []Dependency, - res_buf: []Install.PackageID, - log: *logger.Log, - manager: *Install.PackageManager, - yarn_entry_to_package_id: []const Install.PackageID, - ) ![]Install.PackageID { - var deps_it = deps.iterator(); - var count: usize = 0; - var dep_spec_name_stack = std.heap.stackFallback(1024, bun.default_allocator); - const temp_allocator = dep_spec_name_stack.get(); +fn processDeps( + deps: bun.StringHashMap(string), + dep_type: DependencyType, + yarn_lock_: *YarnLock, + string_buf_: *Semver.String.Buf, + deps_buf: []Dependency, + res_buf: []Install.PackageID, + log: *logger.Log, + manager: *Install.PackageManager, + yarn_entry_to_package_id: []const Install.PackageID, +) ![]Install.PackageID { + var deps_it = deps.iterator(); + var count: usize = 0; + var dep_spec_name_stack = std.heap.stackFallback(1024, bun.default_allocator); + const temp_allocator = dep_spec_name_stack.get(); - while (deps_it.next()) |dep| { - const dep_name = dep.key_ptr.*; - const dep_version = dep.value_ptr.*; - const dep_spec = try std.fmt.allocPrint( - temp_allocator, - "{s}@{s}", - .{ dep_name, dep_version }, - ); - defer temp_allocator.free(dep_spec); + while (deps_it.next()) |dep| { + const dep_name = dep.key_ptr.*; + const dep_version = dep.value_ptr.*; + const dep_spec = try std.fmt.allocPrint( + temp_allocator, + "{s}@{s}", + .{ dep_name, dep_version }, + ); + defer temp_allocator.free(dep_spec); - if (yarn_lock_.findEntryBySpec(dep_spec)) |dep_entry| { - const dep_name_hash = stringHash(dep_name); - const dep_name_str = try string_buf_.appendWithHash(dep_name, dep_name_hash); + if (yarn_lock_.findEntryBySpec(dep_spec)) |dep_entry| { + const dep_name_hash = stringHash(dep_name); + const dep_name_str = try string_buf_.appendWithHash(dep_name, dep_name_hash); - const parsed_version = if (YarnLock.Entry.isNpmAlias(dep_version)) blk: { - const alias_info = YarnLock.Entry.parseNpmAlias(dep_version); - break :blk alias_info.version; - } else dep_version; + const parsed_version = if (YarnLock.Entry.isNpmAlias(dep_version)) blk: { + const alias_info = YarnLock.Entry.parseNpmAlias(dep_version); + break :blk alias_info.version; + } else dep_version; - deps_buf[count] = Dependency{ - .name = dep_name_str, - .name_hash = dep_name_hash, - .version = Dependency.parse( - yarn_lock_.allocator, - dep_name_str, - dep_name_hash, - parsed_version, - &Semver.SlicedString.init(parsed_version, parsed_version), - log, - manager, - ) orelse Dependency.Version{}, - .behavior = .{ - .prod = dep_type == .production, - .optional = dep_type == .optional, - .dev = dep_type == .development, - .peer = dep_type == .peer, - .workspace = dep_entry.workspace, - }, - }; - var found_package_id: ?Install.PackageID = null; - outer: for (yarn_lock_.entries.items, 0..) |entry_, yarn_idx| { - for (entry_.specs) |entry_spec| { - if (strings.eql(entry_spec, dep_spec)) { - found_package_id = yarn_entry_to_package_id[yarn_idx]; - break :outer; - } + deps_buf[count] = Dependency{ + .name = dep_name_str, + .name_hash = dep_name_hash, + .version = Dependency.parse( + yarn_lock_.allocator, + dep_name_str, + dep_name_hash, + parsed_version, + &Semver.SlicedString.init(parsed_version, parsed_version), + log, + manager, + ) orelse Dependency.Version{}, + .behavior = .{ + .prod = dep_type == .production, + .optional = dep_type == .optional, + .dev = dep_type == .development, + .peer = dep_type == .peer, + .workspace = dep_entry.workspace, + }, + }; + var found_package_id: ?Install.PackageID = null; + outer: for (yarn_lock_.entries.items, 0..) |entry_, yarn_idx| { + for (entry_.specs) |entry_spec| { + if (strings.eql(entry_spec, dep_spec)) { + found_package_id = yarn_entry_to_package_id[yarn_idx]; + break :outer; } } + } - if (found_package_id) |pkg_id| { - res_buf[count] = pkg_id; - count += 1; - } + if (found_package_id) |pkg_id| { + res_buf[count] = pkg_id; + count += 1; } } - return res_buf[0..count]; } -}.process; + return res_buf[0..count]; +} pub fn migrateYarnLockfile( this: *Lockfile, @@ -956,9 +954,8 @@ pub fn migrateYarnLockfile( }); } - const version = entry.version; - const sliced_version = Semver.SlicedString.init(version, version); - const result = Semver.Version.parse(sliced_version); + const version = try string_buf.append(entry.version); + const result = Semver.Version.parse(version.sliced(this.buffers.string_bytes.items)); if (!result.valid) { break :blk Resolution{}; } @@ -1033,6 +1030,7 @@ pub fn migrateYarnLockfile( if (found_idx) |idx| { const name_hash = stringHash(dep.name); const dep_name_string = try string_buf.appendWithHash(dep.name, name_hash); + const version_string = try string_buf.append(dep.version); dependencies_buf[actual_root_dep_count] = Dependency{ .name = dep_name_string, @@ -1041,8 +1039,8 @@ pub fn migrateYarnLockfile( allocator, dep_name_string, name_hash, - dep.version, - &Semver.SlicedString.init(dep.version, dep.version), + version_string.slice(this.buffers.string_bytes.items), + &version_string.sliced(this.buffers.string_bytes.items), log, manager, ) orelse Dependency.Version{}, diff --git a/test/cli/install/migration/yarn-lock-migration.test.ts b/test/cli/install/migration/yarn-lock-migration.test.ts index 738f8e7898..54329d56ec 100644 --- a/test/cli/install/migration/yarn-lock-migration.test.ts +++ b/test/cli/install/migration/yarn-lock-migration.test.ts @@ -51,6 +51,180 @@ is-number@^7.0.0: expect(bunLockContent).toMatchSnapshot("simple-yarn-migration"); }); + test("yarn.lock with packages containing long build tags", async () => { + const tempDir = tempDirWithFiles("yarn-migration-build-tags", { + "package.json": JSON.stringify( + { + name: "build-tags-test", + version: "1.0.0", + dependencies: { + "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/preset-modules": "0.1.6-no-external-plugins", + }, + }, + null, + 2, + ), + "yarn.lock": `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/types@^7.4.4": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#61c5b592274a82bb7addc5073ee1d989799e75e4" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@prisma/engines-version@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81": + version "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz#5512069ca14c44af7f38e7c39d9a169480e63a33" + integrity sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +`, + }); + + // Run bun pm migrate + const migrateResult = await Bun.spawn({ + cmd: [bunExe(), "pm", "migrate", "-f"], + cwd: tempDir, + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + stdin: "ignore", + }); + + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(migrateResult.stdout).text(), + new Response(migrateResult.stderr).text(), + migrateResult.exited, + ]); + + expect(exitCode).toBe(0); + expect(fs.existsSync(join(tempDir, "bun.lock"))).toBe(true); + + const bunLockContent = fs.readFileSync(join(tempDir, "bun.lock"), "utf8"); + + // Verify that long build tags are preserved correctly + expect(bunLockContent).toContain("4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"); + expect(bunLockContent).toContain("7.21.0-placeholder-for-preset-env.2"); + expect(bunLockContent).toContain("0.1.6-no-external-plugins"); + + // Ensure no corrupted version strings + expect(bunLockContent).not.toContain("monoreporeact"); + expect(bunLockContent).not.toContain("@types/react"); + expect(bunLockContent).not.toContain("�"); + + // Install should work after migration + const installResult = await Bun.spawn({ + cmd: [bunExe(), "install"], + cwd: tempDir, + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + stdin: "ignore", + }); + + const installExitCode = await installResult.exited; + expect(installExitCode).toBe(0); + }); + + test("yarn.lock with extremely long build tags (regression test)", async () => { + const tempDir = tempDirWithFiles("yarn-migration-extreme-build-tags", { + "package.json": JSON.stringify( + { + name: "extreme-build-tags-test", + version: "1.0.0", + dependencies: { + "test-package": + "1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lambda.mu.nu.xi.omicron.pi.rho.sigma.tau.upsilon.phi.chi.psi.omega.0123456789abcdef", + }, + }, + null, + 2, + ), + "yarn.lock": `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +test-package@1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lambda.mu.nu.xi.omicron.pi.rho.sigma.tau.upsilon.phi.chi.psi.omega.0123456789abcdef: + version "1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lambda.mu.nu.xi.omicron.pi.rho.sigma.tau.upsilon.phi.chi.psi.omega.0123456789abcdef" + resolved "https://registry.yarnpkg.com/test-package/-/test-package-1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lambda.mu.nu.xi.omicron.pi.rho.sigma.tau.upsilon.phi.chi.psi.omega.0123456789abcdef.tgz#abc123" + integrity sha512-xjEohWws8kKwCqz1IbvMrLynEfS7pFdO0CwjbPvzkt+rpL6tpnAKHS0N8d6diEBSp2BAIkUENc6H6H9SEqf0uA== +`, + }); + + // Run bun pm migrate + const migrateResult = await Bun.spawn({ + cmd: [bunExe(), "pm", "migrate", "-f"], + cwd: tempDir, + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + stdin: "ignore", + }); + + const exitCode = await migrateResult.exited; + expect(exitCode).toBe(0); + + const bunLockPath = join(tempDir, "bun.lock"); + expect(fs.existsSync(bunLockPath)).toBe(true); + + const bunLockContent = fs.readFileSync(bunLockPath, "utf8"); + + // The entire long version string should be preserved + const expectedVersion = + "1.0.0-alpha.beta.gamma.delta.epsilon.zeta.eta.theta.iota.kappa.lambda.mu.nu.xi.omicron.pi.rho.sigma.tau.upsilon.phi.chi.psi.omega.0123456789abcdef"; + expect(bunLockContent).toContain(expectedVersion); + + // Should not contain any corruption artifacts + expect(bunLockContent).not.toContain("�"); + expect(bunLockContent).not.toContain("\0"); + expect(bunLockContent).not.toContain("undefined"); + expect(bunLockContent).not.toContain("null"); + }); + test("complex yarn.lock with multiple dependencies and versions", async () => { const tempDir = tempDirWithFiles("yarn-migration-complex", { "package.json": JSON.stringify(