sort-package-json 1.0.0

Sort package.json files according to established npm conventions
Documentation

sort-package-json

Crates.io Docs.rs

MIT licensed Build Status Code Coverage CodSpeed Badge Discord chat

A Rust implementation that sorts package.json files according to well-established npm conventions.

Note on Compatibility: This crate is not compatible with the original sort-package-json npm package. While both tools sort package.json files, this Rust implementation uses different sorting groupings that we believe are clearer and easier to navigate. The field order is inspired by both the original sort-package-json and Prettier's package.json sorting, but organized into more intuitive logical groups.

Features

  • Sorts top-level fields according to npm ecosystem conventions (140 predefined fields)
  • Normalizes string lists where the ecosystem convention is to sort and remove duplicates
  • Respects semantics - exports and imports fields preserve their key order (first-match resolution)
  • Fast and memory-efficient - borrows unescaped strings and minimizes output allocations
  • Idempotent - sorting multiple times produces the same result
  • Handles edge cases - unknown fields sorted alphabetically, private fields (starting with _) sorted last

Usage

Add to your Cargo.toml:

[dependencies]
sort-package-json = "1"

Library API

use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = fs::read_to_string("package.json")?;
    let sorted = sort_package_json::sort_package_json(&contents)?;
    fs::write("package.json", sorted)?;
    Ok(())
}

With custom options:

use sort_package_json::{sort_package_json_with_options, SortOptions};

let contents = r#"{"scripts":{"test":"vitest","build":"tsup"}}"#;
let options = SortOptions::new().with_pretty(false).with_sort_scripts(true);
let sorted = sort_package_json_with_options(contents, &options).expect("valid package.json");

Running the Example

To test on a repository, run the included example which recursively finds and sorts all package.json files:

cargo run --example simple [PATH]

If no path is provided, it defaults to the current directory.

Example

Given an unsorted package.json:

{
  "version": "1.0.0",
  "dependencies": { "foo": "1.0.0" },
  "name": "my-package",
  "scripts": { "test": "vitest" }
}

After sorting:

{
  "name": "my-package",
  "version": "1.0.0",
  "scripts": { "test": "vitest" },
  "dependencies": { "foo": "1.0.0" }
}

Field Ordering

Fields are sorted into 12 logical groups, followed by unknown fields alphabetically, then private fields (starting with _) at the end. The abbreviated example below shows the groups and common fields. The ordering is based on both the original sort-package-json and Prettier's package.json sorting implementations.

{
  // 1. Core Package Metadata
  "$schema": "https://json.schemastore.org/package.json",
  "name": "my-package",
  "displayName": "My Package",
  "version": "1.0.0",
  "private": true,
  "description": "A sample package",
  "categories": ["linters", "formatters"],
  "keywords": ["sample", "test"],
  "homepage": "https://example.com",
  "bugs": { "url": "https://github.com/user/repo/issues", "email": "support@example.com" },

  // 2. License & People
  "license": "MIT",
  "author": { "name": "Author", "email": "author@example.com", "url": "https://example.com" },
  "maintainers": [{ "name": "Maintainer", "email": "maintainer@example.com" }],
  "contributors": [{ "name": "Contributor", "email": "contributor@example.com" }],

  // 3. Repository & Funding
  "repository": { "type": "git", "url": "https://github.com/user/repo.git" },
  "funding": { "type": "github", "url": "https://github.com/sponsors/user" },

  // 4. Package Content & Distribution
  "bin": { "my-cli": "./bin/cli.js" },
  "directories": { "lib": "lib", "bin": "bin", "man": "man", "doc": "doc" },
  "workspaces": ["packages/*"],
  "files": ["dist", "lib", "src/index.js"],
  "os": ["darwin", "linux"],
  "cpu": ["x64", "arm64"],

  // 5. Package Entry Points
  "type": "module",
  "sideEffects": false,
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "browser": "./dist/browser.js",
  "types": "./dist/index.d.ts",
  "imports": {/* preserved as-is (order has meaning per spec) */},
  "exports": {/* preserved as-is (order has meaning per spec) */},
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" },

  // 6. Scripts
  "scripts": {
    "build": "tsup",
    "test": "vitest",
    "lint": "eslint .",
  },

  // 7. Dependencies
  "dependencies": { "lodash": "^4.17.21" },
  "devDependencies": { "typescript": "^5.0.0", "vitest": "^1.0.0" },
  "peerDependencies": { "react": ">=18.0.0" },
  "peerDependenciesMeta": { "react": { "optional": true } },
  "optionalDependencies": { "fsevents": "^2.3.0" },
  "bundledDependencies": ["internal-lib"],
  "overrides": { "semver": "^7.5.4" },

  // 8. Git Hooks & Commit Tools
  "simple-git-hooks": { "pre-commit": "npx lint-staged" },
  "vite-staged": { "*.ts": "vitest related command" },
  "lint-staged": { "*.ts": ["eslint --fix", "prettier --write"] },
  "nano-staged": { "*.md": "prettier --write" },
  "commitlint": { "extends": ["@commitlint/config-conventional"] },

  // 9. VSCode Extension Specific
  "contributes": { "commands": [] },
  "activationEvents": ["onLanguage:javascript"],
  "icon": "icon.png",

  // 10. Build & Tool Configuration
  "browserslist": ["> 1%", "last 2 versions"],
  "prettier": { "semi": false, "singleQuote": true },
  "eslintConfig": { "extends": ["eslint:recommended"] },

  // 11. Testing
  "jest": { "testEnvironment": "node" },
  "c8": { "include": ["src/**"] },

  // 12. Runtime & Package Manager
  "engines": { "node": ">=18.0.0" },
  "packageManager": "pnpm@8.0.0",
  "pnpm": { "overrides": {} },

  // Unknown fields (sorted alphabetically)
  "customField": "value",
  "myConfig": {},

  // Private fields (sorted alphabetically, always last)
  "_internal": "hidden",
  "_private": "data",
}

Why Not simd-json?

We use serde_json instead of simd-json because:

  • No preserve_order support - simd-json can't maintain custom field insertion order (required for our sorting)
  • Platform issues - simd-json doesn't work on big-endian architectures (#437)

Development

Building

cargo build --release

Running Tests

cargo test

Tests use snapshot testing via insta. To review and accept snapshot changes:

cargo insta review

Or to accept all changes:

cargo insta accept

License

MIT

References

Sponsored By