diff --git a/docs/cli/pm.md b/docs/cli/pm.md
index 577ab383cd..5d0a29d1ef 100644
--- a/docs/cli/pm.md
+++ b/docs/cli/pm.md
@@ -8,15 +8,70 @@ To create a tarball of the current workspace:
$ bun pm pack
```
-Options for the `pack` command:
+This command creates a `.tgz` file containing all files that would be published to npm, following the same rules as `npm pack`.
-- `--dry-run`: Perform all tasks except writing the tarball to disk.
-- `--destination`: Specify the directory where the tarball will be saved.
-- `--filename`: Specify an exact file name for the tarball to be saved at.
+## Examples
+
+Basic usage:
+
+```bash
+$ bun pm pack
+# Creates my-package-1.0.0.tgz in current directory
+```
+
+Quiet mode for scripting:
+
+```bash
+$ TARBALL=$(bun pm pack --quiet)
+$ echo "Created: $TARBALL"
+# Output: Created: my-package-1.0.0.tgz
+```
+
+Custom destination:
+
+```bash
+$ bun pm pack --destination ./dist
+# Saves tarball in ./dist/ directory
+```
+
+## Options
+
+- `--dry-run`: Perform all tasks except writing the tarball to disk. Shows what would be included.
+- `--destination
`: Specify the directory where the tarball will be saved.
+- `--filename `: Specify an exact file name for the tarball to be saved at.
- `--ignore-scripts`: Skip running pre/postpack and prepare scripts.
-- `--gzip-level`: Set a custom compression level for gzip, ranging from 0 to 9 (default is 9).
+- `--gzip-level <0-9>`: Set a custom compression level for gzip, ranging from 0 to 9 (default is 9).
+- `--quiet`: Only output the tarball filename, suppressing verbose output. Ideal for scripts and automation.
-> Note `--filename` and `--destination` cannot be used at the same time
+> **Note:** `--filename` and `--destination` cannot be used at the same time.
+
+## Output Modes
+
+**Default output:**
+
+```bash
+$ bun pm pack
+bun pack v1.2.19
+
+packed 131B package.json
+packed 40B index.js
+
+my-package-1.0.0.tgz
+
+Total files: 2
+Shasum: f2451d6eb1e818f500a791d9aace80b394258a90
+Unpacked size: 171B
+Packed size: 249B
+```
+
+**Quiet output:**
+
+```bash
+$ bun pm pack --quiet
+my-package-1.0.0.tgz
+```
+
+The `--quiet` flag is particularly useful for automation workflows where you need to capture the generated tarball filename for further processing.
## bin
diff --git a/src/cli/pack_command.zig b/src/cli/pack_command.zig
index 35e427ee49..8945aee711 100644
--- a/src/cli/pack_command.zig
+++ b/src/cli/pack_command.zig
@@ -63,7 +63,7 @@ pub const PackCommand = struct {
maybe_integrity: ?[sha.SHA512.digest]u8,
log_level: LogLevel,
) void {
- if (log_level != .silent) {
+ if (log_level != .silent and log_level != .quiet) {
Output.prettyln("\nTotal files: {d}", .{stats.total_files});
if (maybe_shasum) |shasum| {
Output.prettyln("Shasum: {s}", .{std.fmt.bytesToHex(shasum, .lower)});
@@ -93,8 +93,10 @@ pub const PackCommand = struct {
};
pub fn execWithManager(ctx: Command.Context, manager: *PackageManager) !void {
- Output.prettyln("bun pack v" ++ Global.package_json_version_with_sha ++ "", .{});
- Output.flush();
+ if (manager.options.log_level != .silent and manager.options.log_level != .quiet) {
+ Output.prettyln("bun pack v" ++ Global.package_json_version_with_sha ++ "", .{});
+ Output.flush();
+ }
var lockfile: Lockfile = undefined;
const load_from_disk_result = lockfile.loadFromCwd(
@@ -2426,7 +2428,7 @@ pub const PackCommand = struct {
package_json_len: usize,
) void {
const root_dir = bun.FD.fromStdDir(root_dir_std);
- if (ctx.manager.options.log_level == .silent) return;
+ if (ctx.manager.options.log_level == .silent or ctx.manager.options.log_level == .quiet) return;
const packed_fmt = "packed {} {s}";
if (comptime is_dry_run) {
diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig
index 9d22dc4c1c..113c53d2f2 100644
--- a/src/cli/package_manager_command.zig
+++ b/src/cli/package_manager_command.zig
@@ -121,7 +121,8 @@ pub const PackageManagerCommand = struct {
\\ ├ --destination the directory the tarball will be saved in
\\ ├ --filename the name of the tarball
\\ ├ --ignore-scripts don't run pre/postpack and prepare scripts
- \\ └ --gzip-level specify a custom compression level for gzip (0-9, default is 9)
+ \\ ├ --gzip-level specify a custom compression level for gzip (0-9, default is 9)
+ \\ └ --quiet only output the tarball filename
\\ bun pm bin print the path to bin folder
\\ └ -g print the global path to bin folder
\\ bun pm ls list the dependency tree according to the current lockfile
diff --git a/src/install/PackageManager/CommandLineArguments.zig b/src/install/PackageManager/CommandLineArguments.zig
index a3251006ad..8e795ba3f4 100644
--- a/src/install/PackageManager/CommandLineArguments.zig
+++ b/src/install/PackageManager/CommandLineArguments.zig
@@ -32,6 +32,7 @@ const shared_params = [_]ParamType{
clap.parseParam("--cache-dir Store & load cached data from a specific directory path") catch unreachable,
clap.parseParam("--no-cache Ignore manifest cache entirely") catch unreachable,
clap.parseParam("--silent Don't log anything") catch unreachable,
+ clap.parseParam("--quiet Only show tarball name when packing") catch unreachable,
clap.parseParam("--verbose Excessively verbose logging") catch unreachable,
clap.parseParam("--no-progress Disable the progress bar") catch unreachable,
clap.parseParam("--no-summary Don't print a summary") catch unreachable,
@@ -168,6 +169,7 @@ dry_run: bool = false,
force: bool = false,
no_cache: bool = false,
silent: bool = false,
+quiet: bool = false,
verbose: bool = false,
no_progress: bool = false,
no_verify: bool = false,
@@ -672,6 +674,7 @@ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !Com
cli.no_verify = args.flag("--no-verify");
cli.no_cache = args.flag("--no-cache");
cli.silent = args.flag("--silent");
+ cli.quiet = args.flag("--quiet");
cli.verbose = args.flag("--verbose") or Output.is_verbose;
cli.ignore_scripts = args.flag("--ignore-scripts");
cli.trusted = args.flag("--trust");
diff --git a/src/install/PackageManager/PackageManagerOptions.zig b/src/install/PackageManager/PackageManagerOptions.zig
index 764d352a3a..919786e15f 100644
--- a/src/install/PackageManager/PackageManagerOptions.zig
+++ b/src/install/PackageManager/PackageManagerOptions.zig
@@ -101,6 +101,7 @@ pub const LogLevel = enum {
default,
verbose,
silent,
+ quiet,
default_no_progress,
verbose_no_progress,
@@ -511,6 +512,9 @@ pub fn load(
} else if (cli.silent) {
this.log_level = .silent;
PackageManager.verbose_install = false;
+ } else if (cli.quiet) {
+ this.log_level = .quiet;
+ PackageManager.verbose_install = false;
} else {
this.log_level = if (disable_progress_bar) LogLevel.default_no_progress else LogLevel.default;
PackageManager.verbose_install = false;
diff --git a/test/cli/install/bun-pack.test.ts b/test/cli/install/bun-pack.test.ts
index ec969e1c67..59b5044be9 100644
--- a/test/cli/install/bun-pack.test.ts
+++ b/test/cli/install/bun-pack.test.ts
@@ -394,6 +394,35 @@ describe("flags", () => {
expect(results).toEqual([true, true, false, true, false]);
});
+
+ test("--quiet", async () => {
+ await Promise.all([
+ write(
+ join(packageDir, "package.json"),
+ JSON.stringify({
+ name: "pack-quiet-test",
+ version: "1.1.1",
+ }),
+ ),
+ write(join(packageDir, "index.js"), "console.log('hello ./index.js')"),
+ ]);
+
+ const { out } = await pack(packageDir, bunEnv, "--quiet");
+
+ // Should not contain verbose output
+ expect(out).not.toContain("Total files:");
+ expect(out).not.toContain("Shasum:");
+ expect(out).not.toContain("Integrity:");
+ expect(out).not.toContain("Unpacked size:");
+ expect(out).not.toContain("Packed size:");
+ expect(out).not.toContain("bun pack v");
+
+ // Should only contain the tarball name
+ expect(out.trim()).toBe("pack-quiet-test-1.1.1.tgz");
+
+ // Should still create the tarball
+ expect(await exists(join(packageDir, "pack-quiet-test-1.1.1.tgz"))).toBeTrue();
+ });
});
test("shasum and integrity are consistent", async () => {