Skill: openlark/archiver (version 1.0.0)

--- name: archiver description: Use the Archiver library for streaming archive packaging in Node.js. Supports creating ZIP/TAR archives, appending content from streams, strings, buffers, file paths, directories, and glob patterns, as well as registering custom formats. --- # Archiver — Streaming Archive Packaging A Node.js streaming archive library that supports ZIP and TAR, capable of appending content from multiple data sources. ## Use Cases Use when users need to compress and package, create zip/tar archives, package directories, or programmatically generate compressed files. ## Trigger Words Compress, package, archive, zip, tar, archive, archiver. ## Installation ```bash npm install archiver ``` ## Quick Start: ZIP Packaging ```js const fs = require('fs'); const archiver = require('archiver'); const output = fs.createWriteStream('output.zip'); const archive = archiver('zip', { zlib: { level: 9 } }); output.on('close', () => { console.log(`${archive.pointer()} bytes written`); }); archive.on('error', (err) => { throw err; }); archive.pipe(output); // Append — multiple data sources archive.append(fs.createReadStream('file.txt'), { name: 'file.txt' }); // Stream archive.append('string content', { name: 'readme.txt' }); // String archive.append(Buffer.from('data'), { name: 'data.bin' }); // Buffer archive.file('local-file.txt', { name: 'renamed.txt' }); // Local file archive.directory('src/', 'src'); // Directory → subdirectory in archive archive.directory('dist/', false); // Directory contents → archive root archive.glob('*.js', { cwd: __dirname }); // Glob match archive.finalize(); ``` ## Data Source Overview | Method | Data Source | name Parameter | |--------|-------------|----------------| | `archive.append(stream, { name })` | ReadStream | Required | | `archive.append(string, { name })` | String | Required | | `archive.append(buffer, { name })` | Buffer | Required | | `archive.file(path, { name })` | File path | Optional, can rename | | `archive.directory(path, dest)` | Directory | `false` = contents to root; string = subdirectory name | | `archive.glob(pattern, { cwd })` | Glob match | Auto-uses matched filenames | ## TAR Packaging ```js const archive = archiver('tar', { gzip: true, gzipOptions: { level: 6 } }); ``` TAR-specific options: | Option | Description | Default | |--------|-------------|---------| | `gzip` | Enable gzip compression | `false` | | `gzipOptions.level` | Compression level 0-9 | 6 | ## Events ```js archive.on('warning', (err) => { if (err.code === 'ENOENT') console.warn('File not found:', err); else throw err; }); archive.on('error', (err) => { throw err; }); // Events on the piped destination stream (from Node.js Stream API) output.on('close', () => { /* File descriptor closed */ }); output.on('end', () => { /* Data drained */ }); output.on('finish', () => { /* All data written */ }); ``` - **`warning`** — Non-fatal errors (e.g., file not found, stat failure, etc.); `ENOENT` can be ignored, others should be thrown - **`error`** — Fatal errors; must be handled - **`close`** (output) — Emitted after the file descriptor is closed; `archive.pointer()` can be used to get the total byte count at this point - **`end`** (output) — Data drained; emitted regardless of the data source - **`progress`** — Progress tracking, see below ## Progress Tracking ```js archive.on('progress', (progress) => { console.log(`${progress.entries.processed} / ${progress.entries.total} entries`); console.log(`${progress.fs.processedBytes} / ${progress.fs.totalBytes} bytes`); }); ``` `progress` object structure: ``` { entries: { total: number, processed: number }, fs: { totalBytes: number, processedBytes: number } } ``` ## Common Patterns ### Pattern 1: Responsive Archiving — HTTP Streaming Output ```js app.get('/download', (req, res) => { res.attachment('archive.zip'); const archive = archiver('zip', { zlib: { level: 1 } }); // Low compression = faster archive.on('error', (err) => { res.status(500).end(); }); archive.pipe(res); archive.directory('user-files/', false); archive.finalize(); }); ``` ### Pattern 2: Conditional Append — On-Demand Packaging ```js const archive = archiver('zip'); // Dynamically append based on conditions if (includeSource) { archive.directory('src/', 'source'); } if (includeDocs) { archive.glob('docs/**/*.md', { cwd: __dirname }); } archive.finalize(); ``` ### Pattern 3: In-Memory Archiving — No File Output ```js const { Writable } = require('stream'); const chunks = []; const memoryStream = new Writable({ write(chunk, enc, cb) { chunks.push(chunk); cb(); } }); const archive = archiver('zip'); archive.pipe(memoryStream); archive.append('hello', { name: 'hello.txt' }); archive.finalize(); memoryStream.on('finish', () => { const buffer = Buffer.concat(chunks); console.log(`Archive in memory: ${buffer.length} bytes`); // Can be used for uploading, sending, etc. }); ``` ### Pattern 4: Batch Packaging — Multiple Archives in Series ```js async function createBatchedArchives(fileGroups, outputDir) { for (const [i, files] of fileGroups.entries()) { await new Promise((resolve, reject) => { const output = fs.createWriteStream(`${outputDir}/batch-${i}.zip`); const archive = archiver('zip'); archive.on('error', reject); output.on('close', resolve); archive.pipe(output); files.forEach(f => archive.file(f, { name: path.basename(f) })); archive.finalize(); }); console.log(`Batch ${i} complete`); } } ``` ## Custom Format Registration ```js archiver.registerFormat('myformat', module); const archive = archiver('myformat'); ``` Check if a format is registered: ```js if (archiver.isRegisteredFormat('zip')) { // ZIP is available } ``` ## Symlink Handling ```js archive.symlink('target', { name: 'link-name' }); ``` ## Common Options Reference — `archiver(format, options)` ```js const archive = archiver('zip', { zlib: { level: 9 }, // Compression level 0-9 comment: 'my comment', // ZIP comment forceLocalTime: true, // Use local time instead of UTC forceZip64: false, // Whether to force Zip64 namePrependSlash: false, // Prepend / to filenames statConcurrency: 4 // Stat concurrency }); ``` For the full API reference, see [references/api-reference.md](references/api-reference.md).

