# Unpack bundles

> Operational guide for --unpack modes (auto vs strict), --raw extraction, multi-file entry+chunk inputs, directory scanning rules, and --force overwrite protection.

- Repository: pionxzh/wakaru
- GitHub: https://github.com/pionxzh/wakaru
- Human docs: https://grok-wiki.com/public/docs/pionxzh-wakaru-77a438a6cc6b
- Complete Markdown: https://grok-wiki.com/public/docs/pionxzh-wakaru-77a438a6cc6b/llms-full.txt

## Source Files

- `README.md`
- `crates/cli/src/main.rs`
- `crates/cli/src/discovery.rs`
- `crates/core/src/driver.rs`
- `docs/architecture.md`
- `crates/cli/src/output.rs`

---

---
title: Unpack bundles
description: Operational guide for --unpack modes (auto vs strict), --raw extraction, multi-file entry+chunk inputs, directory scanning rules, and --force overwrite protection.
---

Unpack mode splits bundled JavaScript into separate module files, then runs the decompile pipeline on each module in parallel. Use it when you have a webpack, esbuild, Browserify, or similar bundle — or a `dist/` directory full of entry and chunk files — and want readable source back on disk.

## Prerequisites

- An output directory via `-o` / `--output`. `--unpack` always requires it; Wakaru refuses to unpack to stdout.
- One or more inputs: a single bundle file, multiple explicit files (entry + chunks), a directory path, or stdin (`-` or piped input when no file is given).

<Steps>
<Step title="Choose an unpack mode">

| Mode | Flag | Behavior |
|------|------|----------|
| Auto (default) | `--unpack` or `--unpack=auto` | Structural bundle detection first; if no format matches, heuristic scope-hoisted splitting for Rollup/Vite/flat esbuild output |
| Strict | `--unpack=strict` | Structural detection only — webpack, Browserify, esbuild/Bun markers, SystemJS, AMD, webpack chunks. No heuristic fallback |

Auto mode is appropriate for most real-world bundles. Use strict when you want predictable, marker-based splitting only — for example, when heuristic scope splitting produces false positives.

</Step>

<Step title="Run unpack">

<CodeGroup>

```bash title="Full unpack (default)"
wakaru bundle.js --unpack -o out/
```

```bash title="Raw extraction only"
wakaru bundle.js --unpack --raw -o out/
```

```bash title="Strict structural detection"
wakaru bundle.js --unpack=strict -o out/
```

```bash title="Webpack entry + chunk files"
wakaru entry.js chunk.abc123.js --unpack -o out/
```

```bash title="Scan a dist directory"
wakaru dist/ --unpack -o out/
```

</CodeGroup>

</Step>

<Step title="Verify output">

On a TTY, Wakaru prints a summary to stderr:

```
scanned: 4 file(s), detected: 2 bundle/chunk file(s), skipped: 2 file(s)
detected: webpack5, webpack5_chunk
total: 47 module(s) in 1.23s
```

- **scanned / detected / skipped** — only shown when at least one directory input was scanned
- **detected** — bundle formats recognized across all inputs
- **total** — module files written under the output directory

The process exits non-zero if any module produced an error-level warning (for example, a parse failure during decompilation).

</Step>
</Steps>

## Unpack modes in detail

### Auto mode (`--unpack` / `--unpack=auto`)

Auto mode sets `heuristic_split: true` in the core driver. The unpacker tries structural detectors in order (webpack5, webpack4, webpack5 chunk, Browserify, SystemJS, esbuild/Bun). When none match:

1. **Single explicit file** — Wakaru attempts scope-hoisted splitting. If that yields more than one module, those modules are unpacked. Otherwise the file falls back to single-file decompilation and is written as `module.js`.
2. **Directory scan** — files that fail both structural and heuristic detection are **skipped**, not copied or decompiled.

At `--level aggressive`, auto mode also retries scope-hoist splitting **inside** modules already extracted by a structural detector (nested scope split). This can recover additional modules from large webpack output that still contains scope-hoisted regions.

### Strict mode (`--unpack=strict`)

Strict mode disables heuristic splitting entirely. Only files that match a structural bundle or chunk shape are unpacked. Scope-hoisted Rollup/Vite output without `__export` or `__commonJS` markers will not split in strict mode.

