├── .npmrc ├── test ├── fixtures │ └── dummy.js ├── unit.test.ts ├── test_utils.ts ├── utils.test.ts └── commands.test.ts ├── .gitignore ├── src ├── index.ts ├── api.ts ├── download.ts ├── pkg_manager.ts ├── commands.ts ├── bin.ts └── utils.ts ├── tsconfig.esm.json ├── tsconfig.json ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── LICENSE.md ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | @jsr:registry=https://npm.jsr.io 2 | -------------------------------------------------------------------------------- /test/fixtures/dummy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log("it works"); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | dist-esm/ 5 | .download/ 6 | *.log 7 | *.tgz 8 | package/ -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 the JSR authors. MIT license. 2 | export { install, publish, remove } from "./commands.ts"; 3 | export type { InstallOptions, PublishOptions } from "./commands.ts"; 4 | export { JsrPackage, JsrPackageNameError } from "./utils.ts"; 5 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "target": "ESNext", 6 | "moduleResolution": "NodeNext", 7 | "outDir": "dist-esm/" 8 | }, 9 | "include": ["src"], 10 | // we don't want to compile the bin file in ESM mode 11 | "exclude": ["src/bin.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "rewriteRelativeImportExtensions": true, 6 | "erasableSyntaxOnly": true, 7 | "esModuleInterop": true, 8 | "outDir": "dist", 9 | "strict": true, 10 | "declaration": true, 11 | "sourceMap": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | # Setup .npmrc file to publish to npm 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: "22.x" 14 | registry-url: "https://registry.npmjs.org" 15 | - run: npm ci 16 | - run: npm publish 17 | env: 18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2024 the JSR authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/unit.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path"; 2 | import * as assert from "node:assert/strict"; 3 | import { describe, it } from "node:test"; 4 | import { runInTempDir } from "./test_utils.ts"; 5 | import { setupNpmRc } from "../src/commands.ts"; 6 | import { readTextFile, writeTextFile } from "../src/utils.ts"; 7 | 8 | describe("npmrc", () => { 9 | it("doesn't overwrite exising jsr mapping", async () => { 10 | await runInTempDir(async (dir) => { 11 | const npmrc = path.join(dir, ".npmrc"); 12 | await writeTextFile(npmrc, "@jsr:registry=https://example.com\n"); 13 | 14 | await setupNpmRc(dir); 15 | 16 | const content = await readTextFile(npmrc); 17 | assert.equal(content.trim(), "@jsr:registry=https://example.com"); 18 | }); 19 | }); 20 | 21 | it("adds newline in between entries if necessary", async () => { 22 | await runInTempDir(async (dir) => { 23 | const npmrc = path.join(dir, ".npmrc"); 24 | await writeTextFile(npmrc, "@foo:registry=https://example.com"); 25 | 26 | await setupNpmRc(dir); 27 | 28 | const content = await readTextFile(npmrc); 29 | assert.equal( 30 | content.trim(), 31 | [ 32 | "@foo:registry=https://example.com", 33 | "@jsr:registry=https://npm.jsr.io", 34 | ].join("\n"), 35 | ); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | lint-and-format: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: denoland/setup-deno@v1 15 | - run: deno fmt --check 16 | # TODO 17 | # - run: deno lint 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js lts/* 24 | uses: actions/setup-node@v3 25 | with: 26 | cache: "npm" 27 | node-version: "lts/*" 28 | 29 | - run: npm ci 30 | - run: node --run build 31 | 32 | test: 33 | strategy: 34 | fail-fast: false # prevent a failure in other versions run cancelling others 35 | matrix: 36 | platform: [ubuntu-latest, macos-latest, windows-latest] 37 | node-version: ["22.x", "24.x"] 38 | 39 | runs-on: ${{ matrix.platform }} 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Use Node.js ${{ matrix.node-version }} 44 | uses: actions/setup-node@v3 45 | with: 46 | cache: "npm" 47 | node-version: ${{ matrix.node-version }} 48 | - uses: pnpm/action-setup@v3 49 | with: 50 | version: 8 51 | - uses: oven-sh/setup-bun@v1 52 | - run: corepack enable yarn 53 | 54 | - run: npm ci 55 | - run: node --run test 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsr", 3 | "version": "0.13.5", 4 | "description": "jsr.io package manager for npm, yarn, pnpm and bun", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/jsr-io/jsr-npm.git" 8 | }, 9 | "bin": { 10 | "jsr": "dist/bin.js" 11 | }, 12 | "main": "dist/index.js", 13 | "exports": { 14 | ".": { 15 | "import": "./dist-esm/index.js", 16 | "require": "./dist/index.js" 17 | } 18 | }, 19 | "scripts": { 20 | "test": "node --no-warnings --test --experimental-strip-types test/**.test.ts", 21 | "test:coverage": "node --no-warnings --test --experimental-strip-types --experimental-test-coverage --test-coverage-exclude='**/.cache/**' --test-coverage-exclude='test/**' test/**.test.ts", 22 | "cli": "node --no-warnings --experimental-strip-types src/bin.ts", 23 | "build": "rimraf dist dist-esm && tsc && tsc -p tsconfig.esm.json", 24 | "prepack": "node --run build" 25 | }, 26 | "keywords": [ 27 | "install", 28 | "modules", 29 | "package manager", 30 | "package.json", 31 | "dependencies", 32 | "npm" 33 | ], 34 | "homepage": "https://jsr.io", 35 | "author": "", 36 | "license": "MIT", 37 | "files": [ 38 | "dist/", 39 | "dist-esm/" 40 | ], 41 | "devDependencies": { 42 | "@types/node": "^22.9.0", 43 | "rimraf": "^5.0.5", 44 | "typescript": "^5.8.3" 45 | }, 46 | "dependencies": { 47 | "node-stream-zip": "^1.15.0", 48 | "semiver": "^1.1.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { JsrPackage } from "./utils.ts"; 2 | 3 | export const JSR_URL = process.env.JSR_URL ?? "https://jsr.io"; 4 | 5 | export interface PackageMeta { 6 | scope: string; 7 | name: string; 8 | latest?: string; 9 | description?: string; 10 | versions: Record; 11 | } 12 | 13 | export async function getPackageMeta(pkg: JsrPackage): Promise { 14 | const url = `${JSR_URL}/@${pkg.scope}/${pkg.name}/meta.json`; 15 | const res = await fetch(url); 16 | if (!res.ok) { 17 | // cancel unconsumed body to avoid memory leak 18 | await res.body?.cancel(); 19 | throw new Error(`Received ${res.status} from ${url}`); 20 | } 21 | 22 | return (await res.json()) as PackageMeta; 23 | } 24 | 25 | export async function getLatestPackageVersion( 26 | pkg: JsrPackage, 27 | ): Promise { 28 | const info = await getPackageMeta(pkg); 29 | const { latest } = info; 30 | if (latest === undefined) { 31 | throw new Error(`Unable to find latest version of ${pkg}`); 32 | } 33 | return latest; 34 | } 35 | 36 | export interface NpmPackageInfo { 37 | name: string; 38 | description: string; 39 | "dist-tags": { latest: string }; 40 | versions: Record; 50 | }>; 51 | time: { 52 | created: string; 53 | modified: string; 54 | [key: string]: string; 55 | }; 56 | } 57 | 58 | export async function getNpmPackageInfo( 59 | pkg: JsrPackage, 60 | ): Promise { 61 | const tmpUrl = new URL(`${JSR_URL}/@jsr/${pkg.scope}__${pkg.name}`); 62 | const url = `${tmpUrl.protocol}//npm.${tmpUrl.host}${tmpUrl.pathname}`; 63 | const res = await fetch(url); 64 | if (!res.ok) { 65 | // Cancel unconsumed body to avoid memory leak 66 | await res.body?.cancel(); 67 | throw new Error(`Received ${res.status} from ${tmpUrl}`); 68 | } 69 | const json = await res.json(); 70 | return json as NpmPackageInfo; 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSR npm command line tool 2 | 3 | The JSR npm CLI integrates JSR (JavaScript Registry) packages with npm-based 4 | projects, facilitating the use of JSR packages in environments that 5 | traditionally rely on npm. Learn more about JSR at [jsr.io](https://jsr.io). 6 | 7 | ## Quick Start 8 | 9 | Add a JSR package to your project: 10 | 11 | ```sh 12 | npx jsr add @package/name # 'install' and 'i' are also supported 13 | ``` 14 | 15 | This command auto-updates your `package.json` and installs the package, 16 | automatically detecting and using your project's package manager. 17 | 18 | ## How It Works 19 | 20 | The CLI creates or updates a `.npmrc` file in your project with: 21 | 22 | ``` 23 | @jsr:registry=https://npm.jsr.io 24 | ``` 25 | 26 | This line redirects npm to fetch JSR packages from the JSR registry instead of 27 | the default npm registry. 28 | 29 | Packages are added to `package.json` with an alias, mapping the JSR package name 30 | to the npm registry URL hosted by JSR, like so: 31 | 32 | ```json 33 | { 34 | "dependencies": { 35 | "@luca/flag": "npm:@jsr/luca__flag@1" 36 | } 37 | } 38 | ``` 39 | 40 | This ensures that the package is fetched from JSR when you run npm install 41 | commands. 42 | 43 | ## Commands 44 | 45 | - `add`, `install`, `i`: Adds a JSR package to your project. 46 | - `remove`, `uninstall`, `r`: Remove a JSR package from your project. 47 | - `publish`: Publish `package.json` libraries to JSR. 48 | - `run