path: references/api-reference.md
content: # Archiver API Reference

## Constructor

```js
const archive = archiver(format, options)
```

- `format` — `'zip'` | `'tar'`
- `options` — Format-specific options (see below)

---

## Methods

### archive.append(data, options)

Append content to the archive.

- `data` — `ReadableStream` | `Buffer` | `string`
- `options.name` — Filename within the archive (required)
- `options.prefix` — Path prefix
- `options.date` — File date (Date | string), defaults to current time
- `options.mode` — File permissions (e.g., 0o644)
- `options.store` — `true` to store without compression (ZIP only), `false` to compress

**Additional options (when data is a ReadStream):**
- `options.stats` — fs.Stats object (avoids an extra stat call)
- `options.sourceMetadata` — Custom metadata

### archive.file(filepath, options)

Append a file from the file system.

- `filepath` — File path
- `options.name` — Filename within the archive; defaults to `path.basename(filepath)` if not provided
- `options.prefix`, `options.date`, `options.mode`, `options.store` — Same as append

### archive.directory(dirpath, destpath, options)

Append an entire directory.

- `dirpath` — Local directory path
- `destpath` — Path within the archive. `false` places contents at the archive root; a string becomes the subdirectory name
- `options` — Same as file()

### archive.glob(pattern, options)

Match and append files via a glob pattern.

- `pattern` — Glob pattern
- `options.cwd` — Base directory for searching (required)
- `options.root` — Root path prefix (used for relative paths)
- `options.dot` — Include files starting with `.`
- `options.nodir` — Exclude directories
- `options.ignore` — Glob pattern(s) to exclude
- `options.expand` — `true` to enable brace expansion (e.g., `{a,b}`)
- `options.nonull` — Do not throw when no matches are found

```js
// Ignore node_modules and .git
archive.glob('**/*', {
  cwd: 'project/',
  ignore: ['node_modules/**', '.git/**']
});
```

### archive.symlink(target, options)

Add a symbolic link (only supported by certain formats).

- `target` — Link target
- `options.name` — Filename within the archive
- `options.mode` — Link permissions

### archive.finalize()

Finalize the archive build. No further content can be appended after this call. Events (close/end/finish) must be registered before this call.

### archive.pointer()

Returns the current number of bytes written (ZIP: before compression, TAR: file content only).

```js
archive.on('progress', () => {
  console.log(archive.pointer());
});
```

### archive.pipe(destination)

Pipe archive data to a destination writable stream.

---

## Events

### archive.on('progress', callback)

```js
archive.on('progress', (progress) => {
  // progress = {
  //   entries: { total: number, processed: number },
  //   fs: { totalBytes: number, processedBytes: number }
  // }
});
```

### archive.on('warning', callback)

- `err.code === 'ENOENT'` — File/directory does not exist; can be ignored
- Other — Should be treated as an error

### archive.on('error', callback)

Fatal errors; must be handled.

### archive.on('entry', callback)

Emitted when each entry is added to the archive (useful for logging/tracking).

```js
archive.on('entry', (entry) => {
  console.log(`Added: ${entry.name} (${entry.size} bytes)`);
});
```

---

## ZIP Options

```js
archiver('zip', {
  zlib: { level: 9 },       // Compression level 0-9 (0=store, 1=fastest, 9=smallest)
  comment: 'comment',       // Archive comment
  forceLocalTime: true,     // Use local time
  forceZip64: false,        // Force Zip64 format
  namePrependSlash: false,  // Prepend filenames with /
  store: false,             // Disable compression globally
  statConcurrency: 4        // Number of parallel stat calls
})
```

## TAR Options

```js
archiver('tar', {
  gzip: true,
  gzipOptions: { level: 6 },  // gzip compression level
  statConcurrency: 4
})
```

---

## Static Methods

### archiver.registerFormat(format, module)

Register a custom archive format.

```js
archiver.registerFormat('zip-encrypted', require('archiver-zip-encrypted'));
```

### archiver.isRegisteredFormat(format)

```js
archiver.isRegisteredFormat('zip') // → true
archiver.isRegisteredFormat('rar') // → false
```

---

## Common Errors

| Error Code | Meaning | Handling |
|------------|---------|----------|
| `ENOENT` | File/directory does not exist | Can be ignored (warning event) |
| `EACCES` | Insufficient permissions | Should abort |
| `ENOTDIR` | Path is not a directory | Check the dirpath |
| `QUEUEDONE` | Content appended after `finalize()` | Ensure finalize is the last step |

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)