@yaonyan/jsr2npm
v0.1.16
Published
> **Bridge the gap**: Publish JSR packages to NPM with zero hassle
Readme
JSR to NPM Converter
Bridge the gap: Publish JSR packages to NPM with zero hassle
We can publish JSR packages with npm deps, but not vice versa, so jsr2npm fills the gap by converting JSR packages to NPM-compatible format.
✨ Key Features
- 🎯 Zero Configuration - Works out of the box, just specify package name and version
- 📦 Preserves JSR Exports - Keeps your original module structure intact
- 🔧 CLI Tools Support - Add executable commands with simple
binconfiguration - 🚀 Smart Bundling - Bundles JSR/Deno code while keeping NPM deps external
- 📝 Type Definitions - Automatically copies TypeScript declarations
- 🔄 CI/CD Ready - Easy GitHub Actions integration for automated publishing
- 💎 Clean & Simple - Minimal config, maximum clarity
Why jsr2npm?
JSR is great for publishing TypeScript/Deno packages, but the NPM ecosystem is still huge.
Many developers want to:
- Publish CLI tools that work with
npx - Make their JSR packages available on NPM
- Support both ecosystems without maintaining duplicate code
- Write pure TypeScript with Deno, publish to NPM
jsr2npm automates this entire process while preserving your package structure and metadata.
How to Use
Create a
jsr2npm.config.jsonfile:Basic Package (Uses JSR exports as-is)
{ "packages": [ { "name": "@scope/package", "version": "latest", "packageJson": { "name": "@myorg/package", "description": "Package description" } } ] }CLI Tool (Adds bin command)
{ "packages": [ { "name": "@scope/cli-tool", "version": "latest", "bin": { "your-command": "src/bin.ts" }, "packageJson": { "name": "@myorg/cli-tool", "description": "Your CLI tool description" } } ] }Configuration:
name(required): JSR package nameversion(required): JSR package versionbin(optional): CLI commands to add- Key: command name (e.g., "mycli")
- Value: source file path (e.g., "src/bin.ts")
- Bundles to
bin/{command}.mjsautomatically - JSR exports are preserved completely
packageJson(optional): Override package.json fields
Available
packageJsonoverrides:name: NPM package name (recommended, e.g., "@myorg/cli-tool")version: Override the package versiondescription: Override package descriptionauthor: Override author (string or object with name/email/url)license: Override licensehomepage: Override homepage URLrepository: Override repository (string or object with type/url)keywords: Override keywords arrayscripts: Merge additional scripts
Run the script:
deno run --allow-all cli.tsOr use npx:
npx -y @yaonyan/jsr2npm@latest
How It Works
graph TD
A[Read jsr2npm.config.json] --> B[Create Workspace Folder]
B --> C[Download JSR Package<br/>npm install @jsr/...]
C --> D[Bundle Code with esbuild]
D --> E[Analyze Dependencies]
E --> F{Dependency Type?}
F -->|JSR/Deno Code| G[Include in Bundle]
F -->|NPM Package| H[Mark as External]
F -->|Node.js Built-in| H
G --> I[Generate package.json]
H --> I
I --> J[Set External NPM Deps<br/>with Correct Versions]
J --> K[Preserve JSR Exports]
K --> L[Copy Types & Files]
L --> M[dist/ Ready to Publish]
style E fill:#ff9,stroke:#333,stroke-width:2px
style F fill:#f9f,stroke:#333,stroke-width:3px
style J fill:#9ff,stroke:#333,stroke-width:2px
style M fill:#9f9,stroke:#333,stroke-width:2pxThe script automates these steps:
- Create Workspace - Creates
__scope__package_version/folder for organization - Download JSR Package - Uses
npm installto fetch the package from JSR registry - Bundle with esbuild - Processes the code and intelligently handles dependencies
- Analyze Dependencies - Core feature: Separates different types of
dependencies:
- JSR/Deno code: Bundled into the output
- NPM packages: Marked as external dependencies
- Node.js built-ins: Marked as external
- Generate package.json - Creates NPM metadata with:
- External NPM dependencies with correct versions (automatically detected)
- Preserved JSR
exportsfield - Type definitions paths
binfield for CLI commands (if configured)
- Copy Files - Includes TypeScript declarations, README, and LICENSE
- Output - Ready-to-publish NPM package in
dist/folder
Requirements
- Node.js (which includes
npx)
Usage
Local Development
Run the conversion locally:
deno run -A cli.tsOr use npx to run the latest version:
npx -y @yaonyan/jsr2npm@latestThe converted packages will be in __<scope>__<package>_<version>/dist/
directories.
Publishing to npm
After conversion, you can publish manually:
# For a single package
cd __scope__package_version/dist
npm publish --access public
# For multiple packages (skip if already published)
for dir in __*_*/dist; do
cd "$dir"
NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "$NAME@$VERSION" version 2>/dev/null; then
echo "Skipping $NAME@$VERSION (already published)"
else
npm publish --access public
fi
cd ../..
done---
## Setting up Automated CI/CD (For Package Maintainers)
If you maintain a JSR package with CLI tools and want to automatically publish npm versions, follow these steps:
### Step 1: Add jsr2npm Config to Your Repository
Create `jsr2npm.config.json` in your JSR package repository root:
```json
{
"packages": [
{
"name": "@your-scope/your-package",
"version": "0.1.0",
"bin": {
"your-command": "src/cli.ts"
},
"packageJson": {
"name": "@npm-org/package-name",
"description": "Your package description"
}
}
]
}Step 2: Create GitHub Workflow
Create .github/workflows/publish-npm.yml:
name: Publish CLI to NPM
on:
workflow_dispatch:
push:
tags:
- "v*"
jobs:
convert-and-publish:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "latest"
registry-url: "https://registry.npmjs.org"
- name: Run JSR to NPM conversion
run: npx -y @yaonyan/jsr2npm@latest
- name: Publish to npm
run: |
for dir in __*_*/dist; do
cd "$dir"
NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "$NAME@$VERSION" version 2>/dev/null; then
echo "Skipping $NAME@$VERSION (already published)"
else
npm publish --access public --provenance
fi
cd ../..
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: npm-package
path: __*_*/dist/
retention-days: 7Step 3: Add NPM Token
- Go to npmjs.com → Account Settings → Access Tokens
- Create a new Automation token
- Add to your GitHub repository: Settings → Secrets → Actions → New repository
secret
- Name:
NPM_TOKEN - Value: Your npm token
- Name:
Step 4: Trigger Publishing
Before publishing, update the version in jsr2npm.config.json to match your
release.
Then trigger the workflow:
Option A: Manual trigger
- Go to Actions tab → "Publish CLI to NPM" → Run workflow
Option B: Tag and push
# Update version in jsr2npm.config.json first!
git add jsr2npm.config.json
git commit -m "Release v1.0.0"
git tag v1.0.0
git push origin main --tagsDone! Your package will be published to npm.
Example Output
After conversion, you'll have a structure like:
__scope__package_1.0.0/
├── node_modules/ (JSR package and dependencies)
└── dist/ (Ready to publish)
├── package.json (Generated for npm, preserves JSR exports)
├── bin/ (CLI tools, if configured)
│ └── command.mjs (Bundled executable)
├── types/ (TypeScript declarations)
│ └── mod.d.ts
├── README.md (Copied from source)
└── LICENSE (Copied from source)