mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Compare commits
5 Commits
claude/yam
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6d10743df | ||
|
|
1aa1cf621d | ||
|
|
3ad7d257d8 | ||
|
|
e538188efa | ||
|
|
0519821c31 |
@@ -471,6 +471,7 @@ src/cli/pm_trusted_command.zig
|
||||
src/cli/pm_version_command.zig
|
||||
src/cli/pm_view_command.zig
|
||||
src/cli/pm_why_command.zig
|
||||
src/cli/prune_command.zig
|
||||
src/cli/publish_command.zig
|
||||
src/cli/remove_command.zig
|
||||
src/cli/run_command.zig
|
||||
|
||||
176
docs/cli/prune.md
Normal file
176
docs/cli/prune.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
name: bun prune
|
||||
---
|
||||
|
||||
The `bun prune` command removes extraneous packages from your project's `node_modules` directory. Extraneous packages are those that are installed but not declared as dependencies in your `package.json` or referenced in your lockfile.
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
```
|
||||
|
||||
This command is useful for:
|
||||
|
||||
- Cleaning up packages that were manually installed but never added to `package.json`
|
||||
- Removing leftover packages after removing dependencies
|
||||
- Ensuring your `node_modules` directory only contains necessary packages
|
||||
- Reducing disk space usage by removing unused packages
|
||||
|
||||
## How it works
|
||||
|
||||
`bun prune` analyzes your project's lockfile (`bun.lockb` or `bun.lock`) to determine which packages should be present in `node_modules`, then removes any packages that aren't referenced.
|
||||
|
||||
The command:
|
||||
|
||||
1. Reads your project's lockfile to identify legitimate packages
|
||||
2. Scans the `node_modules` directory for installed packages
|
||||
3. Removes any packages not found in the lockfile
|
||||
4. Handles both regular packages (`package-name`) and scoped packages (`@scope/package-name`)
|
||||
5. Reports the number of packages removed
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Remove extraneous packages
|
||||
$ bun prune
|
||||
|
||||
# Show help
|
||||
$ bun prune --help
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic usage
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
bun prune v1.2.21
|
||||
|
||||
Removing lodash
|
||||
Removing @types/unused-package
|
||||
Removed 2 extraneous packages
|
||||
Pruned extraneous packages
|
||||
```
|
||||
|
||||
### No extraneous packages
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
bun prune v1.2.21
|
||||
|
||||
Pruned extraneous packages
|
||||
```
|
||||
|
||||
### Missing lockfile
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
bun prune v1.2.21
|
||||
|
||||
error: Lockfile not found
|
||||
```
|
||||
|
||||
## When to use `bun prune`
|
||||
|
||||
### After removing dependencies
|
||||
|
||||
When you remove a dependency with `bun remove`, the package is removed from `package.json` and the lockfile, but may still exist in `node_modules`. Use `bun prune` to clean it up:
|
||||
|
||||
```bash
|
||||
$ bun remove lodash
|
||||
$ bun prune # Remove lodash from node_modules if still present
|
||||
```
|
||||
|
||||
### After manual package installation
|
||||
|
||||
If you manually installed packages without adding them to `package.json`:
|
||||
|
||||
```bash
|
||||
$ cd node_modules && npm install some-package # Manual install
|
||||
$ bun prune # Will remove some-package since it's not in package.json
|
||||
```
|
||||
|
||||
### Before deployment
|
||||
|
||||
Clean up your dependencies before deploying to ensure only necessary packages are included:
|
||||
|
||||
```bash
|
||||
$ bun install
|
||||
$ bun prune
|
||||
$ bun run build
|
||||
```
|
||||
|
||||
### Disk space cleanup
|
||||
|
||||
Remove unused packages to free up disk space:
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
Removed 15 extraneous packages
|
||||
# Freed up several MB of disk space
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
### Missing package.json
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
error: No package.json was found for directory "/path/to/project"
|
||||
|
||||
Note: Run "bun init" to initialize a project
|
||||
```
|
||||
|
||||
### Missing lockfile
|
||||
|
||||
```bash
|
||||
$ bun prune
|
||||
error: Lockfile not found
|
||||
```
|
||||
|
||||
Run `bun install` first to generate a lockfile:
|
||||
|
||||
```bash
|
||||
$ bun install
|
||||
$ bun prune
|
||||
```
|
||||
|
||||
### No node_modules directory
|
||||
|
||||
If `node_modules` doesn't exist, `bun prune` succeeds without doing anything:
|
||||
|
||||
```bash
|
||||
$ rm -rf node_modules
|
||||
$ bun prune
|
||||
bun prune v1.2.21
|
||||
|
||||
Pruned extraneous packages
|
||||
```
|
||||
|
||||
## Comparison with other package managers
|
||||
|
||||
| Command | Behavior |
|
||||
| ------------ | --------------------------------------------------------------- |
|
||||
| `npm prune` | Removes extraneous packages not in `package.json` dependencies |
|
||||
| `pnpm prune` | Removes orphaned packages not referenced by any dependency tree |
|
||||
| `bun prune` | Removes packages not referenced in the lockfile |
|
||||
|
||||
`bun prune` is most similar to `npm prune` but uses Bun's lockfile as the source of truth rather than just `package.json`.
|
||||
|
||||
## Flags
|
||||
|
||||
Currently, `bun prune` doesn't support additional flags beyond `--help`. The command operates on the current working directory and uses the default log level for output.
|
||||
|
||||
## Technical details
|
||||
|
||||
- **Lockfile dependency**: Requires a valid lockfile (`bun.lockb` or `bun.lock`)
|
||||
- **Package detection**: Scans `node_modules` directory structure
|
||||
- **Scope handling**: Properly handles scoped packages under `@scope/` directories
|
||||
- **Safety**: Only removes packages not found in the lockfile
|
||||
- **Performance**: Efficiently processes large `node_modules` directories
|
||||
|
||||
## Related commands
|
||||
|
||||
- [`bun install`](/docs/cli/install) - Install dependencies and generate lockfile
|
||||
- [`bun remove`](/docs/cli/remove) - Remove dependencies from package.json
|
||||
- [`bun add`](/docs/cli/add) - Add dependencies to package.json
|
||||
- [`bun pm cache rm`](/docs/cli/pm#bun-pm-cache-rm) - Clear the global package cache
|
||||
26
src/cli.zig
26
src/cli.zig
@@ -91,6 +91,7 @@ pub const PackCommand = @import("./cli/pack_command.zig").PackCommand;
|
||||
pub const AuditCommand = @import("./cli/audit_command.zig").AuditCommand;
|
||||
pub const InitCommand = @import("./cli/init_command.zig").InitCommand;
|
||||
pub const WhyCommand = @import("./cli/why_command.zig").WhyCommand;
|
||||
pub const PruneCommand = @import("./cli/prune_command.zig").PruneCommand;
|
||||
|
||||
pub const Arguments = @import("./cli/Arguments.zig");
|
||||
|
||||
@@ -177,6 +178,7 @@ pub const HelpCommand = struct {
|
||||
\\ <b><blue>outdated<r> Display latest versions of outdated dependencies
|
||||
\\ <b><blue>link<r> <d>[\<package\>]<r> Register or link a local npm package
|
||||
\\ <b><blue>unlink<r> Unregister a local npm package
|
||||
\\ <b><blue>prune<r> Remove extraneous packages from node_modules
|
||||
\\ <b><blue>publish<r> Publish a package to the npm registry
|
||||
\\ <b><blue>patch <d>\<pkg\><r> Prepare a package for patching
|
||||
\\ <b><blue>pm <d>\<subcommand\><r> Additional package management utilities
|
||||
@@ -585,7 +587,7 @@ pub const Command = struct {
|
||||
RootCommandMatcher.case("login") => .ReservedCommand,
|
||||
RootCommandMatcher.case("logout") => .ReservedCommand,
|
||||
RootCommandMatcher.case("whoami") => .ReservedCommand,
|
||||
RootCommandMatcher.case("prune") => .ReservedCommand,
|
||||
RootCommandMatcher.case("prune") => .PruneCommand,
|
||||
RootCommandMatcher.case("list") => .ReservedCommand,
|
||||
RootCommandMatcher.case("why") => .WhyCommand,
|
||||
|
||||
@@ -752,6 +754,11 @@ pub const Command = struct {
|
||||
try WhyCommand.exec(ctx);
|
||||
return;
|
||||
},
|
||||
.PruneCommand => {
|
||||
const ctx = try Command.init(allocator, log, .PruneCommand);
|
||||
try PruneCommand.exec(ctx);
|
||||
return;
|
||||
},
|
||||
.BunxCommand => {
|
||||
const ctx = try Command.init(allocator, log, .BunxCommand);
|
||||
|
||||
@@ -925,6 +932,7 @@ pub const Command = struct {
|
||||
PublishCommand,
|
||||
AuditCommand,
|
||||
WhyCommand,
|
||||
PruneCommand,
|
||||
|
||||
/// Used by crash reports.
|
||||
///
|
||||
@@ -962,6 +970,7 @@ pub const Command = struct {
|
||||
.PublishCommand => 'k',
|
||||
.AuditCommand => 'A',
|
||||
.WhyCommand => 'W',
|
||||
.PruneCommand => 'X',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1274,6 +1283,21 @@ pub const Command = struct {
|
||||
Output.pretty(intro_text, .{});
|
||||
Output.flush();
|
||||
},
|
||||
.PruneCommand => {
|
||||
const intro_text =
|
||||
\\<b>Usage<r>: <b><green>bun prune<r> <cyan>[flags]<r>
|
||||
\\Remove extraneous packages from node_modules that are not declared as dependencies
|
||||
\\
|
||||
\\<b>Examples:<r>
|
||||
\\ <d>$<r> <b><green>bun prune<r>
|
||||
\\
|
||||
\\Full documentation is available at <magenta>https://bun.sh/docs/cli/prune<r>
|
||||
\\
|
||||
;
|
||||
|
||||
Output.pretty(intro_text, .{});
|
||||
Output.flush();
|
||||
},
|
||||
else => {
|
||||
HelpCommand.printWithReason(.explicit);
|
||||
},
|
||||
|
||||
174
src/cli/prune_command.zig
Normal file
174
src/cli/prune_command.zig
Normal file
@@ -0,0 +1,174 @@
|
||||
pub const PruneCommand = struct {
|
||||
pub fn exec(ctx: Command.Context) !void {
|
||||
const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .install);
|
||||
|
||||
var manager, const original_cwd = PackageManager.init(ctx, cli, .install) catch |err| {
|
||||
if (err == error.MissingPackageJSON) {
|
||||
var cwd_buf: bun.PathBuffer = undefined;
|
||||
if (bun.getcwd(&cwd_buf)) |cwd| {
|
||||
Output.errGeneric("No package.json was found for directory \"{s}\"", .{cwd});
|
||||
} else |_| {
|
||||
Output.errGeneric("No package.json was found", .{});
|
||||
}
|
||||
Output.note("Run \"bun init\" to initialize a project", .{});
|
||||
Global.exit(1);
|
||||
}
|
||||
return err;
|
||||
};
|
||||
defer ctx.allocator.free(original_cwd);
|
||||
|
||||
if (manager.options.shouldPrintCommandName()) {
|
||||
Output.prettyln("<r><b>bun prune <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{});
|
||||
Output.flush();
|
||||
}
|
||||
|
||||
// Load the lockfile to understand which packages should be kept
|
||||
const load_lockfile = manager.lockfile.loadFromCwd(manager, ctx.allocator, ctx.log, true);
|
||||
if (load_lockfile == .not_found) {
|
||||
if (manager.options.log_level != .silent) {
|
||||
Output.errGeneric("Lockfile not found", .{});
|
||||
}
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
if (load_lockfile == .err) {
|
||||
if (manager.options.log_level != .silent) {
|
||||
Output.errGeneric("Error loading lockfile: {s}", .{@errorName(load_lockfile.err.value)});
|
||||
}
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
const lockfile = load_lockfile.ok.lockfile;
|
||||
|
||||
try pruneNodeModules(ctx.allocator, manager, lockfile);
|
||||
|
||||
if (manager.options.log_level != .silent) {
|
||||
Output.prettyln("<r><green>Pruned extraneous packages<r>", .{});
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn pruneNodeModules(allocator: std.mem.Allocator, manager: *PackageManager, lockfile: *Lockfile) !void {
|
||||
// Get the current working directory
|
||||
var cwd_buf: bun.PathBuffer = undefined;
|
||||
const cwd = bun.getcwd(&cwd_buf) catch {
|
||||
Output.prettyErrorln("<r><red>error<r>: Could not get current working directory", .{});
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
// Construct node_modules path
|
||||
var node_modules_buf: bun.PathBuffer = undefined;
|
||||
const node_modules_path = try std.fmt.bufPrint(&node_modules_buf, "{s}/node_modules", .{cwd});
|
||||
|
||||
// Get the list of packages that should exist according to the lockfile
|
||||
var expected_packages = bun.StringHashMap(void).init(allocator);
|
||||
defer expected_packages.deinit();
|
||||
|
||||
// Add all packages that are actually used/installed
|
||||
// We'll iterate through the lockfile packages to find what should be installed
|
||||
const dependencies = lockfile.buffers.dependencies.items;
|
||||
const string_bytes = lockfile.buffers.string_bytes.items;
|
||||
const packages_slice = lockfile.packages.slice();
|
||||
const package_names = packages_slice.items(.name);
|
||||
|
||||
// Add all packages that are in the lockfile
|
||||
for (package_names) |package_name_string| {
|
||||
const package_name = package_name_string.slice(string_bytes);
|
||||
try expected_packages.put(package_name, {});
|
||||
}
|
||||
|
||||
// Also check for any hoisted dependencies
|
||||
for (lockfile.buffers.hoisted_dependencies.items) |hoisted_dep| {
|
||||
if (hoisted_dep < dependencies.len) {
|
||||
const dep = dependencies[hoisted_dep];
|
||||
const package_name = dep.name.slice(string_bytes);
|
||||
try expected_packages.put(package_name, {});
|
||||
}
|
||||
}
|
||||
|
||||
// Open node_modules directory
|
||||
var node_modules_dir = std.fs.openDirAbsolute(node_modules_path, .{ .iterate = true }) catch |err| switch (err) {
|
||||
error.FileNotFound => {
|
||||
// No node_modules directory, nothing to prune
|
||||
return;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
defer node_modules_dir.close();
|
||||
|
||||
var iterator = node_modules_dir.iterate();
|
||||
var pruned_count: u32 = 0;
|
||||
|
||||
while (try iterator.next()) |entry| {
|
||||
if (entry.kind != .directory) continue;
|
||||
|
||||
const dirname = entry.name;
|
||||
|
||||
// Skip .bin directory and other dot directories
|
||||
if (strings.hasPrefix(dirname, ".")) continue;
|
||||
|
||||
// Handle scoped packages (@scope/package)
|
||||
if (strings.hasPrefix(dirname, "@")) {
|
||||
// This is a scope directory, iterate through it
|
||||
var scope_dir = node_modules_dir.openDir(dirname, .{ .iterate = true }) catch continue;
|
||||
defer scope_dir.close();
|
||||
|
||||
var scope_iterator = scope_dir.iterate();
|
||||
while (try scope_iterator.next()) |scope_entry| {
|
||||
if (scope_entry.kind != .directory) continue;
|
||||
|
||||
const scoped_package_name = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dirname, scope_entry.name });
|
||||
defer allocator.free(scoped_package_name);
|
||||
|
||||
if (!expected_packages.contains(scoped_package_name)) {
|
||||
// This package should be removed
|
||||
if (manager.options.log_level.showProgress()) {
|
||||
Output.prettyln("<r><d>Removing<r> {s}", .{scoped_package_name});
|
||||
}
|
||||
|
||||
scope_dir.deleteTree(scope_entry.name) catch |err| {
|
||||
if (manager.options.log_level != .silent) {
|
||||
Output.err(err, "Failed to remove {s}", .{scoped_package_name});
|
||||
}
|
||||
continue;
|
||||
};
|
||||
pruned_count += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular package
|
||||
if (!expected_packages.contains(dirname)) {
|
||||
// This package should be removed
|
||||
if (manager.options.log_level.showProgress()) {
|
||||
Output.prettyln("<r><d>Removing<r> {s}", .{dirname});
|
||||
}
|
||||
|
||||
node_modules_dir.deleteTree(dirname) catch |err| {
|
||||
if (manager.options.log_level != .silent) {
|
||||
Output.err(err, "Failed to remove {s}", .{dirname});
|
||||
}
|
||||
continue;
|
||||
};
|
||||
pruned_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (manager.options.log_level != .silent and pruned_count > 0) {
|
||||
Output.prettyln("<r><green>Removed {d} extraneous package{s}<r>", .{ pruned_count, if (pruned_count == 1) "" else "s" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Lockfile = @import("../install/lockfile.zig");
|
||||
const fs = @import("../fs.zig");
|
||||
const std = @import("std");
|
||||
const Command = @import("../cli.zig").Command;
|
||||
|
||||
const Install = @import("../install/install.zig");
|
||||
const PackageManager = Install.PackageManager;
|
||||
|
||||
const bun = @import("bun");
|
||||
const Global = bun.Global;
|
||||
const Output = bun.Output;
|
||||
const strings = bun.strings;
|
||||
235
test/cli/prune.test.ts
Normal file
235
test/cli/prune.test.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
test("bun prune removes extraneous packages", async () => {
|
||||
const tempDir = tempDirWithFiles("prune-test", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-project",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// First, install dependencies to create a valid lockfile
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [, , exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Manually create an extraneous package in node_modules
|
||||
const nodeModulesPath = join(tempDir, "node_modules");
|
||||
const extraneousPackagePath = join(nodeModulesPath, "extraneous-package");
|
||||
mkdirSync(extraneousPackagePath, { recursive: true });
|
||||
writeFileSync(
|
||||
join(extraneousPackagePath, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "extraneous-package",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
);
|
||||
|
||||
// Create a scoped extraneous package
|
||||
const scopedPath = join(nodeModulesPath, "@scope");
|
||||
mkdirSync(scopedPath, { recursive: true });
|
||||
const scopedPackagePath = join(scopedPath, "extraneous");
|
||||
mkdirSync(scopedPackagePath, { recursive: true });
|
||||
writeFileSync(
|
||||
join(scopedPackagePath, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@scope/extraneous",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
);
|
||||
|
||||
// Run bun prune
|
||||
await using pruneProc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [pruneStdout, pruneStderr, pruneExitCode] = await Promise.all([
|
||||
pruneProc.stdout.text(),
|
||||
pruneProc.stderr.text(),
|
||||
pruneProc.exited,
|
||||
]);
|
||||
|
||||
expect(pruneExitCode).toBe(0);
|
||||
expect(pruneStdout).toMatch(/Removed \d+ extraneous package/);
|
||||
|
||||
// Verify the extraneous packages were removed
|
||||
const extraneousExists = await Bun.file(join(extraneousPackagePath, "package.json")).exists();
|
||||
const scopedExists = await Bun.file(join(scopedPackagePath, "package.json")).exists();
|
||||
|
||||
expect(extraneousExists).toBe(false);
|
||||
expect(scopedExists).toBe(false);
|
||||
|
||||
// Verify legitimate packages remain
|
||||
const reactExists = await Bun.file(join(nodeModulesPath, "react", "package.json")).exists();
|
||||
expect(reactExists).toBe(true);
|
||||
});
|
||||
|
||||
test("bun prune with no extraneous packages", async () => {
|
||||
const tempDir = tempDirWithFiles("prune-test-no-extra", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-project",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Install dependencies
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
await proc.exited;
|
||||
|
||||
// Run bun prune - should succeed with no changes
|
||||
await using pruneProc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [pruneStdout, pruneStderr, pruneExitCode] = await Promise.all([
|
||||
pruneProc.stdout.text(),
|
||||
pruneProc.stderr.text(),
|
||||
pruneProc.exited,
|
||||
]);
|
||||
|
||||
expect(pruneExitCode).toBe(0);
|
||||
expect(pruneStdout).toMatch(/Pruned extraneous packages/);
|
||||
});
|
||||
|
||||
test("bun prune without lockfile fails", async () => {
|
||||
const tempDir = tempDirWithFiles("prune-test-no-lockfile", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-project",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run bun prune without installing first (no lockfile)
|
||||
await using pruneProc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [pruneStdout, pruneStderr, pruneExitCode] = await Promise.all([
|
||||
pruneProc.stdout.text(),
|
||||
pruneProc.stderr.text(),
|
||||
pruneProc.exited,
|
||||
]);
|
||||
|
||||
expect(pruneExitCode).toBe(1);
|
||||
expect(pruneStderr).toMatch(/Lockfile not found/);
|
||||
});
|
||||
|
||||
test("bun prune without package.json fails", async () => {
|
||||
const tempDir = tempDirWithFiles("prune-test-no-package-json", {});
|
||||
|
||||
// Run bun prune without package.json
|
||||
await using pruneProc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [pruneStdout, pruneStderr, pruneExitCode] = await Promise.all([
|
||||
pruneProc.stdout.text(),
|
||||
pruneProc.stderr.text(),
|
||||
pruneProc.exited,
|
||||
]);
|
||||
|
||||
expect(pruneExitCode).toBe(1);
|
||||
expect(pruneStderr).toMatch(/No package\.json was found/);
|
||||
});
|
||||
|
||||
test("bun prune --help shows help text", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune", "--help"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toMatch(/Usage.*bun prune/);
|
||||
expect(stdout).toMatch(/Remove extraneous packages/);
|
||||
});
|
||||
|
||||
test("bun prune with no node_modules directory", async () => {
|
||||
const tempDir = tempDirWithFiles("prune-test-no-node-modules", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-project",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"react": "^18.0.0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Install first to create a valid lockfile
|
||||
await using installProc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
await installProc.exited;
|
||||
|
||||
// Remove node_modules directory after installing but keep lockfile
|
||||
rmSync(join(tempDir, "node_modules"), { recursive: true, force: true });
|
||||
|
||||
// Run bun prune
|
||||
await using pruneProc = Bun.spawn({
|
||||
cmd: [bunExe(), "prune"],
|
||||
cwd: tempDir,
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [pruneStdout, pruneStderr, pruneExitCode] = await Promise.all([
|
||||
pruneProc.stdout.text(),
|
||||
pruneProc.stderr.text(),
|
||||
pruneProc.exited,
|
||||
]);
|
||||
|
||||
// Should succeed even with no node_modules
|
||||
expect(pruneExitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user