Recursively pack source code directories into clean ZIP archives — fast, smart, and ignore-aware.
Features
- Smart ignoring — respects
.packsrcignore,.gitignore,.dockerignore,.npmignoreand built-in defaults - Force-include support — use
.packsrcincludeto override ignore rules for specific files - Streaming ZIP — never loads entire files into memory; scales to large repos
- Deterministic output — files are sorted for reproducible archives
- Polished CLI — progress spinners, colored output, human-readable stats
- Zip-slip safe — all paths are validated before archiving
- Zero config — sensible defaults out of the box
Installation
# npm (global) npm install -g pack-src # pnpm pnpm add -g pack-src # yarn yarn global add pack-src # npx (no install) npx pack-src .
Usage
# Pack current directory pack-src # Pack a specific directory pack-src ./my-project # Specify output file pack-src ./my-project -o snapshot.zip # With timestamp in filename pack-src ./my-project --timestamp # → my-project-2026-05-21.zip # Dry run — preview files without creating archive pack-src ./my-project --dry-run # List files only pack-src ./my-project --list-files # Print compression statistics pack-src ./my-project --stats # Set compression level (0 = store, 9 = maximum) pack-src ./my-project -c 9 # Include .env files (excluded by default) pack-src ./my-project --include-env # Include .git directory (excluded by default) pack-src ./my-project --include-git # Disable .gitignore pack-src ./my-project --no-gitignore # Disable all built-in exclusions pack-src ./my-project --no-default-ignore # Overwrite existing output pack-src ./my-project --overwrite # Quiet mode (CI-friendly) pack-src ./my-project -q
CLI Reference
Usage: pack-src [options] [source]
Arguments:
source Source directory to pack (default: ".")
Options:
-o, --output <file> Output ZIP file path
-c, --compression <0-9> Compression level (default: 6)
--dry-run Preview files without creating archive
--verbose Verbose output
--include-env Include .env and secret files
--include-git Include .git directory in archive
--no-gitignore Do not use .gitignore files
--no-default-ignore Do not apply built-in exclusions
--stats Print compression statistics
--list-files List files that would be included
--timestamp Append date (YYYY-MM-DD) to output filename
--overwrite Overwrite existing output file
-q, --quiet Suppress all output except errors
-v, --version Show version
-h, --help Show help
Ignore Behavior
Ignore rules are loaded in this priority order:
.packsrcignore— project-specific overrides.gitignore— standard git ignores.dockerignore— Docker ignores.npmignore— npm ignores- Built-in defaults — see below
Built-in Default Exclusions
node_modules dist build coverage
.git .next .nuxt .cache
.turbo .parcel-cache .svelte-kit .angular
.vercel .output .yarn .pnpm-store
__pycache__ .pytest_cache .mypy_cache .ruff_cache
.venv venv env target
bin obj .gradle .idea
.vscode .DS_Store Thumbs.db
Env File Exclusion
These patterns are excluded by default to prevent accidental secret leakage:
.env .env.* *.pem *.key *.p12 id_rsa id_ed25519 *.secret
Use --include-env to explicitly include them.
Nested Ignore Files
pack-src loads ignore files from every subdirectory it traverses, so nested .gitignore rules are respected correctly — just like git itself.
Negation Support
Negation patterns (e.g., !important.log) work as expected within any ignore file.
Force-Include with .packsrcinclude
Sometimes you need to include specific files that would otherwise be ignored. Create a .packsrcinclude file with patterns to force-include:
# Include specific build artifacts
dist/bundle.min.js
dist/styles.css
# Include all .wasm files in build directory
build/**/*.wasm
How it works:
- Files matching
.packsrcincludepatterns bypass all ignore rules - Works with glob patterns (powered by the same engine as
.gitignore) - Useful for including specific generated files, build artifacts, or vendored dependencies
- Can be placed in any subdirectory (rules apply to that directory and below)
Example use case:
# Your .gitignore excludes dist/ # But you want to include the final bundle echo "dist/bundle.min.js" > .packsrcinclude pack-src . # → dist/bundle.min.js is now included despite dist/ being ignored
Output Naming
| Scenario | Output |
|---|---|
pack-src ./my-project |
my-project.zip |
pack-src ./my-project --timestamp |
my-project-2026-05-21.zip |
pack-src ./my-project -o out/snap.zip |
out/snap.zip |
Accidental overwrites are prevented by default. Use --overwrite to replace existing files.
Benchmarks
Tested on a MacBook Pro M2 with a 1,000-file TypeScript project:
| Tool | Time | Memory |
|---|---|---|
pack-src |
~180ms | ~18MB |
zip -r (naive) |
~240ms | ~24MB |
tar czf |
~160ms | ~15MB |
| naive recursive fs | ~410ms | ~85MB |
pack-src achieves near-native performance by:
- Using
fast-globfor concurrent directory traversal - Streaming files directly into the archive without buffering
- Limiting concurrency to avoid I/O saturation
Run the benchmarks yourself after building:
pnpm build pnpm benchmark
Programmatic API
import { runPack, collectFiles, IgnoreEngine } from 'pack-src'; // Pack a directory await runPack({ source: './my-project', output: 'snapshot.zip', compression: 6, stats: true, }); // Use the ignore engine directly const engine = new IgnoreEngine({ root: './my-project', gitignore: true, defaultIgnore: true, includeEnv: false, includeGit: false, verbose: false, }); await engine.loadDirectory('./my-project'); // Collect files const files = await collectFiles('./my-project', engine, false); console.log(files.map((f) => f.archivePath));
Development
git clone https://github.com/muhammadmuzzammil1998/pack-src.git cd pack-src pnpm install pnpm build pnpm test
See CONTRIBUTING.md for full contribution guide.
Release
Releases are managed with Changesets.
# Create a changeset pnpm changeset # Bump versions pnpm version # Publish to npm pnpm release
The release.yml workflow publishes automatically when a release PR is merged to main.













