├── .gitignore ├── rust-toolchain.toml ├── .ignore ├── .github ├── renovate.json └── workflows │ ├── release.yml │ ├── deny.yml │ ├── zizmor.yml │ ├── benchmark.yml │ └── ci.yml ├── dprint.json ├── .rustfmt.toml ├── justfile ├── LICENSE ├── CHANGELOG.md ├── Cargo.toml ├── examples └── simple.rs ├── tests ├── fixtures │ └── package.json ├── snapshots │ └── integration_test__sort_package_json.snap └── integration_test.rs ├── benches └── sort.rs ├── README.md ├── deny.toml ├── src └── lib.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.91.1" 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | # For watchexec https://github.com/watchexec/watchexec/tree/main/crates/cli#features 2 | 3 | target/** 4 | **/node_modules/** 5 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>Boshen/renovate"] 4 | } 5 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lineWidth": 120, 3 | "json": { 4 | "indentWidth": 2 5 | }, 6 | "toml": { 7 | }, 8 | "excludes": [], 9 | "plugins": [ 10 | "https://plugins.dprint.dev/json-0.21.0.wasm", 11 | "https://plugins.dprint.dev/markdown-0.20.0.wasm", 12 | "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm", 13 | "https://plugins.dprint.dev/toml-0.7.0.wasm" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: {} 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | release-plz: 12 | name: Release-plz 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | contents: write 17 | id-token: write 18 | steps: 19 | - uses: oxc-project/release-plz@44b98e8dda1a7783d4ec2ef66e2f37a3e8c1c759 # v1.0.4 20 | with: 21 | PAT: ${{ secrets.OXC_BOT_PAT }} 22 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | 3 | # Make Rust more readable given most people have wide screens nowadays. 4 | # This is also the setting used by [rustc](https://github.com/rust-lang/rust/blob/master/rustfmt.toml) 5 | use_small_heuristics = "Max" 6 | 7 | # Use field initialize shorthand if possible 8 | use_field_init_shorthand = true 9 | 10 | reorder_modules = true 11 | 12 | # All unstable features that we wish for 13 | # unstable_features = true 14 | # Provide a cleaner impl order 15 | # reorder_impl_items = true 16 | # Provide a cleaner import sort order 17 | # group_imports = "StdExternalCrate" 18 | # Group "use" statements by crate 19 | # imports_granularity = "Crate" 20 | -------------------------------------------------------------------------------- /.github/workflows/deny.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Deny 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize] 9 | paths: 10 | - "Cargo.lock" 11 | - "deny.toml" 12 | - ".github/workflows/deny.yml" 13 | push: 14 | branches: 15 | - main 16 | paths: 17 | - "Cargo.lock" 18 | - "deny.toml" 19 | - ".github/workflows/deny.yml" 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 23 | cancel-in-progress: ${{ github.ref_name != 'main' }} 24 | 25 | jobs: 26 | deny: 27 | name: Cargo Deny 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 31 | 32 | - uses: oxc-project/setup-rust@ecabb7322a2ba5aeedb3612d2a40b86a85cee235 # v1.0.11 33 | with: 34 | restore-cache: false 35 | tools: cargo-deny 36 | 37 | - run: cargo deny check 38 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S just --justfile 2 | 3 | set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] 4 | set shell := ["bash", "-cu"] 5 | 6 | _default: 7 | @just --list -u 8 | 9 | alias r := ready 10 | 11 | init: 12 | cargo binstall watchexec-cli cargo-insta typos-cli cargo-shear dprint -y 13 | 14 | ready: 15 | git diff --exit-code --quiet 16 | typos 17 | just fmt 18 | just check 19 | just test 20 | just lint 21 | just doc 22 | 23 | watch *args='': 24 | watchexec --no-vcs-ignore {{args}} 25 | 26 | fmt: 27 | cargo shear --fix 28 | cargo fmt --all 29 | dprint fmt 30 | 31 | check: 32 | cargo check --workspace --all-features --all-targets --locked 33 | 34 | watch-check: 35 | just watch "'cargo check; cargo clippy'" 36 | 37 | test: 38 | cargo test 39 | 40 | lint: 41 | cargo clippy --workspace --all-targets --all-features -- --deny warnings 42 | 43 | [unix] 44 | doc: 45 | RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items 46 | 47 | [windows] 48 | doc: 49 | $Env:RUSTDOCFLAGS='-D warnings'; cargo doc --no-deps --document-private-items 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present VoidZero Inc. & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: Zizmor 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize] 9 | paths: 10 | - ".github/workflows/**" 11 | push: 12 | branches: 13 | - main 14 | paths: 15 | - ".github/workflows/**" 16 | 17 | jobs: 18 | zizmor: 19 | name: zizmor 20 | runs-on: ubuntu-latest 21 | permissions: 22 | security-events: write 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 26 | with: 27 | persist-credentials: false 28 | 29 | - uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60 30 | with: 31 | tool: zizmor 32 | 33 | - name: Run zizmor 34 | run: zizmor --format sarif . > results.sarif 35 | env: 36 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Upload SARIF file 39 | uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 40 | with: 41 | sarif_file: results.sarif 42 | category: zizmor 43 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize] 9 | paths: 10 | - "**/*.rs" 11 | - "Cargo.lock" 12 | - ".github/workflows/benchmark.yml" 13 | push: 14 | branches: 15 | - main 16 | paths: 17 | - "**/*.rs" 18 | - "Cargo.lock" 19 | - ".github/workflows/benchmark.yml" 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | benchmark: 27 | name: Benchmark 28 | runs-on: ubuntu-latest 29 | permissions: 30 | id-token: write 31 | steps: 32 | - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 33 | 34 | - uses: oxc-project/setup-rust@ecabb7322a2ba5aeedb3612d2a40b86a85cee235 # v1.0.11 35 | with: 36 | cache-key: benchmark 37 | save-cache: ${{ github.ref_name == 'main' }} 38 | tools: cargo-codspeed 39 | 40 | - name: Build benchmark 41 | run: cargo codspeed build --features codspeed 42 | 43 | - name: Run benchmark 44 | uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3 45 | timeout-minutes: 15 46 | with: 47 | mode: instrumentation 48 | run: cargo codspeed run 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize] 9 | push: 10 | branches: 11 | - main 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 15 | cancel-in-progress: ${{ github.ref_name != 'main' }} 16 | 17 | defaults: 18 | run: 19 | shell: bash 20 | 21 | env: 22 | CARGO_INCREMENTAL: 0 23 | RUSTFLAGS: "-D warnings" 24 | 25 | jobs: 26 | test: 27 | name: Test 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | include: 32 | - os: ubuntu-latest 33 | # - os: windows-latest 34 | # - os: macos-14 35 | runs-on: ${{ matrix.os }} 36 | steps: 37 | - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 38 | - uses: oxc-project/setup-rust@ecabb7322a2ba5aeedb3612d2a40b86a85cee235 # v1.0.11 39 | with: 40 | save-cache: ${{ github.ref_name == 'main' }} 41 | - run: cargo check --all-targets --all-features 42 | - run: cargo test 43 | 44 | lint: 45 | name: Lint 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 49 | 50 | - uses: oxc-project/setup-rust@ecabb7322a2ba5aeedb3612d2a40b86a85cee235 # v1.0.11 51 | with: 52 | components: clippy rust-docs rustfmt 53 | 54 | - run: | 55 | cargo fmt --check 56 | cargo clippy --all-targets --all-features -- -D warnings 57 | RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items 58 | 59 | - uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1.40.0 60 | with: 61 | files: . 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.0.5](https://github.com/oxc-project/sort-package-json/compare/v0.0.4...v0.0.5) - 2025-12-17 11 | 12 | ### Other 13 | 14 | - Optimize more functions with in-place mutations 15 | - Optimize array sorting with in-place operations ([#14](https://github.com/oxc-project/sort-package-json/pull/14)) 16 | - Use unstable sort for better performance ([#13](https://github.com/oxc-project/sort-package-json/pull/13)) 17 | - Sort files field with natural path sorting ([#10](https://github.com/oxc-project/sort-package-json/pull/10)) 18 | 19 | ## [0.0.4](https://github.com/oxc-project/sort-package-json/compare/v0.0.3...v0.0.4) - 2025-12-17 20 | 21 | ### Fixed 22 | 23 | - Keep `exports` paths order ([#5](https://github.com/oxc-project/sort-package-json/pull/5)) 24 | 25 | ### Other 26 | 27 | - Add 12 commonly-used fields from npm ecosystem analysis ([#8](https://github.com/oxc-project/sort-package-json/pull/8)) 28 | - Improve field grouping with clearer logical organization ([#7](https://github.com/oxc-project/sort-package-json/pull/7)) 29 | 30 | ## [0.0.3](https://github.com/oxc-project/sort-package-json/compare/v0.0.2...v0.0.3) - 2025-12-10 31 | 32 | ### Other 33 | 34 | - Move main.rs to examples and make ignore a dev dependency 35 | - Replace cloning with ownership and mutation 36 | - fmt 37 | 38 | ## [0.0.2](https://github.com/oxc-project/sort-package-json/compare/v0.0.1...v0.0.2) - 2025-12-08 39 | 40 | ### Other 41 | 42 | - Update README field count to 126 43 | - Use unstable sort for better performance 44 | - Move main field below type field 45 | - Add declare_field_order! macro to simplify field ordering 46 | - Add napi field after bundleDependencies 47 | - Refactor value transformation with helper functions 48 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sort-package-json" 3 | version = "0.0.5" 4 | authors = ["Boshen "] 5 | categories = [] 6 | edition = "2021" 7 | include = ["/src"] 8 | keywords = [] 9 | license = "MIT" 10 | publish = true 11 | readme = "README.md" 12 | repository = "https://github.com/oxc-project/sort-package-json" 13 | rust-version = "1.76" 14 | description = "A Rust implementation of sort-package-json that sorts package.json files according to well-established npm conventions" 15 | 16 | [lints.rust] 17 | absolute_paths_not_starting_with_crate = "warn" 18 | non_ascii_idents = "warn" 19 | unit-bindings = "warn" 20 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)', 'cfg(coverage_nightly)'] } 21 | 22 | [lints.clippy] 23 | all = { level = "warn", priority = -1 } 24 | # restriction 25 | dbg_macro = "warn" 26 | todo = "warn" 27 | unimplemented = "warn" 28 | print_stdout = "warn" # Must be opt-in 29 | print_stderr = "warn" # Must be opt-in 30 | allow_attributes = "warn" 31 | # I like the explicitness of this rule as it removes confusion around `clone`. 32 | # This increases readability, avoids `clone` mindlessly and heap allocating by accident. 33 | clone_on_ref_ptr = "warn" 34 | # These two are mutually exclusive, I like `mod.rs` files for better fuzzy searches on module entries. 35 | self_named_module_files = "warn" # "-Wclippy::mod_module_files" 36 | empty_drop = "warn" 37 | empty_structs_with_brackets = "warn" 38 | exit = "warn" 39 | filetype_is_file = "warn" 40 | get_unwrap = "warn" 41 | impl_trait_in_params = "warn" 42 | rc_buffer = "warn" 43 | rc_mutex = "warn" 44 | rest_pat_in_fully_bound_structs = "warn" 45 | unnecessary_safety_comment = "warn" 46 | undocumented_unsafe_blocks = "warn" 47 | infinite_loop = "warn" 48 | 49 | [dependencies] 50 | serde_json = { version = "1.0", features = ["preserve_order"] } 51 | 52 | [dev-dependencies] 53 | criterion2 = { version = "3", default-features = false } 54 | ignore = "0.4" 55 | insta = "1.41" 56 | 57 | [[bench]] 58 | name = "sort" 59 | harness = false 60 | 61 | [features] 62 | codspeed = ["criterion2/codspeed"] 63 | 64 | [profile.release] 65 | # Configurations explicitly listed here for clarity. 66 | # Using the best options for performance. 67 | opt-level = 3 68 | lto = "fat" 69 | codegen-units = 1 70 | strip = "symbols" # set to `false` for debug information 71 | debug = false # set to `true` for debug information 72 | panic = "abort" # Let it crash and force ourselves to write safe Rust. 73 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::Path; 4 | use std::process; 5 | 6 | use ignore::WalkBuilder; 7 | 8 | #[allow(clippy::print_stderr)] 9 | fn main() { 10 | let args: Vec = env::args().collect(); 11 | 12 | // Parse command line arguments 13 | let search_path = if args.len() > 1 { 14 | let arg = &args[1]; 15 | if arg == "--help" || arg == "-h" { 16 | eprintln!("Usage: {} [PATH]", args[0]); 17 | eprintln!( 18 | "\nRecursively finds and sorts all package.json files in the specified directory." 19 | ); 20 | eprintln!("\nArguments:"); 21 | eprintln!(" PATH Directory to search (defaults to current directory)"); 22 | process::exit(0); 23 | } 24 | Path::new(arg).to_path_buf() 25 | } else { 26 | env::current_dir().unwrap_or_else(|err| { 27 | eprintln!("Error getting current directory: {}", err); 28 | process::exit(1); 29 | }) 30 | }; 31 | 32 | if !search_path.exists() { 33 | eprintln!("Error: Path does not exist: {}", search_path.display()); 34 | process::exit(1); 35 | } 36 | 37 | if !search_path.is_dir() { 38 | eprintln!("Error: Path is not a directory: {}", search_path.display()); 39 | process::exit(1); 40 | } 41 | 42 | // Find all package.json files 43 | let mut found_files = 0; 44 | let mut sorted_files = 0; 45 | let mut errors = 0; 46 | 47 | for entry in WalkBuilder::new(search_path) 48 | .build() 49 | .filter_map(Result::ok) 50 | .filter(|e| e.file_name() == "package.json") 51 | { 52 | found_files += 1; 53 | let file_path = entry.path(); 54 | 55 | match process_file(file_path) { 56 | Ok(()) => { 57 | sorted_files += 1; 58 | eprintln!("✓ Sorted: {}", file_path.display()); 59 | } 60 | Err(err) => { 61 | errors += 1; 62 | eprintln!("✗ Error processing {}: {}", file_path.display(), err); 63 | } 64 | } 65 | } 66 | 67 | eprintln!("\nSummary:"); 68 | eprintln!(" Found: {}", found_files); 69 | eprintln!(" Sorted: {}", sorted_files); 70 | eprintln!(" Errors: {}", errors); 71 | 72 | if errors > 0 { 73 | process::exit(1); 74 | } 75 | } 76 | 77 | fn process_file(file_path: &Path) -> Result<(), String> { 78 | let contents = 79 | fs::read_to_string(file_path).map_err(|err| format!("Failed to read: {}", err))?; 80 | 81 | let sorted = sort_package_json::sort_package_json(&contents) 82 | .map_err(|err| format!("Failed to parse JSON: {}", err))?; 83 | 84 | fs::write(file_path, sorted).map_err(|err| format!("Failed to write: {}", err))?; 85 | 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /tests/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-package", 3 | "version": "1.0.0", 4 | "description": "A test package for sorting", 5 | "keywords": [ 6 | "test", 7 | "sorting", 8 | "json", 9 | "test", 10 | "example" 11 | ], 12 | "homepage": "https://github.com/test/test-package#readme", 13 | "bugs": { 14 | "email": "bugs@example.com", 15 | "url": "https://github.com/test/test-package/issues" 16 | }, 17 | "repository": { 18 | "url": "https://github.com/test/test-package", 19 | "type": "git" 20 | }, 21 | "license": "MIT", 22 | "author": { 23 | "url": "https://example.com", 24 | "email": "author@example.com", 25 | "name": "Test Author" 26 | }, 27 | "maintainers": [ 28 | { 29 | "url": "https://maintainer1.com", 30 | "name": "Maintainer One", 31 | "email": "maintainer1@example.com" 32 | }, 33 | { 34 | "email": "maintainer2@example.com", 35 | "url": "https://maintainer2.com", 36 | "name": "Maintainer Two" 37 | } 38 | ], 39 | "exports": { 40 | ".": { 41 | "default": "./dist/index.js", 42 | "require": "./dist/index.cjs", 43 | "types": "./dist/index.d.ts", 44 | "import": "./dist/index.esm.js" 45 | }, 46 | "./paths-should-keep-b_a": { 47 | "b": "./index.cjs", 48 | "a": "./index.js" 49 | }, 50 | "./package.json": "./package.json", 51 | "./utils": { 52 | "import": "./dist/utils.esm.js", 53 | "default": "./dist/utils.js", 54 | "types": "./dist/utils.d.ts" 55 | } 56 | }, 57 | "main": "./dist/index.js", 58 | "module": "./dist/index.esm.js", 59 | "types": "./dist/index.d.ts", 60 | "files": [ 61 | "src/generated/constants.js", 62 | "src", 63 | "dist", 64 | "src/index.js", 65 | "README.md", 66 | "src/utils/helper.js", 67 | "dist", 68 | "LICENSE", 69 | "bin/cli.js" 70 | ], 71 | "scripts": { 72 | "test": "jest", 73 | "posttest": "echo 'Tests complete'", 74 | "build": "webpack", 75 | "lint": "eslint .", 76 | "pretest": "echo 'Starting tests'", 77 | "dev": "webpack serve" 78 | }, 79 | "dependencies": { 80 | "react": "^18.0.0", 81 | "axios": "^1.0.0", 82 | "lodash": "^4.17.21" 83 | }, 84 | "devDependencies": { 85 | "webpack": "^5.0.0", 86 | "jest": "^29.0.0", 87 | "typescript": "^5.0.0" 88 | }, 89 | "engines": { 90 | "npm": ">=8.0.0", 91 | "node": ">=18.0.0" 92 | }, 93 | "babel": { 94 | "presets": ["@babel/preset-env", "@babel/preset-react"], 95 | "plugins": ["@babel/plugin-proposal-class-properties"] 96 | }, 97 | "publishConfig": { 98 | "access": "public" 99 | }, 100 | "customField": "this is a custom unknown field", 101 | "_custom": "another private field", 102 | "_id": "test-package@1.0.0" 103 | } 104 | -------------------------------------------------------------------------------- /benches/sort.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, black_box, criterion_group, criterion_main}; 2 | use sort_package_json::sort_package_json; 3 | 4 | fn bench_small_package(c: &mut Criterion) { 5 | let input = include_str!("../tests/fixtures/package.json"); 6 | c.bench_function("sort small package.json", |b| { 7 | b.iter(|| sort_package_json(black_box(input))); 8 | }); 9 | } 10 | 11 | fn bench_already_sorted(c: &mut Criterion) { 12 | let input = include_str!("../tests/fixtures/package.json"); 13 | let sorted = sort_package_json(input).unwrap(); 14 | c.bench_function("sort already sorted package.json", |b| { 15 | b.iter(|| sort_package_json(black_box(&sorted))); 16 | }); 17 | } 18 | 19 | fn bench_minimal_package(c: &mut Criterion) { 20 | let input = r#"{ 21 | "version": "1.0.0", 22 | "name": "test", 23 | "description": "A test package" 24 | }"#; 25 | c.bench_function("sort minimal package.json", |b| { 26 | b.iter(|| sort_package_json(black_box(input))); 27 | }); 28 | } 29 | 30 | fn bench_large_package(c: &mut Criterion) { 31 | let input = r#"{ 32 | "version": "1.0.0", 33 | "dependencies": { 34 | "react": "^18.0.0", 35 | "axios": "^1.0.0", 36 | "lodash": "^4.17.21", 37 | "express": "^4.18.0", 38 | "typescript": "^5.0.0", 39 | "webpack": "^5.0.0", 40 | "babel-loader": "^9.0.0", 41 | "eslint": "^8.0.0", 42 | "prettier": "^3.0.0", 43 | "jest": "^29.0.0" 44 | }, 45 | "devDependencies": { 46 | "@types/react": "^18.0.0", 47 | "@types/node": "^20.0.0", 48 | "@types/express": "^4.17.0", 49 | "ts-node": "^10.0.0", 50 | "nodemon": "^3.0.0", 51 | "concurrently": "^8.0.0" 52 | }, 53 | "scripts": { 54 | "test": "jest", 55 | "build": "webpack", 56 | "lint": "eslint .", 57 | "format": "prettier --write .", 58 | "dev": "nodemon", 59 | "start": "node dist/index.js", 60 | "pretest": "npm run lint", 61 | "postbuild": "echo 'Build complete'" 62 | }, 63 | "name": "large-package", 64 | "description": "A larger test package", 65 | "keywords": ["test", "large", "package", "example", "benchmark"], 66 | "author": "Test Author ", 67 | "license": "MIT", 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/test/test" 71 | }, 72 | "bugs": { 73 | "url": "https://github.com/test/test/issues" 74 | }, 75 | "homepage": "https://github.com/test/test#readme", 76 | "main": "./dist/index.js", 77 | "types": "./dist/index.d.ts", 78 | "files": ["dist", "README.md", "LICENSE"], 79 | "engines": { 80 | "node": ">=18.0.0", 81 | "npm": ">=8.0.0" 82 | } 83 | }"#; 84 | c.bench_function("sort large package.json", |b| { 85 | b.iter(|| sort_package_json(black_box(input))); 86 | }); 87 | } 88 | 89 | criterion_group!( 90 | benches, 91 | bench_small_package, 92 | bench_already_sorted, 93 | bench_minimal_package, 94 | bench_large_package 95 | ); 96 | criterion_main!(benches); 97 | -------------------------------------------------------------------------------- /tests/snapshots/integration_test__sort_package_json.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/integration_test.rs 3 | expression: result 4 | --- 5 | { 6 | "name": "test-package", 7 | "version": "1.0.0", 8 | "description": "A test package for sorting", 9 | "keywords": [ 10 | "example", 11 | "json", 12 | "sorting", 13 | "test" 14 | ], 15 | "homepage": "https://github.com/test/test-package#readme", 16 | "bugs": { 17 | "url": "https://github.com/test/test-package/issues", 18 | "email": "bugs@example.com" 19 | }, 20 | "license": "MIT", 21 | "author": { 22 | "name": "Test Author", 23 | "email": "author@example.com", 24 | "url": "https://example.com" 25 | }, 26 | "maintainers": [ 27 | { 28 | "name": "Maintainer One", 29 | "email": "maintainer1@example.com", 30 | "url": "https://maintainer1.com" 31 | }, 32 | { 33 | "name": "Maintainer Two", 34 | "email": "maintainer2@example.com", 35 | "url": "https://maintainer2.com" 36 | } 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/test/test-package" 41 | }, 42 | "files": [ 43 | "dist", 44 | "LICENSE", 45 | "README.md", 46 | "src", 47 | "bin/cli.js", 48 | "src/index.js", 49 | "src/generated/constants.js", 50 | "src/utils/helper.js" 51 | ], 52 | "main": "./dist/index.js", 53 | "module": "./dist/index.esm.js", 54 | "types": "./dist/index.d.ts", 55 | "exports": { 56 | ".": { 57 | "types": "./dist/index.d.ts", 58 | "require": "./dist/index.cjs", 59 | "import": "./dist/index.esm.js", 60 | "default": "./dist/index.js" 61 | }, 62 | "./paths-should-keep-b_a": { 63 | "b": "./index.cjs", 64 | "a": "./index.js" 65 | }, 66 | "./package.json": "./package.json", 67 | "./utils": { 68 | "types": "./dist/utils.d.ts", 69 | "import": "./dist/utils.esm.js", 70 | "default": "./dist/utils.js" 71 | } 72 | }, 73 | "publishConfig": { 74 | "access": "public" 75 | }, 76 | "scripts": { 77 | "test": "jest", 78 | "posttest": "echo 'Tests complete'", 79 | "build": "webpack", 80 | "lint": "eslint .", 81 | "pretest": "echo 'Starting tests'", 82 | "dev": "webpack serve" 83 | }, 84 | "dependencies": { 85 | "axios": "^1.0.0", 86 | "lodash": "^4.17.21", 87 | "react": "^18.0.0" 88 | }, 89 | "devDependencies": { 90 | "jest": "^29.0.0", 91 | "typescript": "^5.0.0", 92 | "webpack": "^5.0.0" 93 | }, 94 | "babel": { 95 | "plugins": [ 96 | "@babel/plugin-proposal-class-properties" 97 | ], 98 | "presets": [ 99 | "@babel/preset-env", 100 | "@babel/preset-react" 101 | ] 102 | }, 103 | "engines": { 104 | "node": ">=18.0.0", 105 | "npm": ">=8.0.0" 106 | }, 107 | "customField": "this is a custom unknown field", 108 | "_custom": "another private field", 109 | "_id": "test-package@1.0.0" 110 | } 111 | -------------------------------------------------------------------------------- /tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | use sort_package_json::{SortOptions, sort_package_json, sort_package_json_with_options}; 2 | use std::fs; 3 | 4 | #[test] 5 | fn test_sort_package_json() { 6 | let input = fs::read_to_string("tests/fixtures/package.json").expect("Failed to read fixture"); 7 | let result = sort_package_json(&input).expect("Failed to parse package.json"); 8 | insta::assert_snapshot!(result); 9 | } 10 | 11 | #[test] 12 | fn test_idempotency() { 13 | let input = fs::read_to_string("tests/fixtures/package.json").expect("Failed to read fixture"); 14 | let first_sort = sort_package_json(&input).expect("Failed to parse package.json on first sort"); 15 | let second_sort = 16 | sort_package_json(&first_sort).expect("Failed to parse package.json on second sort"); 17 | assert_eq!(first_sort, second_sort, "Sorting should be idempotent"); 18 | } 19 | 20 | #[test] 21 | fn test_no_fields_removed() { 22 | use serde_json::Value; 23 | 24 | let input = fs::read_to_string("tests/fixtures/package.json").expect("Failed to read fixture"); 25 | 26 | let sorted = sort_package_json(&input).expect("Failed to parse package.json"); 27 | 28 | // Parse both as JSON values 29 | let input_value: Value = serde_json::from_str(&input).expect("Failed to parse input JSON"); 30 | let sorted_value: Value = serde_json::from_str(&sorted).expect("Failed to parse sorted JSON"); 31 | 32 | // Extract both as objects 33 | let input_obj = input_value.as_object().expect("Input should be an object"); 34 | let sorted_obj = sorted_value.as_object().expect("Sorted should be an object"); 35 | 36 | // Verify all keys from input exist in sorted output 37 | for key in input_obj.keys() { 38 | assert!(sorted_obj.contains_key(key), "Key '{}' was removed during sorting", key); 39 | } 40 | 41 | // Verify no extra keys were added 42 | for key in sorted_obj.keys() { 43 | assert!(input_obj.contains_key(key), "Key '{}' was added during sorting", key); 44 | } 45 | 46 | // Verify the key count is the same 47 | assert_eq!(input_obj.len(), sorted_obj.len(), "Number of fields changed during sorting"); 48 | } 49 | 50 | #[test] 51 | fn test_sort_with_compact_format() { 52 | use serde_json::Value; 53 | 54 | let input = r#"{"version": "1.0.0", "name": "test-package", "description": "A test"}"#; 55 | 56 | let options = SortOptions { pretty: false }; 57 | let result = 58 | sort_package_json_with_options(input, &options).expect("Failed to sort package.json"); 59 | 60 | // Verify it's valid JSON 61 | let parsed: Value = serde_json::from_str(&result).expect("Result should be valid JSON"); 62 | 63 | // Verify it's compact (no newlines or extra whitespace) 64 | assert!(!result.contains('\n'), "Compact format should not contain newlines"); 65 | assert!(!result.contains(" "), "Compact format should not contain double spaces"); 66 | 67 | // Verify field order is correct (name before version) 68 | let name_pos = result.find("\"name\"").expect("Should contain name field"); 69 | let version_pos = result.find("\"version\"").expect("Should contain version field"); 70 | assert!(name_pos < version_pos, "name should come before version"); 71 | 72 | // Verify all fields are present 73 | let obj = parsed.as_object().expect("Result should be an object"); 74 | assert_eq!(obj.len(), 3, "Should have 3 fields"); 75 | assert!(obj.contains_key("name")); 76 | assert!(obj.contains_key("version")); 77 | assert!(obj.contains_key("description")); 78 | } 79 | 80 | #[test] 81 | fn test_compact_format_idempotency() { 82 | let input = fs::read_to_string("tests/fixtures/package.json").expect("Failed to read fixture"); 83 | 84 | let options = SortOptions { pretty: false }; 85 | let first_sort = sort_package_json_with_options(&input, &options).expect("First sort failed"); 86 | let second_sort = 87 | sort_package_json_with_options(&first_sort, &options).expect("Second sort failed"); 88 | 89 | assert_eq!(first_sort, second_sort, "Compact format sorting should be idempotent"); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # sort-package-json 4 | 5 | [![Crates.io][crates-badge]][crates-url] 6 | [![Docs.rs][docs-badge]][docs-url] 7 | 8 | [![MIT licensed][license-badge]][license-url] 9 | [![Build Status][ci-badge]][ci-url] 10 | [![Code Coverage][code-coverage-badge]][code-coverage-url] 11 | [![CodSpeed Badge][codspeed-badge]][codspeed-url] 12 | [![Sponsors][sponsors-badge]][sponsors-url] 13 | [![Discord chat][discord-badge]][discord-url] 14 | 15 |
16 | 17 | A Rust implementation of [sort-package-json](https://github.com/keithamus/sort-package-json) that sorts package.json files according to well-established npm conventions. 18 | 19 | ## Features 20 | 21 | - **Sorts top-level fields** according to npm ecosystem conventions (138 predefined fields) 22 | - **Preserves all data** - only reorders fields, never modifies values 23 | - **Fast and safe** - pure Rust implementation with no unsafe code 24 | - **Idempotent** - sorting multiple times produces the same result 25 | - **Handles edge cases** - unknown fields sorted alphabetically, private fields (starting with `_`) sorted last 26 | 27 | ## Usage 28 | 29 | ```bash 30 | cargo run 31 | ``` 32 | 33 | The tool will recursively find all `package.json` files in the current directory and sort them in place. 34 | 35 | ### Example 36 | 37 | Given an unsorted package.json: 38 | 39 | ```json 40 | { 41 | "version": "1.0.0", 42 | "dependencies": { ... }, 43 | "name": "my-package", 44 | "scripts": { ... } 45 | } 46 | ``` 47 | 48 | Running `cargo run package.json` produces: 49 | 50 | ```json 51 | { 52 | "name": "my-package", 53 | "version": "1.0.0", 54 | "scripts": { ... }, 55 | "dependencies": { ... } 56 | } 57 | ``` 58 | 59 | ## Field Ordering 60 | 61 | Fields are sorted according to this priority: 62 | 63 | 1. **Known fields** - 138 predefined fields organized into 12 logical groups 64 | 2. **Unknown fields** - any custom fields sorted alphabetically 65 | 3. **Private fields** - fields starting with `_` sorted alphabetically at the end 66 | 67 | The complete field order is based on both the [original sort-package-json](https://github.com/keithamus/sort-package-json/blob/main/index.js) and [prettier's package.json sorting](https://github.com/un-ts/prettier/blob/master/packages/pkg/src/rules/sort.ts) implementations. 68 | 69 | ### Known Field Groups 70 | 71 | #### 1. Core Package Metadata 72 | `$schema`, `name`, `displayName`, `version`, `stableVersion`, `gitHead`, `private`, `description`, `categories`, `keywords`, `homepage`, `bugs` 73 | 74 | #### 2. License & People 75 | `license`, `author`, `maintainers`, `contributors` 76 | 77 | #### 3. Repository & Funding 78 | `repository`, `funding`, `donate`, `sponsor`, `qna`, `publisher` 79 | 80 | #### 4. Package Content & Distribution 81 | `man`, `style`, `example`, `examplestyle`, `assets`, `bin`, `source`, `directories`, `workspaces`, `binary`, `files`, `os`, `cpu`, `libc` 82 | 83 | #### 5. Package Entry Points 84 | `type`, `sideEffects`, `main`, `module`, `browser`, `types`, `typings`, `typesVersions`, `typeScriptVersion`, `typesPublisherContentHash`, `react-native`, `svelte`, `unpkg`, `jsdelivr`, `jsnext:main`, `umd`, `umd:main`, `es5`, `esm5`, `fesm5`, `es2015`, `esm2015`, `fesm2015`, `es2020`, `esm2020`, `fesm2020`, `esnext`, `imports`, `exports`, `publishConfig` 85 | 86 | #### 6. Scripts 87 | `scripts`, `betterScripts` 88 | 89 | #### 7. Dependencies 90 | `dependencies`, `devDependencies`, `dependenciesMeta`, `peerDependencies`, `peerDependenciesMeta`, `optionalDependencies`, `bundledDependencies`, `bundleDependencies`, `resolutions`, `overrides` 91 | 92 | #### 8. Git Hooks & Commit Tools 93 | `husky`, `simple-git-hooks`, `pre-commit`, `lint-staged`, `nano-staged`, `commitlint` 94 | 95 | #### 9. VSCode Extension Specific 96 | `l10n`, `contributes`, `activationEvents`, `extensionPack`, `extensionDependencies`, `extensionKind`, `icon`, `badges`, `galleryBanner`, `preview`, `markdown` 97 | 98 | #### 10. Build & Tool Configuration 99 | `napi`, `flat`, `config`, `nodemonConfig`, `browserify`, `babel`, `browserslist`, `xo`, `prettier`, `eslintConfig`, `eslintIgnore`, `standard`, `npmpkgjsonlint`, `npmPackageJsonLintConfig`, `npmpackagejsonlint`, `release`, `auto-changelog`, `remarkConfig`, `stylelint`, `typescript`, `typedoc`, `tshy`, `tsdown`, `size-limit` 100 | 101 | #### 11. Testing 102 | `ava`, `jest`, `jest-junit`, `jest-stare`, `mocha`, `nyc`, `c8`, `tap`, `tsd`, `typeCoverage`, `oclif` 103 | 104 | #### 12. Runtime & Package Manager 105 | `languageName`, `preferGlobal`, `devEngines`, `engines`, `engineStrict`, `volta`, `packageManager`, `pnpm` 106 | 107 | ## Why Not simd-json? 108 | 109 | We use serde_json instead of [simd-json](https://github.com/simd-lite/simd-json) because: 110 | 111 | - **No preserve_order support** - simd-json can't maintain custom field insertion order (required for our sorting) 112 | - **Platform issues** - simd-json doesn't work on big-endian architectures ([#437](https://github.com/simd-lite/simd-json/issues/437)) 113 | 114 | ## Development 115 | 116 | ### Building 117 | 118 | ```bash 119 | cargo build --release 120 | ``` 121 | 122 | ### Running Tests 123 | 124 | ```bash 125 | cargo test 126 | ``` 127 | 128 | Tests use snapshot testing via [insta](https://insta.rs/). To review and accept snapshot changes: 129 | 130 | ```bash 131 | cargo insta review 132 | ``` 133 | 134 | Or to accept all changes: 135 | 136 | ```bash 137 | cargo insta accept 138 | ``` 139 | 140 | ### Test Coverage 141 | 142 | - **Field ordering test** - verifies correct sorting of all field types 143 | - **Idempotency test** - ensures sorting is stable (sorting twice = sorting once) 144 | 145 | ## License 146 | 147 | MIT 148 | 149 | ## References 150 | 151 | - [Original sort-package-json (JavaScript)](https://github.com/keithamus/sort-package-json) 152 | - [simd-json issue #437 - Big Endian Compatibility](https://github.com/simd-lite/simd-json/issues/437) 153 | - [Surprises in the Rust JSON Ecosystem](https://ecton.dev/rust-json-ecosystem/) 154 | 155 | ## [Sponsored By](https://github.com/sponsors/Boshen) 156 | 157 |

158 | 159 | My sponsors 160 | 161 |

162 | 163 | [discord-badge]: https://img.shields.io/discord/1079625926024900739?logo=discord&label=Discord 164 | [discord-url]: https://discord.gg/9uXCAwqQZW 165 | [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg 166 | [license-url]: https://github.com/oxc-project/sort-package-json/blob/main/LICENSE 167 | [ci-badge]: https://github.com/oxc-project/sort-package-json/actions/workflows/ci.yml/badge.svg?event=push&branch=main 168 | [ci-url]: https://github.com/oxc-project/sort-package-json/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain 169 | [code-coverage-badge]: https://codecov.io/github/oxc-project/sort-package-json/branch/main/graph/badge.svg 170 | [code-coverage-url]: https://codecov.io/gh/oxc-project/sort-package-json 171 | [sponsors-badge]: https://img.shields.io/github/sponsors/Boshen 172 | [sponsors-url]: https://github.com/sponsors/Boshen 173 | [codspeed-badge]: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json 174 | [codspeed-url]: https://codspeed.io/oxc-project/sort-package-json 175 | [crates-badge]: https://img.shields.io/crates/d/sort-package-json?label=crates.io 176 | [crates-url]: https://crates.io/crates/sort-package-json 177 | [docs-badge]: https://img.shields.io/docsrs/sort-package-json 178 | [docs-url]: https://docs.rs/sort-package-json 179 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # This section is considered when running `cargo deny check advisories` 13 | # More documentation for the advisories section can be found here: 14 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 15 | [advisories] 16 | # The path where the advisory database is cloned/fetched into 17 | db-path = "~/.cargo/advisory-db" 18 | # The url(s) of the advisory databases to use 19 | db-urls = ["https://github.com/rustsec/advisory-db"] 20 | # The lint level for crates that have been yanked from their source registry 21 | yanked = "warn" 22 | # A list of advisory IDs to ignore. Note that ignored advisories will still 23 | # output a note when they are encountered. 24 | ignore = [ 25 | "RUSTSEC-2024-0399", 26 | # "RUSTSEC-0000-0000", 27 | ] 28 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 29 | # lower than the range specified will be ignored. Note that ignored advisories 30 | # will still output a note when they are encountered. 31 | # * None - CVSS Score 0.0 32 | # * Low - CVSS Score 0.1 - 3.9 33 | # * Medium - CVSS Score 4.0 - 6.9 34 | # * High - CVSS Score 7.0 - 8.9 35 | # * Critical - CVSS Score 9.0 - 10.0 36 | # severity-threshold = 37 | 38 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 39 | # If this is false, then it uses a built-in git library. 40 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 41 | # See Git Authentication for more information about setting up git authentication. 42 | # git-fetch-with-cli = true 43 | 44 | # This section is considered when running `cargo deny check licenses` 45 | # More documentation for the licenses section can be found here: 46 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 47 | [licenses] 48 | # List of explicitly allowed licenses 49 | # See https://spdx.org/licenses/ for list of possible licenses 50 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 51 | allow = [ 52 | "Apache-2.0", 53 | "BSD-3-Clause", 54 | "ISC", 55 | "MIT", 56 | "MPL-2.0", 57 | "OpenSSL", 58 | "Unicode-DFS-2016", 59 | "Unicode-3.0", 60 | ] 61 | # The confidence threshold for detecting a license from license text. 62 | # The higher the value, the more closely the license text must be to the 63 | # canonical license text of a valid SPDX license file. 64 | # [possible values: any between 0.0 and 1.0]. 65 | confidence-threshold = 0.8 66 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 67 | # aren't accepted for every possible crate as with the normal allow list 68 | exceptions = [ 69 | 70 | # Each entry is the crate and version constraint, and its specific allow 71 | # list 72 | # { allow = ["Zlib"], name = "adler32", version = "*" }, 73 | ] 74 | 75 | # Some crates don't have (easily) machine readable licensing information, 76 | # adding a clarification entry for it allows you to manually specify the 77 | # licensing information 78 | [[licenses.clarify]] 79 | # The name of the crate the clarification applies to 80 | name = "ring" 81 | # The optional version constraint for the crate 82 | version = "*" 83 | # The SPDX expression for the license requirements of the crate 84 | expression = "MIT AND ISC AND OpenSSL" 85 | # One or more files in the crate's source used as the "source of truth" for 86 | # the license expression. If the contents match, the clarification will be used 87 | # when running the license check, otherwise the clarification will be ignored 88 | # and the crate will be checked normally, which may produce warnings or errors 89 | # depending on the rest of your configuration 90 | license-files = [ 91 | # Each entry is a crate relative path, and the (opaque) hash of its contents 92 | { path = "LICENSE", hash = 0xbd0eed23 }, 93 | ] 94 | 95 | [licenses.private] 96 | # If true, ignores workspace crates that aren't published, or are only 97 | # published to private registries. 98 | # To see how to mark a crate as unpublished (to the official registry), 99 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 100 | ignore = false 101 | # One or more private registries that you might publish crates to, if a crate 102 | # is only published to private registries, and ignore is true, the crate will 103 | # not have its license(s) checked 104 | registries = [ 105 | 106 | # "https://sekretz.com/registry 107 | ] 108 | 109 | # This section is considered when running `cargo deny check bans`. 110 | # More documentation about the 'bans' section can be found here: 111 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 112 | [bans] 113 | # Lint level for when multiple versions of the same crate are detected 114 | multiple-versions = "warn" 115 | # Lint level for when a crate version requirement is `*` 116 | wildcards = "allow" 117 | # The graph highlighting used when creating dotgraphs for crates 118 | # with multiple versions 119 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 120 | # * simplest-path - The path to the version with the fewest edges is highlighted 121 | # * all - Both lowest-version and simplest-path are used 122 | highlight = "all" 123 | # The default lint level for `default` features for crates that are members of 124 | # the workspace that is being checked. This can be overridden by allowing/denying 125 | # `default` on a crate-by-crate basis if desired. 126 | workspace-default-features = "allow" 127 | # The default lint level for `default` features for external crates that are not 128 | # members of the workspace. This can be overridden by allowing/denying `default` 129 | # on a crate-by-crate basis if desired. 130 | external-default-features = "allow" 131 | # List of crates that are allowed. Use with care! 132 | allow = [ 133 | 134 | # { name = "ansi_term", version = "=0.11.0" }, 135 | ] 136 | # List of crates to deny 137 | deny = [ 138 | 139 | # Each entry the name of a crate and a version range. If version is 140 | # not specified, all versions will be matched. 141 | # { name = "ansi_term", version = "=0.11.0" }, 142 | # 143 | # Wrapper crates can optionally be specified to allow the crate when it 144 | # is a direct dependency of the otherwise banned crate 145 | # { name = "ansi_term", version = "=0.11.0", wrappers = [] }, 146 | ] 147 | 148 | # List of features to allow/deny 149 | # Each entry the name of a crate and a version range. If version is 150 | # not specified, all versions will be matched. 151 | # [[bans.features]] 152 | # name = "reqwest" 153 | # Features to not allow 154 | # deny = ["json"] 155 | # Features to allow 156 | # allow = [ 157 | # "rustls", 158 | # "__rustls", 159 | # "__tls", 160 | # "hyper-rustls", 161 | # "rustls", 162 | # "rustls-pemfile", 163 | # "rustls-tls-webpki-roots", 164 | # "tokio-rustls", 165 | # "webpki-roots", 166 | # ] 167 | # If true, the allowed features must exactly match the enabled feature set. If 168 | # this is set there is no point setting `deny` 169 | # exact = true 170 | 171 | # Certain crates/versions that will be skipped when doing duplicate detection. 172 | skip = [ 173 | 174 | # { name = "ansi_term", version = "=0.11.0" }, 175 | ] 176 | # Similarly to `skip` allows you to skip certain crates during duplicate 177 | # detection. Unlike skip, it also includes the entire tree of transitive 178 | # dependencies starting at the specified crate, up to a certain depth, which is 179 | # by default infinite. 180 | skip-tree = [ 181 | 182 | # { name = "ansi_term", version = "=0.11.0", depth = 20 }, 183 | ] 184 | 185 | # This section is considered when running `cargo deny check sources`. 186 | # More documentation about the 'sources' section can be found here: 187 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 188 | [sources] 189 | # Lint level for what to happen when a crate from a crate registry that is not 190 | # in the allow list is encountered 191 | unknown-registry = "warn" 192 | # Lint level for what to happen when a crate from a git repository that is not 193 | # in the allow list is encountered 194 | unknown-git = "warn" 195 | # List of URLs for allowed crate registries. Defaults to the crates.io index 196 | # if not specified. If it is specified but empty, no registries are allowed. 197 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 198 | # List of URLs for allowed Git repositories 199 | allow-git = [] 200 | 201 | [sources.allow-org] 202 | # 1 or more github.com organizations to allow git sources for 203 | # github = [""] 204 | # 1 or more gitlab.com organizations to allow git sources for 205 | # gitlab = [""] 206 | # 1 or more bitbucket.org organizations to allow git sources for 207 | # bitbucket = [""] 208 | 209 | [graph] 210 | # If 1 or more target triples (and optionally, target_features) are specified, 211 | # only the specified targets will be checked when running `cargo deny check`. 212 | # This means, if a particular package is only ever used as a target specific 213 | # dependency, such as, for example, the `nix` crate only being used via the 214 | # `target_family = "unix"` configuration, that only having windows targets in 215 | # this list would mean the nix crate, as well as any of its exclusive 216 | # dependencies not shared by any other crates, would be ignored, as the target 217 | # list here is effectively saying which targets you are building for. 218 | targets = [ 219 | 220 | # The triple can be any string, but only the target triples built in to 221 | # rustc (as of 1.40) can be checked against actual config expressions 222 | # { triple = "x86_64-unknown-linux-musl" }, 223 | # You can also specify which target_features you promise are enabled for a 224 | # particular target. target_features are currently not validated against 225 | # the actual valid features supported by the target architecture. 226 | # { triple = "wasm32-unknown-unknown", features = ["atomics"] }, 227 | ] 228 | # When creating the dependency graph used as the source of truth when checks are 229 | # executed, this field can be used to prune crates from the graph, removing them 230 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 231 | # is pruned from the graph, all of its dependencies will also be pruned unless 232 | # they are connected to another crate in the graph that hasn't been pruned, 233 | # so it should be used with care. The identifiers are [Package ID Specifications] 234 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 235 | # exclude = [] 236 | # If true, metadata will be collected with `--all-features`. Note that this can't 237 | # be toggled off if true, if you want to conditionally enable `--all-features` it 238 | # is recommended to pass `--all-features` on the cmd line instead 239 | all-features = false 240 | # If true, metadata will be collected with `--no-default-features`. The same 241 | # caveat with `all-features` applies 242 | no-default-features = false 243 | 244 | # If set, these feature will be enabled when collecting metadata. If `--features` 245 | # is specified on the cmd line they will take precedence over this option. 246 | # features = [] 247 | 248 | [output] 249 | # When outputting inclusion graphs in diagnostics that include features, this 250 | # option can be used to specify the depth at which feature edges will be added. 251 | # This option is included since the graphs can be quite large and the addition 252 | # of features from the crate(s) to all of the graph roots can be far too verbose. 253 | # This option can be overridden via `--feature-depth` on the cmd line 254 | feature-depth = 1 255 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{Map, Value}; 2 | 3 | /// Options for controlling JSON formatting when sorting 4 | #[derive(Debug, Clone)] 5 | pub struct SortOptions { 6 | /// Whether to pretty-print the output JSON 7 | pub pretty: bool, 8 | } 9 | 10 | impl Default for SortOptions { 11 | fn default() -> Self { 12 | Self { pretty: true } 13 | } 14 | } 15 | 16 | /// Sorts a package.json string with custom options 17 | pub fn sort_package_json_with_options( 18 | input: &str, 19 | options: &SortOptions, 20 | ) -> Result { 21 | let value: Value = serde_json::from_str(input)?; 22 | 23 | let sorted_value = 24 | if let Value::Object(obj) = value { Value::Object(sort_object_keys(obj)) } else { value }; 25 | 26 | let result = if options.pretty { 27 | let mut s = serde_json::to_string_pretty(&sorted_value)?; 28 | s.push('\n'); 29 | s 30 | } else { 31 | serde_json::to_string(&sorted_value)? 32 | }; 33 | 34 | Ok(result) 35 | } 36 | 37 | /// Sorts a package.json string with default options (pretty-printed) 38 | pub fn sort_package_json(input: &str) -> Result { 39 | sort_package_json_with_options(input, &SortOptions::default()) 40 | } 41 | 42 | /// Declares package.json field ordering with transformations. 43 | /// 44 | /// This macro generates a match statement that handles known package.json fields 45 | /// in a specific order using explicit indices. It supports optional transformation 46 | /// expressions for fields that need special processing. 47 | /// 48 | /// # Usage 49 | /// 50 | /// ```ignore 51 | /// declare_field_order!(key, value, known, non_private, private; [ 52 | /// 0 => "$schema", 53 | /// 1 => "name", 54 | /// 7 => "categories" => transform_array(&value, sort_array_unique), 55 | /// ]); 56 | /// ``` 57 | /// 58 | /// # Parameters 59 | /// 60 | /// - `key`: The field name identifier 61 | /// - `value`: The field value identifier 62 | /// - `known`: The vector to push known fields to 63 | /// - `non_private`: The vector to push non-private unknown fields to 64 | /// - `private`: The vector to push private (underscore-prefixed) fields to 65 | /// - Followed by an array of field declarations in the format: 66 | /// - `index => "field_name"` for fields without transformation 67 | /// - `index => "field_name" => transformation_expr` for fields with transformation 68 | macro_rules! declare_field_order { 69 | ( 70 | $key:ident, $value:ident, $known:ident, $non_private:ident, $private:ident; 71 | [ 72 | $( $idx:literal => $field_name:literal $( => $transform:expr )? ),* $(,)? 73 | ] 74 | ) => { 75 | { 76 | // Compile-time validation: ensure indices are literals 77 | $( let _ = $idx; )* 78 | 79 | // Generate the match statement 80 | match $key.as_str() { 81 | $( 82 | $field_name => { 83 | $known.push(( 84 | $idx, 85 | $key, 86 | declare_field_order!(@value $value $(, $transform)?) 87 | )); 88 | }, 89 | )* 90 | _ => { 91 | // Unknown field - check if private 92 | if $key.starts_with('_') { 93 | $private.push(($key, $value)); 94 | } else { 95 | $non_private.push(($key, $value)); 96 | } 97 | } 98 | } 99 | } 100 | }; 101 | 102 | // Helper: extract value without transformation 103 | (@value $value:ident) => { $value }; 104 | 105 | // Helper: extract value with transformation 106 | (@value $value:ident, $transform:expr) => { $transform }; 107 | } 108 | 109 | fn transform_value(value: Value, transform: F) -> Value 110 | where 111 | F: FnOnce(Map) -> Map, 112 | { 113 | match value { 114 | Value::Object(o) => Value::Object(transform(o)), 115 | _ => value, 116 | } 117 | } 118 | 119 | fn transform_array(value: Value, transform: F) -> Value 120 | where 121 | F: FnOnce(Vec) -> Vec, 122 | { 123 | match value { 124 | Value::Array(arr) => Value::Array(transform(arr)), 125 | _ => value, 126 | } 127 | } 128 | 129 | fn transform_with_key_order(value: Value, key_order: &[&str]) -> Value { 130 | transform_value(value, |o| sort_object_by_key_order(o, key_order)) 131 | } 132 | 133 | fn transform_people_array(value: Value) -> Value { 134 | transform_array(value, |mut arr| { 135 | // Transform objects in-place instead of map().collect() 136 | for v in &mut arr { 137 | if let Value::Object(obj) = std::mem::take(v) { 138 | *v = Value::Object(sort_people_object(obj)); 139 | } 140 | } 141 | arr 142 | }) 143 | } 144 | 145 | fn sort_object_alphabetically(obj: Map) -> Map { 146 | let mut entries: Vec<(String, Value)> = obj.into_iter().collect(); 147 | entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); 148 | entries.into_iter().collect() 149 | } 150 | 151 | fn sort_object_recursive(obj: Map) -> Map { 152 | let mut entries: Vec<(String, Value)> = obj.into_iter().collect(); 153 | entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); 154 | 155 | entries 156 | .into_iter() 157 | .map(|(key, value)| { 158 | let transformed_value = match value { 159 | Value::Object(nested) => Value::Object(sort_object_recursive(nested)), 160 | _ => value, 161 | }; 162 | (key, transformed_value) 163 | }) 164 | .collect() 165 | } 166 | 167 | fn sort_array_unique(mut arr: Vec) -> Vec { 168 | // Filter non-strings in-place (same behavior as filter_map) 169 | arr.retain(|v| v.is_string()); 170 | 171 | // Sort in-place by comparing string values (zero allocations) 172 | arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap())); 173 | 174 | // Remove consecutive duplicates in-place 175 | arr.dedup_by(|a, b| a.as_str() == b.as_str()); 176 | 177 | arr 178 | } 179 | 180 | fn sort_paths_naturally(mut arr: Vec) -> Vec { 181 | // Filter and deduplicate in-place 182 | arr.retain(|v| v.is_string()); 183 | arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap())); 184 | arr.dedup_by(|a, b| a.as_str() == b.as_str()); 185 | 186 | // Pre-compute depth and lowercase ONCE per string (not on every comparison) 187 | // Move Values from arr into tuples (no copying) 188 | let mut with_keys: Vec<(usize, String, Value)> = arr 189 | .into_iter() 190 | .map(|v| { 191 | let s = v.as_str().unwrap(); 192 | let depth = s.matches('/').count(); 193 | let lowercase = s.to_lowercase(); 194 | (depth, lowercase, v) 195 | }) 196 | .collect(); 197 | 198 | // Sort using pre-computed keys (zero allocations during comparison) 199 | with_keys.sort_unstable_by(|(depth_a, lower_a, _), (depth_b, lower_b, _)| { 200 | depth_a.cmp(depth_b).then_with(|| lower_a.cmp(lower_b)) 201 | }); 202 | 203 | // Extract Values (move out of tuples, no copying) 204 | with_keys.into_iter().map(|(_, _, v)| v).collect() 205 | } 206 | 207 | fn sort_object_by_key_order(mut obj: Map, key_order: &[&str]) -> Map { 208 | // Pre-allocate capacity to avoid reallocations 209 | let mut result = Map::with_capacity(obj.len()); 210 | 211 | // Add keys in specified order 212 | for &key in key_order { 213 | if let Some(value) = obj.remove(key) { 214 | result.insert(key.into(), value); 215 | } 216 | } 217 | 218 | // Add remaining keys alphabetically 219 | let mut remaining: Vec<(String, Value)> = obj.into_iter().collect(); 220 | remaining.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); 221 | 222 | for (key, value) in remaining { 223 | result.insert(key, value); 224 | } 225 | 226 | result 227 | } 228 | 229 | fn sort_people_object(obj: Map) -> Map { 230 | sort_object_by_key_order(obj, &["name", "email", "url"]) 231 | } 232 | 233 | fn sort_exports(obj: Map) -> Map { 234 | let mut paths = Vec::new(); 235 | let mut types_conds = Vec::new(); 236 | let mut other_conds = Vec::new(); 237 | let mut default_cond = None; 238 | 239 | for (key, value) in obj { 240 | if key.starts_with('.') { 241 | paths.push((key, value)); 242 | } else if key == "default" { 243 | default_cond = Some((key, value)); 244 | } else if key == "types" || key.starts_with("types@") { 245 | types_conds.push((key, value)); 246 | } else { 247 | other_conds.push((key, value)); 248 | } 249 | } 250 | 251 | let mut result = Map::new(); 252 | 253 | // Add in order: paths, types, others, default 254 | for (key, value) in paths { 255 | let transformed = match value { 256 | Value::Object(nested) => Value::Object(sort_exports(nested)), 257 | _ => value, 258 | }; 259 | result.insert(key, transformed); 260 | } 261 | 262 | for (key, value) in types_conds { 263 | let transformed = match value { 264 | Value::Object(nested) => Value::Object(sort_exports(nested)), 265 | _ => value, 266 | }; 267 | result.insert(key, transformed); 268 | } 269 | 270 | for (key, value) in other_conds { 271 | let transformed = match value { 272 | Value::Object(nested) => Value::Object(sort_exports(nested)), 273 | _ => value, 274 | }; 275 | result.insert(key, transformed); 276 | } 277 | 278 | if let Some((key, value)) = default_cond { 279 | let transformed = match value { 280 | Value::Object(nested) => Value::Object(sort_exports(nested)), 281 | _ => value, 282 | }; 283 | result.insert(key, transformed); 284 | } 285 | 286 | result 287 | } 288 | 289 | fn sort_object_keys(obj: Map) -> Map { 290 | // Storage for categorized keys with their values and ordering information 291 | let mut known: Vec<(usize, String, Value)> = Vec::new(); // (order_index, key, value) 292 | let mut non_private: Vec<(String, Value)> = Vec::new(); 293 | let mut private: Vec<(String, Value)> = Vec::new(); 294 | 295 | // Single pass through all keys using into_iter() 296 | for (key, value) in obj { 297 | declare_field_order!(key, value, known, non_private, private; [ 298 | // Core Package Metadata 299 | 0 => "$schema", 300 | 1 => "name", 301 | 2 => "displayName", 302 | 3 => "version", 303 | 4 => "stableVersion", 304 | 5 => "gitHead", 305 | 6 => "private", 306 | 7 => "description", 307 | 8 => "categories" => transform_array(value, sort_array_unique), 308 | 9 => "keywords" => transform_array(value, sort_array_unique), 309 | 10 => "homepage", 310 | 11 => "bugs" => transform_with_key_order(value, &["url", "email"]), 311 | // License & People 312 | 12 => "license", 313 | 13 => "author" => transform_value(value, sort_people_object), 314 | 14 => "maintainers" => transform_people_array(value), 315 | 15 => "contributors" => transform_people_array(value), 316 | // Repository & Funding 317 | 16 => "repository" => transform_with_key_order(value, &["type", "url"]), 318 | 17 => "funding" => transform_with_key_order(value, &["type", "url"]), 319 | 18 => "donate" => transform_with_key_order(value, &["type", "url"]), 320 | 19 => "sponsor" => transform_with_key_order(value, &["type", "url"]), 321 | 20 => "qna", 322 | 21 => "publisher", 323 | // Package Content & Distribution 324 | 22 => "man", 325 | 23 => "style", 326 | 24 => "example", 327 | 25 => "examplestyle", 328 | 26 => "assets", 329 | 27 => "bin" => transform_value(value, sort_object_alphabetically), 330 | 28 => "source", 331 | 29 => "directories" => transform_with_key_order(value, &["lib", "bin", "man", "doc", "example", "test"]), 332 | 30 => "workspaces", 333 | 31 => "binary" => transform_with_key_order(value, &["module_name", "module_path", "remote_path", "package_name", "host"]), 334 | 32 => "files" => transform_array(value, sort_paths_naturally), 335 | 33 => "os", 336 | 34 => "cpu", 337 | 35 => "libc" => transform_array(value, sort_array_unique), 338 | // Package Entry Points 339 | 36 => "type", 340 | 37 => "sideEffects", 341 | 38 => "main", 342 | 39 => "module", 343 | 40 => "browser", 344 | 41 => "types", 345 | 42 => "typings", 346 | 43 => "typesVersions", 347 | 44 => "typeScriptVersion", 348 | 45 => "typesPublisherContentHash", 349 | 46 => "react-native", 350 | 47 => "svelte", 351 | 48 => "unpkg", 352 | 49 => "jsdelivr", 353 | 50 => "jsnext:main", 354 | 51 => "umd", 355 | 52 => "umd:main", 356 | 53 => "es5", 357 | 54 => "esm5", 358 | 55 => "fesm5", 359 | 56 => "es2015", 360 | 57 => "esm2015", 361 | 58 => "fesm2015", 362 | 59 => "es2020", 363 | 60 => "esm2020", 364 | 61 => "fesm2020", 365 | 62 => "esnext", 366 | 63 => "imports", 367 | 64 => "exports" => transform_value(value, sort_exports), 368 | 65 => "publishConfig" => transform_value(value, sort_object_alphabetically), 369 | // Scripts 370 | 66 => "scripts", 371 | 67 => "betterScripts", 372 | // Dependencies 373 | 68 => "dependencies" => transform_value(value, sort_object_alphabetically), 374 | 69 => "devDependencies" => transform_value(value, sort_object_alphabetically), 375 | 70 => "dependenciesMeta", 376 | 71 => "peerDependencies" => transform_value(value, sort_object_alphabetically), 377 | 72 => "peerDependenciesMeta", 378 | 73 => "optionalDependencies" => transform_value(value, sort_object_alphabetically), 379 | 74 => "bundledDependencies" => transform_array(value, sort_array_unique), 380 | 75 => "bundleDependencies" => transform_array(value, sort_array_unique), 381 | 76 => "resolutions" => transform_value(value, sort_object_alphabetically), 382 | 77 => "overrides" => transform_value(value, sort_object_alphabetically), 383 | // Git Hooks & Commit Tools 384 | 78 => "husky" => transform_value(value, sort_object_recursive), 385 | 79 => "simple-git-hooks", 386 | 80 => "pre-commit", 387 | 81 => "lint-staged", 388 | 82 => "nano-staged", 389 | 83 => "commitlint" => transform_value(value, sort_object_recursive), 390 | // VSCode Extension Specific 391 | 84 => "l10n", 392 | 85 => "contributes", 393 | 86 => "activationEvents" => transform_array(value, sort_array_unique), 394 | 87 => "extensionPack" => transform_array(value, sort_array_unique), 395 | 88 => "extensionDependencies" => transform_array(value, sort_array_unique), 396 | 89 => "extensionKind" => transform_array(value, sort_array_unique), 397 | 90 => "icon", 398 | 91 => "badges", 399 | 92 => "galleryBanner", 400 | 93 => "preview", 401 | 94 => "markdown", 402 | // Build & Tool Configuration 403 | 95 => "napi" => transform_value(value, sort_object_alphabetically), 404 | 96 => "flat", 405 | 97 => "config" => transform_value(value, sort_object_alphabetically), 406 | 98 => "nodemonConfig" => transform_value(value, sort_object_recursive), 407 | 99 => "browserify" => transform_value(value, sort_object_recursive), 408 | 100 => "babel" => transform_value(value, sort_object_recursive), 409 | 101 => "browserslist", 410 | 102 => "xo" => transform_value(value, sort_object_recursive), 411 | 103 => "prettier" => transform_value(value, sort_object_recursive), 412 | 104 => "eslintConfig" => transform_value(value, sort_object_recursive), 413 | 105 => "eslintIgnore", 414 | 106 => "standard" => transform_value(value, sort_object_recursive), 415 | 107 => "npmpkgjsonlint", 416 | 108 => "npmPackageJsonLintConfig", 417 | 109 => "npmpackagejsonlint", 418 | 110 => "release", 419 | 111 => "auto-changelog" => transform_value(value, sort_object_recursive), 420 | 112 => "remarkConfig" => transform_value(value, sort_object_recursive), 421 | 113 => "stylelint" => transform_value(value, sort_object_recursive), 422 | 114 => "typescript" => transform_value(value, sort_object_recursive), 423 | 115 => "typedoc" => transform_value(value, sort_object_recursive), 424 | 116 => "tshy" => transform_value(value, sort_object_recursive), 425 | 117 => "tsdown" => transform_value(value, sort_object_recursive), 426 | 118 => "size-limit" => transform_array(value, sort_array_unique), 427 | // Testing 428 | 119 => "ava" => transform_value(value, sort_object_recursive), 429 | 120 => "jest" => transform_value(value, sort_object_recursive), 430 | 121 => "jest-junit", 431 | 122 => "jest-stare", 432 | 123 => "mocha" => transform_value(value, sort_object_recursive), 433 | 124 => "nyc" => transform_value(value, sort_object_recursive), 434 | 125 => "c8" => transform_value(value, sort_object_recursive), 435 | 126 => "tap", 436 | 127 => "tsd" => transform_value(value, sort_object_recursive), 437 | 128 => "typeCoverage" => transform_value(value, sort_object_recursive), 438 | 129 => "oclif" => transform_value(value, sort_object_recursive), 439 | // Runtime & Package Manager 440 | 130 => "languageName", 441 | 131 => "preferGlobal", 442 | 132 => "devEngines" => transform_value(value, sort_object_alphabetically), 443 | 133 => "engines" => transform_value(value, sort_object_alphabetically), 444 | 134 => "engineStrict", 445 | 135 => "volta" => transform_value(value, sort_object_recursive), 446 | 136 => "packageManager", 447 | 137 => "pnpm", 448 | ]); 449 | } 450 | 451 | // Sort each category (using unstable sort for better performance) 452 | known.sort_unstable_by_key(|(index, _, _)| *index); 453 | non_private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); 454 | private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); 455 | 456 | // Build result map 457 | let mut result = Map::new(); 458 | 459 | // Insert known fields (already transformed) 460 | for (_index, key, value) in known { 461 | result.insert(key, value); 462 | } 463 | 464 | // Insert non-private unknown fields 465 | for (key, value) in non_private { 466 | result.insert(key, value); 467 | } 468 | 469 | // Insert private fields 470 | for (key, value) in private { 471 | result.insert(key, value); 472 | } 473 | 474 | result 475 | } 476 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.2.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "dc43e46599f3d77fcf2f2ca89e4d962910b0c19c44e7b58679cbbdfd1820a662" 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.100" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 25 | 26 | [[package]] 27 | name = "approx" 28 | version = "0.5.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 31 | dependencies = [ 32 | "num-traits", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.5.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 40 | 41 | [[package]] 42 | name = "bincode" 43 | version = "1.3.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 46 | dependencies = [ 47 | "serde", 48 | ] 49 | 50 | [[package]] 51 | name = "bitflags" 52 | version = "2.10.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 55 | 56 | [[package]] 57 | name = "bpaf" 58 | version = "0.9.20" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31" 61 | 62 | [[package]] 63 | name = "bstr" 64 | version = "1.12.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" 67 | dependencies = [ 68 | "memchr", 69 | "serde", 70 | ] 71 | 72 | [[package]] 73 | name = "bumpalo" 74 | version = "3.19.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 77 | 78 | [[package]] 79 | name = "cast" 80 | version = "0.3.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "1.0.4" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 89 | 90 | [[package]] 91 | name = "cfg_aliases" 92 | version = "0.2.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 95 | 96 | [[package]] 97 | name = "ciborium" 98 | version = "0.2.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 101 | dependencies = [ 102 | "ciborium-io", 103 | "ciborium-ll", 104 | "serde", 105 | ] 106 | 107 | [[package]] 108 | name = "ciborium-io" 109 | version = "0.2.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 112 | 113 | [[package]] 114 | name = "ciborium-ll" 115 | version = "0.2.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 118 | dependencies = [ 119 | "ciborium-io", 120 | "half", 121 | ] 122 | 123 | [[package]] 124 | name = "codspeed" 125 | version = "3.0.5" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117" 128 | dependencies = [ 129 | "anyhow", 130 | "bincode", 131 | "colored 2.2.0", 132 | "glob", 133 | "libc", 134 | "nix", 135 | "serde", 136 | "serde_json", 137 | "statrs", 138 | "uuid", 139 | ] 140 | 141 | [[package]] 142 | name = "colored" 143 | version = "2.2.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" 146 | dependencies = [ 147 | "lazy_static", 148 | "windows-sys", 149 | ] 150 | 151 | [[package]] 152 | name = "colored" 153 | version = "3.0.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 156 | dependencies = [ 157 | "windows-sys", 158 | ] 159 | 160 | [[package]] 161 | name = "console" 162 | version = "0.15.11" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 165 | dependencies = [ 166 | "encode_unicode", 167 | "libc", 168 | "once_cell", 169 | "windows-sys", 170 | ] 171 | 172 | [[package]] 173 | name = "criterion2" 174 | version = "3.0.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "77cd1059d67baa066c334993d8d6e757ad257d21030db6a9a945dddbb559d4fe" 177 | dependencies = [ 178 | "anes", 179 | "bpaf", 180 | "cast", 181 | "ciborium", 182 | "codspeed", 183 | "colored 3.0.0", 184 | "num-traits", 185 | "oorandom", 186 | "serde", 187 | "serde_json", 188 | "walkdir", 189 | ] 190 | 191 | [[package]] 192 | name = "crossbeam-deque" 193 | version = "0.8.6" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 196 | dependencies = [ 197 | "crossbeam-epoch", 198 | "crossbeam-utils", 199 | ] 200 | 201 | [[package]] 202 | name = "crossbeam-epoch" 203 | version = "0.9.18" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 206 | dependencies = [ 207 | "crossbeam-utils", 208 | ] 209 | 210 | [[package]] 211 | name = "crossbeam-utils" 212 | version = "0.8.21" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 215 | 216 | [[package]] 217 | name = "crunchy" 218 | version = "0.2.4" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 221 | 222 | [[package]] 223 | name = "encode_unicode" 224 | version = "1.0.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 227 | 228 | [[package]] 229 | name = "equivalent" 230 | version = "1.0.2" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 233 | 234 | [[package]] 235 | name = "getrandom" 236 | version = "0.3.4" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 239 | dependencies = [ 240 | "cfg-if", 241 | "libc", 242 | "r-efi", 243 | "wasip2", 244 | ] 245 | 246 | [[package]] 247 | name = "glob" 248 | version = "0.3.3" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 251 | 252 | [[package]] 253 | name = "globset" 254 | version = "0.4.18" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" 257 | dependencies = [ 258 | "aho-corasick", 259 | "bstr", 260 | "log", 261 | "regex-automata", 262 | "regex-syntax", 263 | ] 264 | 265 | [[package]] 266 | name = "half" 267 | version = "2.7.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 270 | dependencies = [ 271 | "cfg-if", 272 | "crunchy", 273 | "zerocopy", 274 | ] 275 | 276 | [[package]] 277 | name = "hashbrown" 278 | version = "0.16.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 281 | 282 | [[package]] 283 | name = "ignore" 284 | version = "0.4.25" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" 287 | dependencies = [ 288 | "crossbeam-deque", 289 | "globset", 290 | "log", 291 | "memchr", 292 | "regex-automata", 293 | "same-file", 294 | "walkdir", 295 | "winapi-util", 296 | ] 297 | 298 | [[package]] 299 | name = "indexmap" 300 | version = "2.12.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" 303 | dependencies = [ 304 | "equivalent", 305 | "hashbrown", 306 | ] 307 | 308 | [[package]] 309 | name = "insta" 310 | version = "1.44.3" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" 313 | dependencies = [ 314 | "console", 315 | "once_cell", 316 | "similar", 317 | ] 318 | 319 | [[package]] 320 | name = "itoa" 321 | version = "1.0.15" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 324 | 325 | [[package]] 326 | name = "js-sys" 327 | version = "0.3.83" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 330 | dependencies = [ 331 | "once_cell", 332 | "wasm-bindgen", 333 | ] 334 | 335 | [[package]] 336 | name = "lazy_static" 337 | version = "1.5.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 340 | 341 | [[package]] 342 | name = "libc" 343 | version = "0.2.178" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 346 | 347 | [[package]] 348 | name = "log" 349 | version = "0.4.29" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 352 | 353 | [[package]] 354 | name = "memchr" 355 | version = "2.7.6" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 358 | 359 | [[package]] 360 | name = "nix" 361 | version = "0.29.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 364 | dependencies = [ 365 | "bitflags", 366 | "cfg-if", 367 | "cfg_aliases", 368 | "libc", 369 | ] 370 | 371 | [[package]] 372 | name = "num-traits" 373 | version = "0.2.19" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 376 | dependencies = [ 377 | "autocfg", 378 | ] 379 | 380 | [[package]] 381 | name = "once_cell" 382 | version = "1.21.3" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 385 | 386 | [[package]] 387 | name = "oorandom" 388 | version = "11.1.5" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" 391 | 392 | [[package]] 393 | name = "proc-macro2" 394 | version = "1.0.103" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 397 | dependencies = [ 398 | "unicode-ident", 399 | ] 400 | 401 | [[package]] 402 | name = "quote" 403 | version = "1.0.42" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 406 | dependencies = [ 407 | "proc-macro2", 408 | ] 409 | 410 | [[package]] 411 | name = "r-efi" 412 | version = "5.3.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 415 | 416 | [[package]] 417 | name = "regex-automata" 418 | version = "0.4.13" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 421 | dependencies = [ 422 | "aho-corasick", 423 | "memchr", 424 | "regex-syntax", 425 | ] 426 | 427 | [[package]] 428 | name = "regex-syntax" 429 | version = "0.8.8" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 432 | 433 | [[package]] 434 | name = "rustversion" 435 | version = "1.0.22" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 438 | 439 | [[package]] 440 | name = "ryu" 441 | version = "1.0.20" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 444 | 445 | [[package]] 446 | name = "same-file" 447 | version = "1.0.6" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 450 | dependencies = [ 451 | "winapi-util", 452 | ] 453 | 454 | [[package]] 455 | name = "serde" 456 | version = "1.0.228" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 459 | dependencies = [ 460 | "serde_core", 461 | "serde_derive", 462 | ] 463 | 464 | [[package]] 465 | name = "serde_core" 466 | version = "1.0.228" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 469 | dependencies = [ 470 | "serde_derive", 471 | ] 472 | 473 | [[package]] 474 | name = "serde_derive" 475 | version = "1.0.228" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 478 | dependencies = [ 479 | "proc-macro2", 480 | "quote", 481 | "syn", 482 | ] 483 | 484 | [[package]] 485 | name = "serde_json" 486 | version = "1.0.145" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 489 | dependencies = [ 490 | "indexmap", 491 | "itoa", 492 | "memchr", 493 | "ryu", 494 | "serde", 495 | "serde_core", 496 | ] 497 | 498 | [[package]] 499 | name = "similar" 500 | version = "2.7.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 503 | 504 | [[package]] 505 | name = "sort-package-json" 506 | version = "0.0.5" 507 | dependencies = [ 508 | "criterion2", 509 | "ignore", 510 | "insta", 511 | "serde_json", 512 | ] 513 | 514 | [[package]] 515 | name = "statrs" 516 | version = "0.18.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" 519 | dependencies = [ 520 | "approx", 521 | "num-traits", 522 | ] 523 | 524 | [[package]] 525 | name = "syn" 526 | version = "2.0.111" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "unicode-ident", 533 | ] 534 | 535 | [[package]] 536 | name = "unicode-ident" 537 | version = "1.0.22" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 540 | 541 | [[package]] 542 | name = "uuid" 543 | version = "1.19.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" 546 | dependencies = [ 547 | "getrandom", 548 | "js-sys", 549 | "wasm-bindgen", 550 | ] 551 | 552 | [[package]] 553 | name = "walkdir" 554 | version = "2.5.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 557 | dependencies = [ 558 | "same-file", 559 | "winapi-util", 560 | ] 561 | 562 | [[package]] 563 | name = "wasip2" 564 | version = "1.0.1+wasi-0.2.4" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 567 | dependencies = [ 568 | "wit-bindgen", 569 | ] 570 | 571 | [[package]] 572 | name = "wasm-bindgen" 573 | version = "0.2.106" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 576 | dependencies = [ 577 | "cfg-if", 578 | "once_cell", 579 | "rustversion", 580 | "wasm-bindgen-macro", 581 | "wasm-bindgen-shared", 582 | ] 583 | 584 | [[package]] 585 | name = "wasm-bindgen-macro" 586 | version = "0.2.106" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 589 | dependencies = [ 590 | "quote", 591 | "wasm-bindgen-macro-support", 592 | ] 593 | 594 | [[package]] 595 | name = "wasm-bindgen-macro-support" 596 | version = "0.2.106" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 599 | dependencies = [ 600 | "bumpalo", 601 | "proc-macro2", 602 | "quote", 603 | "syn", 604 | "wasm-bindgen-shared", 605 | ] 606 | 607 | [[package]] 608 | name = "wasm-bindgen-shared" 609 | version = "0.2.106" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 612 | dependencies = [ 613 | "unicode-ident", 614 | ] 615 | 616 | [[package]] 617 | name = "winapi-util" 618 | version = "0.1.11" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 621 | dependencies = [ 622 | "windows-sys", 623 | ] 624 | 625 | [[package]] 626 | name = "windows-sys" 627 | version = "0.59.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 630 | dependencies = [ 631 | "windows-targets", 632 | ] 633 | 634 | [[package]] 635 | name = "windows-targets" 636 | version = "0.52.6" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 639 | dependencies = [ 640 | "windows_aarch64_gnullvm", 641 | "windows_aarch64_msvc", 642 | "windows_i686_gnu", 643 | "windows_i686_gnullvm", 644 | "windows_i686_msvc", 645 | "windows_x86_64_gnu", 646 | "windows_x86_64_gnullvm", 647 | "windows_x86_64_msvc", 648 | ] 649 | 650 | [[package]] 651 | name = "windows_aarch64_gnullvm" 652 | version = "0.52.6" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 655 | 656 | [[package]] 657 | name = "windows_aarch64_msvc" 658 | version = "0.52.6" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 661 | 662 | [[package]] 663 | name = "windows_i686_gnu" 664 | version = "0.52.6" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 667 | 668 | [[package]] 669 | name = "windows_i686_gnullvm" 670 | version = "0.52.6" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 673 | 674 | [[package]] 675 | name = "windows_i686_msvc" 676 | version = "0.52.6" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 679 | 680 | [[package]] 681 | name = "windows_x86_64_gnu" 682 | version = "0.52.6" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 685 | 686 | [[package]] 687 | name = "windows_x86_64_gnullvm" 688 | version = "0.52.6" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 691 | 692 | [[package]] 693 | name = "windows_x86_64_msvc" 694 | version = "0.52.6" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 697 | 698 | [[package]] 699 | name = "wit-bindgen" 700 | version = "0.46.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 703 | 704 | [[package]] 705 | name = "zerocopy" 706 | version = "0.8.31" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" 709 | dependencies = [ 710 | "zerocopy-derive", 711 | ] 712 | 713 | [[package]] 714 | name = "zerocopy-derive" 715 | version = "0.8.31" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" 718 | dependencies = [ 719 | "proc-macro2", 720 | "quote", 721 | "syn", 722 | ] 723 | --------------------------------------------------------------------------------