UNPKG

deno-npm-sync

Version:

A CLI tool to synchronize dependencies between Deno and NPM projects.

568 lines (450 loc) โ€ข 13.7 kB
# deno-npm-sync A CLI tool and library to automatically synchronize npm and JSR package versions from `package.json` to Deno's `deno.json` imports. This ensures your Deno projects always use the same package versions as defined in your package.json, eliminating version drift and manual updates. ## Features - ๐Ÿ”„ **Automatic Synchronization**: Syncs npm and JSR package versions from `package.json` to `deno.json` - ๐Ÿ“ฆ **Dual Registry Support**: Works with both npm (`npm:`) and JSR (`jsr:`) imports - ๐ŸŽฏ **Subpath Support**: Handles imports with subpaths (e.g., `npm:lodash@4.17.21/fp`, `jsr:@std/assert@1.0.0/equals`) - ๐Ÿ” **Smart Detection**: Only updates packages that have version mismatches - ๐Ÿ“‹ **PNPM Catalog Support**: Automatically resolves pnpm catalog references from `pnpm-workspace.yaml` - ๐ŸŽจ **Flexible Version Precision**: Four modes - auto-detect, major-only, major.minor, or full version - ๐Ÿ›ก๏ธ **Type-Safe**: Written in TypeScript with full type definitions - ๐Ÿงช **Well-Tested**: Comprehensive test coverage with 29+ test cases - ๐Ÿš€ **CLI & Library**: Use as a command-line tool or programmatically in your code ## Installation ### Global Installation (CLI) ```bash pnpm install -g deno-npm-sync # or npm install -g deno-npm-sync # or yarn global add deno-npm-sync ``` ### Local Installation (Library) ```bash pnpm add -D deno-npm-sync # or npm install --save-dev deno-npm-sync # or yarn add -D deno-npm-sync ``` ## CLI Usage ### Basic Usage ```bash # Sync from default locations (./package.json and ./deno.json) deno-npm-sync # Specify custom paths deno-npm-sync --deno ./functions/deno.json --package ./package.json # Silent mode (no output) deno-npm-sync --silent ``` ### CLI Options ```bash Options: -V, --version output the version number -d, --deno <path> Path to deno.json file (default: "./deno.json") -p, --package <path> Path to package.json file (default: "./package.json") -s, --silent Suppress console output (default: false) -v, --version-precision <mode> Version precision mode: 'auto' (preserve format), 'major' (x), 'minor' (x.y), or 'full' (x.y.z) (default: "auto") -h, --help display help for command ``` ### Examples ```bash # Sync with custom deno.json location deno-npm-sync --deno ./src/deno.json # Sync with custom package.json location deno-npm-sync --package ./custom-package.json # Sync both with custom paths deno-npm-sync -d ./functions/deno.json -p ./package.json # Run in silent mode (useful for CI/CD) deno-npm-sync --silent # Use 'auto' mode to preserve version format from deno.json (default) deno-npm-sync --version-precision auto # Use 'major' mode to always use major version only (e.g., npm:lodash@4) deno-npm-sync --version-precision major # Use 'minor' mode to always use major.minor version (e.g., npm:lodash@4.17) deno-npm-sync --version-precision minor # Use 'full' mode to always use full version (e.g., npm:lodash@4.17.21) deno-npm-sync --version-precision full # Combine options deno-npm-sync --deno ./functions/deno.json --silent --version-precision minor ``` ## Automation & Integration Add to your `package.json` scripts for automatic synchronization: ```json { "scripts": { "sync:deno": "deno-npm-sync", "sync:deno:major": "deno-npm-sync --version-precision major", "sync:deno:minor": "deno-npm-sync --version-precision minor", "sync:deno:full": "deno-npm-sync --version-precision full", "postinstall": "pnpm run sync:deno" } } ``` ### Usage Examples - **Post-install hook**: Automatically sync after `pnpm install` - **Pre-commit hook**: Ensure deno.json is up-to-date before commits - **CI/CD pipeline**: Add to your build process for consistency - **Development workflow**: Run manually or via npm scripts ## Version Precision Modes The tool supports four version precision modes to control how version numbers are formatted in your `deno.json`: ### Auto Mode (Default) In `auto` mode, the tool automatically detects and preserves the version format from your existing `deno.json` entries: - **Major only** (`1`): If your deno.json has `npm:lodash@4`, it stays as `npm:lodash@4` - **Major.Minor** (`1.0`): If your deno.json has `npm:lodash@4.17`, it stays as `npm:lodash@4.17` - **Major.Minor.Patch** (`1.0.0`): If your deno.json has `npm:lodash@4.17.21`, it stays as `npm:lodash@4.17.21` **Example:** ``` // deno.json (before) { "imports": { "lodash": "npm:lodash@4", // Major only "react": "npm:react@18.2", // Major.Minor "typescript": "npm:typescript@5.0.0" // Major.Minor.Patch } } // package.json { "dependencies": { "lodash": "^4.17.21", "react": "^18.3.1", "typescript": "^5.4.5" } } // deno.json (after sync with --version-precision auto) { "imports": { "lodash": "npm:lodash@4", // Preserved as major only "react": "npm:react@18.3", // Preserved as major.minor "typescript": "npm:typescript@5.4.5" // Preserved as full version } } ``` ### Major Mode In `major` mode, the tool always uses only the major version number (e.g., `4`): **Example:** ``` // deno.json (before) { "imports": { "lodash": "npm:lodash@4.17.21", "react": "npm:react@18.2.0", "typescript": "npm:typescript@5.0.0" } } // package.json { "dependencies": { "lodash": "^4.17.21", "react": "^18.3.1", "typescript": "^5.4.5" } } // deno.json (after sync with --version-precision major) { "imports": { "lodash": "npm:lodash@4", // Forced to major only "react": "npm:react@18", // Forced to major only "typescript": "npm:typescript@5" // Forced to major only } } ``` ### Minor Mode In `minor` mode, the tool always uses major.minor version format (e.g., `4.17`): **Example:** ``` // deno.json (before) { "imports": { "lodash": "npm:lodash@4", "react": "npm:react@18.2.0", "typescript": "npm:typescript@5.0.0" } } // package.json { "dependencies": { "lodash": "^4.17.21", "react": "^18.3.1", "typescript": "^5.4.5" } } // deno.json (after sync with --version-precision minor) { "imports": { "lodash": "npm:lodash@4.17", // Forced to major.minor "react": "npm:react@18.3", // Forced to major.minor "typescript": "npm:typescript@5.4" // Forced to major.minor } } ``` ### Full Mode In `full` mode, the tool always uses the complete version (e.g., `4.17.21`): **Example:** ``` // deno.json (before) { "imports": { "lodash": "npm:lodash@4", "react": "npm:react@18.2", "typescript": "npm:typescript@5.0.0" } } // package.json { "dependencies": { "lodash": "^4.17.21", "react": "^18.3.1", "typescript": "^5.4.5" } } // deno.json (after sync with --version-precision full) { "imports": { "lodash": "npm:lodash@4.17.21", // Forced to full version "react": "npm:react@18.3.1", // Forced to full version "typescript": "npm:typescript@5.4.5" // Forced to full version } } ``` **Use Cases:** - **Auto mode**: Perfect for projects where you want to maintain existing version precision per package - **Major mode**: Ideal when you want to lock to major versions only for maximum compatibility - **Minor mode**: Good balance between stability and getting minor updates - **Full mode**: When you need exact version matching and full control ## PNPM Catalog Support The tool automatically resolves pnpm catalog references from `pnpm-workspace.yaml`, making it easy to manage centralized dependencies in monorepos. ### Default Catalog Use the default catalog with `catalog:` prefix: ```yaml # pnpm-workspace.yaml catalog: zod: ^3.22.0 react: ^18.2.0 lodash: ^4.17.21 ``` ```json // package.json { "dependencies": { "zod": "catalog:", "react": "catalog:", "lodash": "catalog:" } } ``` The tool will automatically resolve these to `^3.22.0`, `^18.2.0`, and `^4.17.21` respectively. ### Named Catalogs Use named catalogs for different dependency groups: ```yaml # pnpm-workspace.yaml catalogs: prod: zod: ^3.22.0 lodash: ^4.17.21 dev: typescript: ^5.0.0 vitest: ^1.0.0 ``` ```json // package.json { "dependencies": { "zod": "catalog:prod", "lodash": "catalog:prod" }, "devDependencies": { "typescript": "catalog:dev", "vitest": "catalog:dev" } } ``` The tool will resolve these references to the actual versions from the specified catalog. ### How It Works 1. The tool detects you're using pnpm (via `package-manager-detector`) 2. It looks for `pnpm-workspace.yaml` in your workspace root 3. When it finds a `catalog:` or `catalog:name` reference in `package.json`, it resolves the actual version 4. The resolved version is then used to sync with your `deno.json` **Example:** ``` // deno.json (before) { "imports": { "zod": "npm:zod@3.21.0" } } // package.json { "dependencies": { "zod": "catalog:prod" // Resolves to ^3.22.0 } } // deno.json (after sync) { "imports": { "zod": "npm:zod@3.22.0" } } ``` ## Registry Support ### NPM Registry The tool syncs npm packages with the `npm:` specifier: ``` # deno.json { "imports": { "lodash": "npm:lodash@4.17.21", "react": "npm:react@18.2.0" } } # package.json { "dependencies": { "lodash": "^4.17.21", "react": "^18.2.0" } } ``` ### JSR (JavaScript Registry) The tool also syncs JSR packages with the `jsr:` specifier: ``` # deno.json { "imports": { "@std/assert": "jsr:@std/assert@1.0.0", "@std/path": "jsr:@std/path@0.225.0" } } # package.json { "dependencies": { "@std/assert": "^1.0.0", "@std/path": "^0.225.0" } } ``` ### Mixed Registries You can use both npm and JSR imports in the same project: ```json // deno.json { "imports": { "lodash": "npm:lodash@4.17.21", "@std/assert": "jsr:@std/assert@1.0.0", "react": "npm:react@18.2.0" } } ``` The tool will automatically detect and sync the correct registry for each package. ## Programmatic Usage You can also use the library programmatically in your Node.js or TypeScript code: ```typescript import { syncDenoNpmDependencies } from 'deno-npm-sync'; // Basic usage with auto mode (default) const result = syncDenoNpmDependencies({ denoJsonPath: './deno.json', packageJsonPath: './package.json', silent: false, }); // Using major mode to always use major version only const result2 = syncDenoNpmDependencies({ denoJsonPath: './deno.json', packageJsonPath: './package.json', silent: false, versionPrecision: 'major', }); // Using minor mode for major.minor versions const result3 = syncDenoNpmDependencies({ denoJsonPath: './deno.json', packageJsonPath: './package.json', silent: false, versionPrecision: 'minor', }); // Using full mode for complete versions const result4 = syncDenoNpmDependencies({ denoJsonPath: './deno.json', packageJsonPath: './package.json', silent: false, versionPrecision: 'full', }); // Check results if (result.hasUpdates) { console.log(`Updated ${result.updates.length} packages:`); result.updates.forEach((update) => { console.log(` ${update.name}: ${update.oldVersion} โ†’ ${update.newVersion}`); }); } else { console.log('All packages are already in sync!'); } ``` ### API #### `syncDenoNpmDependencies(options: SyncOptions): SyncResult` Synchronizes npm package versions from package.json to deno.json imports. **Parameters:** - `options.denoJsonPath` (string): Path to deno.json file - `options.packageJsonPath` (string): Path to package.json file - `options.silent` (boolean, optional): If true, suppresses console output (default: false) - `options.versionPrecision` ('auto' | 'major' | 'minor' | 'full', optional): Version precision mode (default: 'auto') - `'auto'`: Preserves the version format from deno.json (e.g., `1`, `1.0`, `1.0.0`) - `'major'`: Always uses only major version (e.g., `1`) - `'minor'`: Always uses major.minor version (e.g., `1.0`) - `'full'`: Always uses full version (e.g., `1.0.0`) **Returns:** - `hasUpdates` (boolean): Whether any updates were made - `updates` (array): Array of update objects containing: - `name` (string): Package name - `oldVersion` (string): Previous version in deno.json - `newVersion` (string): New version from package.json ## How It Works The tool scans your `deno.json` imports for npm and JSR-prefixed packages: ```json { "imports": { "lodash": "npm:lodash@4.17.0", "typescript": "npm:typescript@4.9.0/lib/typescript.d.ts", "@std/assert": "jsr:@std/assert@0.226.0", "@std/path": "jsr:@std/path@0.224.0/posix" } } ``` Then it checks your `package.json` for the same packages: ```json { "dependencies": { "lodash": "^4.17.21", "@std/assert": "^1.0.0", "@std/path": "^0.225.0" }, "devDependencies": { "typescript": "^5.0.0" } } ``` And updates the deno.json to match: ```json { "imports": { "lodash": "npm:lodash@4.17.21", "typescript": "npm:typescript@5.0.0/lib/typescript.d.ts", "@std/assert": "jsr:@std/assert@1.0.0", "@std/path": "jsr:@std/path@0.225.0/posix" } } ``` ## Use Cases - **Monorepos**: Keep Deno and Node.js dependencies in sync - **Hybrid Projects**: Projects using both Deno and Node.js - **JSR + npm**: Projects using both JSR and npm registries - **CI/CD Pipelines**: Automate dependency synchronization in your build process - **Development Workflow**: Ensure consistency between package managers ## Integration with Scripts Add to your `package.json` scripts: ```json { "scripts": { "sync:deno": "deno-npm-sync", "postinstall": "deno-npm-sync --silent" } } ```