For explicit file inputs that are not bundles, strict mode still falls back to single-file decompilation (same as auto). Directory scans use the same detection gate but never apply the heuristic — plain `.js` files in `dist/` are skipped.

## Raw extraction (`--raw`)

`--raw` requires `--unpack`. It runs extraction and bundler-coupled normalization only — **not** the full decompile rule pipeline (~60 rewrite rules, cross-module fact collection, or Phase 2 late pass).

Use raw extraction when you want to:

- Inspect what the unpacker extracted before readability transforms
- Debug bundle detection or extraction boundaries
- Compare against reference fixtures at the raw layer

Raw output still includes webpack-specific normalization tied to extraction (factory param renaming, `require()` rewriting, runtime helper removal). Webpack ESM markers and export getters remain so a later full decompile pass can recover live exports.

```bash
wakaru bundle.js --unpack --raw -o raw/
wakaru entry.js chunk.js --unpack --raw -o raw/
```

`--formatter` still applies to raw output if passed. `--emit-source-map` is not populated in raw mode.

## Multi-file entry and chunk inputs

Pass multiple file paths to combine entry runtime and async chunks in one unpack operation:

```bash
wakaru main.bundle.js src_greet_js.bundle.js --unpack -o out/
```

When more than one input is provided, the core `unpack_files` (or `unpack_files_raw`) path:

1. Detects each file independently
2. Merges extracted modules into a single module set
3. Stabilizes filenames and rewrites unambiguous numeric webpack module IDs across physical files so entry→chunk references resolve
4. Runs the two-phase parallel decompile pipeline once over the combined set

Duplicate numeric webpack IDs across unrelated runtimes (common when scanning a whole `dist/` tree) are treated as ambiguous and are **not** rewritten globally — this prevents merging unrelated bundles.

Files that are not detected as bundles still participate as fallback modules (basename used as filename) when passed explicitly. This differs from directory scanning, which skips non-detected files entirely.

<ParamField body="--source-map" type="path">
Supported only with a **single** input file. Multi-file unpack rejects `--source-map` / `-m`.
</ParamField>

## Directory scanning

Directory inputs work **only** with `--unpack`. Passing a directory without `--unpack` errors with guidance to use `--unpack`.

```bash
wakaru dist/ --unpack -o out/
```

### What gets scanned

The CLI recursively walks the directory and collects candidates matching:

| Rule | Detail |
|------|--------|
| Extensions | `.js`, `.mjs`, `.cjs` (case-insensitive) |
| Skipped directories | Hidden directories (name starts with `.`) and `node_modules` |
| Skipped files | Hidden files (name starts with `.`) |
| Non-JS files | Ignored entirely (`.map`, `.ts`, `.txt`, etc.) |

Results are sorted by path string before processing.

### Detect-only filter

Each candidate is read and passed to `is_detected_unpack_input`. A file is included only when:

- Structural bundle detection succeeds (`try_unpack_bundle`), **or**
- Auto mode is on and scope-hoisted heuristic splitting would produce more than one module

Plain application source, runtime-like stubs without bundle markers, and non-bundle chunks are **skipped** — not copied, not decompiled. If every file in a directory input is skipped, Wakaru exits with:

```
no bundle or chunk files detected in directory input
```

### Scan statistics

When at least one directory was scanned, stderr reports:

```
scanned: N file(s), detected: M bundle/chunk file(s), skipped: K file(s)
```

`scanned` counts all `.js`/`.mjs`/`.cjs` candidates read; `detected` counts those kept; `skipped` counts those filtered out.

## Output directory and path safety

`-o` / `--output` must be a directory path. Wakaru creates it if missing.

Module filenames come from bundle contents and may contain traversal attempts. Before writing, the CLI:

1. Lexically validates each filename (rejects `../`, absolute paths, Windows drive prefixes)
2. Deduplicates collisions (`index.js` → `index_2.js`, etc.)
3. Canonicalizes parent directories and rejects symlink escapes outside the output root

Untrusted paths like `../node_modules/@wakaru/cli/bin/wakaru` are written **inside** the output directory as ordinary relative paths, never outside it.

## Overwrite protection (`--force`)

Wakaru refuses to clobber existing output unless `--force` is passed.

