Skip to content

folders:download --zip produces an empty archive (or hangs) since v4.10.0: this.zip is assigned after traversal starts #718

Description

@dgilman-perplexity

Description

Since v4.10.0, box folders:download <id> --zip writes a 0-byte .zip and puts the downloaded files loose next to it (or hangs), instead of producing an archive. v4.8.2 produces a valid archive for the same folder.

Root cause

#686 moved folders:download to archiver 8, which is ESM-only, so _setupZip() became async and now awaits a dynamic import('archiver') before assigning this.zip:

// src/commands/folders/download.js (v4.10.0)
outputFinalized = this._setupZip(path.join(destinationPath, fileName)); // not awaited
...
for await (let item of this._getItems(id, '')) {
    if (item.type === 'folder' && !this.zip) { ... mkdirp ... }
    else if (item.type === 'file') {
        if (this.zip) { this.zip.append(stream, { name: item.path }); }
        else { await saveFileToDisk(destinationPath, item, stream); }
    }
}
if (this.zip) { this.zip.finalize(); }
await outputFinalized;

run() starts traversing the folder without waiting for the import, so whenever the first folder listing comes back before the module has loaded, this.zip is still undefined: the items take the non-zip path (folders are created on disk, files are saved to disk), finalize() is skipped because this.zip is checked before the import resolves, and the write stream for the archive is never finalized. Depending on timing the process either exits with an empty .zip or waits forever on outputFinalized.

Simply awaiting _setupZip() is not a fix: it returns the promise that resolves when the output stream closes, so awaiting it before traversal deadlocks.

box files:zip is not affected (it uses the server-side zip download API).

Steps to reproduce

  1. Point the CLI at a local mock of the API (apiRootURL in ~/.box/settings.json) that serves one folder containing one file, or use a real small folder.
  2. box folders:download <folder-id> --zip --destination out -t <token>
  3. Inspect out/.

Observed with v4.10.0 on Node 20.20.1 (5/5 runs): folders-download-<id>-....zip is 0 bytes, and out/<folder>/<file> exists on disk. Same steps with v4.8.2: valid archive containing <folder>/<file>, no loose files. On Node 24 the import happens to win the race, so the bug can look intermittent across environments.

Versions

  • @box/cli 4.10.0 (archiver 8.0.0, box-node-sdk 4.14.0)
  • Node 20.20.1, macOS arm64 (on Node 24 the import happens to win the race and the command succeeds)

Fix

Load the archiver module before starting traversal, and keep _setupZip() synchronous so this.zip is assigned before the first item is processed. I have a PR with this change plus a regression test that asserts this.zip is set before _getItems() runs and that the archive contains the expected entries.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions