Files
bun.sh/test/cli/install/lockfile-only.test.ts
robobun d9742eece7 Optimize --lockfile-only to skip tarball downloads (#21768)
## Summary

Optimizes the `--lockfile-only` flag to skip downloading **npm package
tarballs** since they're not needed for lockfile generation. This saves
bandwidth and improves performance for lockfile-only operations while
preserving accuracy for non-npm dependencies.

## Changes

- **Add `prefetch_resolved_tarballs` flag** to
`PackageManagerOptions.Do` struct (defaults to `true`)
- **Set flag to `false`** when `--lockfile-only` is used
- **Skip tarball downloads for npm packages only** when flag is
disabled:
- `getOrPutResolvedPackageWithFindResult` - Main npm package resolution
(uses `Task.Id.forNPMPackage`)
- `enqueuePackageForDownload` - NPM package downloads (uses
`bun.Semver.Version`)
- **Preserve tarball downloads for non-npm dependencies** to maintain
lockfile accuracy:
  - Remote tarball URLs (needed for lockfile generation)
  - GitHub dependencies (needed for lockfile generation)  
  - Generic tarball downloads (may be remote)
  - Patch-related downloads (needed for patch application)
- **Add comprehensive test** that verifies only package manifests are
fetched for npm packages with `--lockfile-only`

## Rationale

Only npm registry packages can safely skip tarball downloads during
lockfile generation because:

 **NPM packages**: Metadata is available from registry manifests,
tarball not needed for lockfile
 **Remote URLs**: Need tarball content to determine package metadata
and generate accurate lockfile
 **GitHub deps**: Need tarball content to extract package.json and
determine dependencies
 **Tarball URIs**: Need content to determine package structure and
dependencies

This selective approach maximizes bandwidth savings while ensuring
lockfile accuracy.

## Test Plan

-  New test in `test/cli/install/lockfile-only.test.ts` verifies only
npm manifest URLs are requested
-  Uses absolute package versions to ensure the npm resolution code
path is hit
-  Test output normalized to work with both debug and non-debug builds
-  All existing install/update tests still pass (including remote
dependency tests)

## Performance Impact

For `--lockfile-only` operations with npm packages, this eliminates
unnecessary tarball downloads, reducing:
- **Network bandwidth usage** (manifests only, not tarballs)
- **Installation time** (no tarball extraction/processing)
- **Cache storage requirements** (tarballs not cached)

The optimization only affects npm packages in `--lockfile-only` mode and
has zero impact on:
- Regular installs (npm packages still download tarballs)
- Remote dependencies (always download tarballs for accuracy)
- GitHub dependencies (always download tarballs for accuracy)

## Files Changed

- `src/install/PackageManager/PackageManagerOptions.zig` - Add flag and
configure for lockfile-only
- `src/install/PackageManager/PackageManagerEnqueue.zig` - Skip npm
tarball generation selectively
- `test/cli/install/lockfile-only.test.ts` - Test with dummy registry

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-08-12 22:19:10 -07:00

82 lines
2.0 KiB
TypeScript

import { spawn } from "bun";
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
import { access, writeFile } from "fs/promises";
import { bunExe, bunEnv as env } from "harness";
import { join } from "path";
import {
dummyAfterAll,
dummyAfterEach,
dummyBeforeAll,
dummyBeforeEach,
dummyRegistry,
package_dir,
requested,
root_url,
setHandler,
} from "./dummy.registry.js";
beforeAll(dummyBeforeAll);
afterAll(dummyAfterAll);
beforeEach(dummyBeforeEach);
afterEach(dummyAfterEach);
it.each(["bun.lockb", "bun.lock"])("should not download tarballs with --lockfile-only using %s", async lockfile => {
const isLockb = lockfile === "bun.lockb";
const urls: string[] = [];
const registry = { "0.0.1": { as: "0.0.1" }, latest: "0.0.1" };
setHandler(dummyRegistry(urls, registry));
await writeFile(
join(package_dir, "package.json"),
JSON.stringify({
name: "foo",
dependencies: {
baz: "0.0.1",
},
}),
);
const cmd = [bunExe(), "install", "--lockfile-only"];
if (!isLockb) {
// the default beforeEach disables --save-text-lockfile in the dummy registry, so we should restore
// default behaviour
await writeFile(
join(package_dir, "bunfig.toml"),
`
[install]
cache = false
registry = "${root_url}/"
`,
);
}
const { stdout, stderr, exited } = spawn({
cmd,
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
expect(await exited).toBe(0);
const err = await stderr.text();
expect(err).not.toContain("error:");
expect(err).toContain("Saved lockfile");
const out = await stdout.text();
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
expect.stringContaining(`Saved ${lockfile}`),
]);
expect(urls.sort()).toEqual([`${root_url}/baz`]);
expect(requested).toBe(1);
await access(join(package_dir, lockfile));
});