feat: add `deno pack` command to create npm tarballs (#32139)
Add `deno pack` command that creates npm-compatible tarballs from
Deno/JSR projects. It transpiles TypeScript to JavaScript, rewrites
`jsr:`/`npm:` specifiers to bare npm names, generates `package.json`
with conditional exports and `.d.ts` type entries, and bundles
everything into a `.tgz` ready for `npm publish`.
Types are extracted via fast-check (same as `deno publish`). When
Deno API usage is detected, `@deno/shim-deno` is automatically
added as a dependency.
## Usage examples
```sh
# Basic: pack the current project
deno pack
# Override version without editing deno.json
deno pack --set-version 2.0.0
# Write tarball to a specific path
deno pack --output my-package.tgz
# Preview contents without creating the tarball
deno pack --dry-run
# Skip .d.ts generation (allow slow types)
deno pack --allow-slow-types
# Don't inject @deno/shim-deno polyfill
deno pack --no-deno-shim
# Pack with uncommitted changes
deno pack --allow-dirty
# Exclude test files
deno pack --ignore=tests/
# Publish the tarball to npm
npm publish scope-package-1.0.0.tgz
```
## What it does
Given a `deno.json` like:
```jsonc
{
"name": "@scope/my-lib",
"version": "1.0.0",
"exports": "./mod.ts"
}
```
Running `deno pack` produces `scope-my-lib-1.0.0.tgz` containing:
- `package.json` with `name`, `version`, `type: "module"`,
conditional `exports` (types/import/default), and extracted
`dependencies`
- Transpiled `.js` files (with inline source maps by default)
- Generated `.d.ts` declaration files (via fast-check)
- README and LICENSE files (auto-included if present)
Specifier rewrites:
- `jsr:@std/path` -> `@jsr/std__path`
- `npm:express@4` -> `express`
- `./utils.ts` -> `./utils.js`
- `node:fs` -> unchanged
## How `deno pack` differs from `npm pack`
`deno pack` is not an `npm pack` equivalent -- it is a build tool
that converts a Deno/JSR project into an npm-publishable package,
closer to `tsc` + `npm pack` combined.
Notable behavioral differences:
- **File selection is graph-based**: only files reachable from
`exports` end up in the tarball, not a file listing. Non-JS assets
(data files, WASM) are not included unless imported.
- **No `.npmignore`/`.gitignore` support**: uses `--ignore` flag and
hard-coded exclusions (`.git`, `node_modules`, `.env*`).
- **No lifecycle scripts**: `prepublishOnly`/`prepare` hooks are not
executed.
- **No `bundledDependencies`/`optionalDependencies`**: the generated
`package.json` only contains `dependencies`.
- **JSR deps use `@jsr/` scope**: e.g. `jsr:@std/path` becomes
`@jsr/std__path`, which requires consumers to have the JSR npm
registry configured or use `npx jsr add`.
## Implementation details
- New modules under `cli/tools/pack/`: `mod.rs`, `extensions.rs`,
`unfurl.rs`, `package_json.rs`, `npm_tarball.rs`
- Shared unfurl utilities extracted to `cli/tools/unfurl_utils.rs`
(reused by `deno publish`)
- `check_if_git_repo_dirty` moved to `cli/util/git.rs` (shared
between pack and publish)
- Reproducible tarballs: `BTreeMap` for deterministic key ordering,
sorted tar entries, fixed tar headers (mode/mtime/uid/gid)
- Security: symlink checks on auto-included files, `.env`/`.git`
exclusion, path traversal protection on tarball filenames
The changeset is large but most of it is test coverage in
`tests/specs/pack/`.
---------
Co-authored-by: Bartek IwaĆczuk <biwanczuk@gmail.com>
Co-authored-by: Leo Kettmeir <crowlkats@toaxl.com>
Co-authored-by: crowlbot <280062030+crowlbot@users.noreply.github.com>