| Target | Without `--force` | With `--force` |
|--------|-------------------|----------------|
| Single output file (decompile mode) | Error if file exists | Overwrites |
| Output directory (empty or new) | Creates and writes | Creates and writes |
| Output directory (non-empty) | Error: `output directory … is not empty` | Writes into directory |
| Output path exists but is a file (unpack `-o`) | Error: `exists and is not a directory` | N/A |

When `--force` writes into an **existing non-empty** directory, Wakaru uses a write-if-changed fast path: identical files (same length and bytes) are not rewritten, which avoids touching timestamps on unchanged modules during re-runs.

```bash
wakaru dist/ --unpack -o out/              # fails if out/ has files
wakaru dist/ --unpack -o out/ --force      # updates changed modules only
```

`--force` is a global flag and also applies to `wakaru extract` and `wakaru debug trace` output paths.

## Combining with other flags

| Flag | With `--unpack` |
|------|-----------------|
| `--level minimal\|standard\|aggressive` | Controls rewrite aggressiveness per module; `aggressive` + auto enables nested scope split |
| `--dce` | Full dead-code sweep on each decompiled module (not raw) |
| `--formatter` | Final format pass on each output file |
| `--emit-source-map` | Writes `.map` alongside each module (full unpack only) |
| `--json` | Machine-readable JSON summary to stdout; human summary still on stderr unless `--json` suppresses styled warnings |
| `--diagnostics` | Post-transform checks; warnings in stderr or JSON |
| `--profile` | Chrome trace including parallel module work |

Example CI invocation:

```bash
wakaru dist/ --unpack --json --force -o out/ 2>stderr.json
```

See [JSON output and CI](/json-output-and-ci) for the stdout schema (`detected_formats`, `modules`, `warnings`, `total`, `failed`, `elapsed_ms`).

## Raw vs full unpack

```mermaid
flowchart TD
    A[Input file(s)] --> B{--raw?}
    B -->|no| C[Detect bundle format]
    B -->|yes| D[Detect + extract only]
    C --> E[Phase 1: parallel fact collection]
    E --> F[Phase 2: cross-module decompile]
    F --> G[Write modules to -o]
    D --> H[Bundler-coupled normalization]
    H --> G
```

Full unpack runs the complete two-phase pipeline described in [Decompile pipeline](/decompile-pipeline) and [Cross-module facts](/cross-module-facts). Raw unpack stops after extraction.

## Common failure modes

<AccordionGroup>
<Accordion title="--unpack requires -o/--output">

Unpack always needs an output directory. Stdout is not supported for multi-module output.

```bash
wakaru bundle.js --unpack        # error
wakaru bundle.js --unpack -o out/  # ok
```

</Accordion>

<Accordion title="Directory contains no detected bundles">

All candidates were read but none matched bundle/chunk shapes (strict) or heuristic scope split (auto). Add explicit file paths, switch to auto mode, or verify the input is actually bundled output.

</Accordion>

<Accordion title="Output directory is not empty">

Pass `--force` to write into an existing directory, or choose a new output path.

</Accordion>

<Accordion title="Single module.js for an expected bundle">

The input may be scope-hoisted ESM without structural markers. Try `--unpack` (auto) instead of strict, or `--level aggressive` for nested splitting inside webpack output. See [Bundle formats and unpacking](/bundle-formats-and-unpacking) for detection order and format markers.

</Accordion>

<Accordion title="errors in N module(s)">

One or more modules failed during decompilation (parse recovery, TDZ, etc.). Warnings name the failing module filename. See [Troubleshooting](/troubleshooting) for `UnpackWarningKind` codes.

</Accordion>
</AccordionGroup>

## Related pages

<Card href="/quickstart" title="Quickstart" icon="play">
First successful unpack runs and expected success signals.
</Card>

<Card href="/bundle-formats-and-unpacking" title="Bundle formats and unpacking" icon="package">
Detection order, format variants, and raw vs full semantics.
</Card>

<Card href="/webpack-bundle-recipe" title="Webpack bundle recipe" icon="book">
End-to-end webpack4/webpack5 entry + chunk workflow.
</Card>

<Card href="/cli-reference" title="CLI reference" icon="terminal">
Complete flag surface including unpack sub-options.
</Card>

<Card href="/troubleshooting" title="Troubleshooting" icon="alert-circle">
Overwrite protection, directory skip behavior, and warning kinds.
</Card>
