ts-node-pack
v0.2.1
Published
Pack a TypeScript package into a Node-compatible npm tarball without modifying the source tree
Readme
ts-node-pack
Pack a TypeScript package into a Node-compatible npm tarball — without modifying the source tree, without bundling, and without changing module resolution semantics.
Given a TypeScript package whose sources use .ts files and .ts in relative import specifiers, ts-node-pack produces a .tgz whose contents are plain .js + .d.ts with correct .js specifiers, ready to npm install into any Node ESM project.
Why
TypeScript 5.7 introduced rewriteRelativeImportExtensions, which lets you author import './foo.ts' and have tsc emit import './foo.js' in the compiled .js. But:
tscdoes not rewrite.tsspecifiers inside emitted.d.tsfiles.- Your
package.json(main,module,exports,types) still points at.ts. - You probably don't want the
.tssources in the published tarball at all.
ts-node-pack wraps tsc + npm pack and fills in exactly those gaps.
Install
npm install --save-dev ts-node-packRequires Node ≥ 20 and TypeScript ≥ 5.7 available in the package being packed (resolved via npx tsc).
Usage
ts-node-pack <packageDir> [--tsconfig <path>] [--stage-to <dir>] [--skip-pack] [--force] [--verbose]| Flag | Description |
| ------------------- | -------------------------------------------------------------------------------------------- |
| --tsconfig <path> | tsconfig to extend. Defaults to tsconfig.build.json if present, otherwise tsconfig.json. |
| --stage-to <dir> | Stage into <dir> instead of an auto-created temp dir. Caller owns its lifecycle. |
| --skip-pack | Skip the final npm pack step. Requires --stage-to. |
| --force | With --stage-to, clear <dir> if it already has contents. |
| -v, --verbose | Log each pipeline phase to stderr. |
| -h, --help | Show help. |
The resulting <name>-<version>.tgz is written to the current working directory (unless --skip-pack is set).
Example
cd my-project
ts-node-pack ./packages/core --verbose
npm install ./my-core-1.2.3.tgz--stage-to and --skip-pack
By default ts-node-pack stages into an auto-created mkdtemp() directory, runs npm pack against it, copies the resulting .tgz to the current working directory, and removes the temp dir.
Pass --stage-to <dir> when you want to keep the staged contents — for example, to let another tool pack from that directory instead (lerna publish --contents <dir>, an alternate tarball builder, etc.). <dir> must either not exist, be empty, or be opted-in for clearing via --force.
Combine with --skip-pack to stop after staging and never run npm pack at all. --skip-pack is only valid with --stage-to (otherwise the staged contents would have no accessible location).
| Invocation | Behavior |
| ------------------------------------------------- | ------------------------------------------------------------------- |
| ts-node-pack <pkg> | Stage to temp dir, pack, copy .tgz to CWD, delete temp dir. |
| ts-node-pack <pkg> --stage-to <dir> | Stage to <dir>, pack, copy .tgz to CWD, leave <dir> in place. |
| ts-node-pack <pkg> --stage-to <dir> --skip-pack | Stage to <dir>, skip pack, leave <dir> in place. |
| ts-node-pack <pkg> --skip-pack | Error: skipPack requires stageTo. |
Pipeline
- Resolve package — read
package.json, pick tsconfig. - Stage — use
--stage-to <dir>if given, otherwise createmkdtemp()/package/. A small separatemkdtemp()work dir always holds auxiliary files (e.g. the derived tsconfig) so they never land in the packed contents. - Derived tsconfig — write
tsconfig.emit.jsoninside the work dir thatextendsthe chosen tsconfig (by absolute path) and forcesoutDir,declaration,rewriteRelativeImportExtensions: true,noEmit: false. If the base tsconfig enablessourceMap,inlineSourceMap, ordeclarationMap,inlineSources: trueis also set so debuggers get full source-level fidelity without any.tsfiles in the tarball. - Emit — run
tsc -pagainst the derived config. - Rewrite
.d.ts— for each emitted.d.ts, rewrite./foo.ts→./foo.jsinimport/export from/ dynamicimport()specifiers. Non-relative specifiers are left alone. - Rewrite
package.json— rewrite.ts→.js(and →.d.tsundertypesconditions) inmain,module,types,typings,bin,exports, and thefilesarray. StripdevDependenciesandscripts. - Copy assets —
README*,LICENSE*,CHANGELOG*,NOTICE*. Source.tsfiles are never copied. - Validate — fail if any
.tsspecifier remains in emitted.js/.d.ts/package.json, or if a referenced entry point does not exist. - Pack — unless
--skip-pack:npm packin the staging directory and move the tarball to the original CWD. - Cleanup — always remove the work dir. In default mode this also removes the staging dir (which is nested inside). In
--stage-tomode the staging dir is the caller's, and survives.
The source tree is never mutated.
Sourcemaps
If your tsconfig has sourceMap (or inlineSourceMap / declarationMap) enabled, ts-node-pack automatically forces inlineSources: true so each emitted .map embeds its source text via sourcesContent. This gives full source-level debugging and "Go to Definition" without shipping .ts files — sidestepping dual-resolution hazards where a downstream bundler or TS project might pick ./foo.ts over ./foo.js. Detection is a shallow read of the chosen tsconfig; if you inherit sourceMap from a base config via extends, set it explicitly at the leaf.
Non-goals
- No bundling.
- No AST transforms.
- No custom module resolution.
- No
npm publishlogic.
License
MIT
