├── .gitignore ├── .rustfmt.toml ├── rust-toolchain.toml ├── README.md ├── src ├── sync.rs └── lib.rs ├── Cargo.toml ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── LICENSE ├── clippy.toml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `deno_package_json` 2 | 3 | **ARCHIVED:** This repo was moved into https://github.com/denoland/deno 4 | 5 | The package.json implementation used in the Deno CLI. 6 | 7 | ## Versioning Strategy 8 | 9 | This crate does not follow semver so make sure to pin it to a patch version. 10 | Instead a versioning strategy that optimizes for more efficient maintenance is 11 | used: 12 | 13 | - Does [deno_config](https://github.com/denoland/deno_config) still compile? 14 | - If yes, it's a patch release. 15 | - If no, it's a minor release. 16 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | pub use inner::*; 4 | 5 | #[cfg(feature = "sync")] 6 | mod inner { 7 | #![allow(clippy::disallowed_types)] 8 | pub use std::sync::Arc as MaybeArc; 9 | pub use std::sync::OnceLock as MaybeOnceLock; 10 | } 11 | 12 | #[cfg(not(feature = "sync"))] 13 | mod inner { 14 | pub use std::cell::OnceCell as MaybeOnceLock; 15 | pub use std::rc::Rc as MaybeArc; 16 | } 17 | 18 | // ok for constructing 19 | #[allow(clippy::disallowed_types)] 20 | pub fn new_rc(value: T) -> MaybeArc { 21 | MaybeArc::new(value) 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deno_package_json" 3 | description = "package.json implementation for the Deno CLI" 4 | version = "0.9.0" 5 | edition = "2021" 6 | authors = ["the Deno authors"] 7 | license = "MIT" 8 | repository = "https://github.com/denoland/deno_package_json" 9 | 10 | [features] 11 | sync = [] 12 | 13 | [dependencies] 14 | indexmap = { version = "2", features = ["serde"] } 15 | serde = { version = "1.0.149", features = ["derive"] } 16 | serde_json = "1.0.85" 17 | url = { version = "2.5.1" } 18 | thiserror = "2" 19 | deno_semver = "0.8.0" 20 | deno_path_util = "0.4.0" 21 | deno_error = { version = "0.6.0", features = ["serde", "serde_json"] } 22 | boxed_error = "0.2.3" 23 | sys_traits = "0.1.0" 24 | 25 | [dev-dependencies] 26 | pretty_assertions = "1.4.0" 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseKind: 7 | description: "Kind of release" 8 | default: "minor" 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | required: true 14 | 15 | jobs: 16 | rust: 17 | name: release 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v4 24 | with: 25 | token: ${{ secrets.DENOBOT_PAT }} 26 | 27 | - uses: denoland/setup-deno@v1 28 | - uses: dsherret/rust-toolchain-file@v1 29 | 30 | - name: Tag and release 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.DENOBOT_PAT }} 33 | GH_WORKFLOW_ACTOR: ${{ github.actor }} 34 | run: | 35 | git config user.email "denobot@users.noreply.github.com" 36 | git config user.name "denobot" 37 | deno run -A https://raw.githubusercontent.com/denoland/automation/0.15.0/tasks/publish_release.ts --${{github.event.inputs.releaseKind}} 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2018-2024 the Deno authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | rust: 7 | name: deno_package_json-${{ matrix.os }} 8 | if: | 9 | (github.event_name == 'push' || !startsWith(github.event.pull_request.head.label, 'denoland:')) 10 | && github.ref_name != 'deno_package_json' 11 | && !startsWith(github.ref, 'refs/tags/deno/') 12 | runs-on: ${{ matrix.os }} 13 | timeout-minutes: 15 14 | strategy: 15 | matrix: 16 | os: [macOS-latest, ubuntu-latest, windows-latest] 17 | 18 | env: 19 | CARGO_INCREMENTAL: 0 20 | GH_ACTIONS: 1 21 | RUST_BACKTRACE: full 22 | RUSTFLAGS: -D warnings 23 | 24 | steps: 25 | - name: Clone repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Install Rust 29 | uses: dsherret/rust-toolchain-file@v1 30 | 31 | - uses: Swatinem/rust-cache@v2 32 | with: 33 | save-if: ${{ github.ref == 'refs/heads/main' }} 34 | 35 | - name: Format 36 | if: contains(matrix.os, 'ubuntu') 37 | run: | 38 | cargo fmt -- --check 39 | 40 | - name: Cargo test 41 | run: cargo test --locked --release --all-features --bins --tests --examples 42 | 43 | - name: Lint 44 | if: contains(matrix.os, 'ubuntu') 45 | run: | 46 | cargo clippy --locked --all-features --all-targets -- -D clippy::all 47 | 48 | - name: Cargo publish 49 | if: | 50 | contains(matrix.os, 'ubuntu') && 51 | github.repository == 'denoland/deno_package_json' && 52 | startsWith(github.ref, 'refs/tags/') 53 | env: 54 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 55 | run: cargo publish 56 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-methods = [ 2 | { path = "std::env::current_dir", reason = "File system operations should be done using the sys_traits crate" }, 3 | { path = "std::path::Path::exists", reason = "File system operations should be done using the sys_traits crate" }, 4 | { path = "std::path::Path::canonicalize", reason = "File system operations should be done using the sys_traits crate" }, 5 | { path = "std::path::Path::is_dir", reason = "File system operations should be done using the sys_traits crate" }, 6 | { path = "std::path::Path::is_file", reason = "File system operations should be done using the sys_traits crate" }, 7 | { path = "std::path::Path::is_symlink", reason = "File system operations should be done using the sys_traits crate" }, 8 | { path = "std::path::Path::metadata", reason = "File system operations should be done using the sys_traits crate" }, 9 | { path = "std::path::Path::read_dir", reason = "File system operations should be done using the sys_traits crate" }, 10 | { path = "std::path::Path::read_link", reason = "File system operations should be done using the sys_traits crate" }, 11 | { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using the sys_traits crate" }, 12 | { path = "std::path::Path::try_exists", reason = "File system operations should be done using the sys_traits crate" }, 13 | { path = "std::path::PathBuf::exists", reason = "File system operations should be done using the sys_traits crate" }, 14 | { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using the sys_traits crate" }, 15 | { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using the sys_traits crate" }, 16 | { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using the sys_traits crate" }, 17 | { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using the sys_traits crate" }, 18 | { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using the sys_traits crate" }, 19 | { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using the sys_traits crate" }, 20 | { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using the sys_traits crate" }, 21 | { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using the sys_traits crate" }, 22 | { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using the sys_traits crate" }, 23 | { path = "std::fs::canonicalize", reason = "File system operations should be done using the sys_traits crate" }, 24 | { path = "std::fs::copy", reason = "File system operations should be done using the sys_traits crate" }, 25 | { path = "std::fs::create_dir", reason = "File system operations should be done using the sys_traits crate" }, 26 | { path = "std::fs::create_dir_all", reason = "File system operations should be done using the sys_traits crate" }, 27 | { path = "std::fs::hard_link", reason = "File system operations should be done using the sys_traits crate" }, 28 | { path = "std::fs::metadata", reason = "File system operations should be done using the sys_traits crate" }, 29 | { path = "std::fs::read", reason = "File system operations should be done using the sys_traits crate" }, 30 | { path = "std::fs::read_dir", reason = "File system operations should be done using the sys_traits crate" }, 31 | { path = "std::fs::read_link", reason = "File system operations should be done using the sys_traits crate" }, 32 | { path = "std::fs::read_to_string", reason = "File system operations should be done using the sys_traits crate" }, 33 | { path = "std::fs::remove_dir", reason = "File system operations should be done using the sys_traits crate" }, 34 | { path = "std::fs::remove_dir_all", reason = "File system operations should be done using the sys_traits crate" }, 35 | { path = "std::fs::remove_file", reason = "File system operations should be done using the sys_traits crate" }, 36 | { path = "std::fs::rename", reason = "File system operations should be done using the sys_traits crate" }, 37 | { path = "std::fs::set_permissions", reason = "File system operations should be done using the sys_traits crate" }, 38 | { path = "std::fs::symlink_metadata", reason = "File system operations should be done using the sys_traits crate" }, 39 | { path = "std::fs::write", reason = "File system operations should be done using the sys_traits crate" } 40 | ] 41 | disallowed-types = [ 42 | { path = "std::sync::Arc", reason = "use crate::sync::MaybeArc instead" }, 43 | { path = "std::sync::OnceLock", reason = "use crate::sync::MaybeOnceLock instead" }, 44 | ] 45 | -------------------------------------------------------------------------------- /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 = "boxed_error" 7 | version = "0.2.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "17d4f95e880cfd28c4ca5a006cf7f6af52b4bcb7b5866f573b2faa126fb7affb" 10 | dependencies = [ 11 | "quote", 12 | "syn", 13 | ] 14 | 15 | [[package]] 16 | name = "capacity_builder" 17 | version = "0.5.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "8f2d24a6dcf0cd402a21b65d35340f3a49ff3475dc5fdac91d22d2733e6641c6" 20 | dependencies = [ 21 | "capacity_builder_macros", 22 | "ecow", 23 | "hipstr", 24 | "itoa", 25 | ] 26 | 27 | [[package]] 28 | name = "capacity_builder_macros" 29 | version = "0.3.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" 32 | dependencies = [ 33 | "quote", 34 | "syn", 35 | ] 36 | 37 | [[package]] 38 | name = "deno_error" 39 | version = "0.6.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "43674d0eb32a7457e0911164fee92b45320e02a52c0c530e6cde47163cdf6dc8" 42 | dependencies = [ 43 | "deno_error_macro", 44 | "libc", 45 | "serde", 46 | "serde_json", 47 | "url", 48 | ] 49 | 50 | [[package]] 51 | name = "deno_error_macro" 52 | version = "0.6.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "06d727b287cd43aa5120cbd00d1ca5be5e829d502bbb52d4bbb144a5a057372e" 55 | dependencies = [ 56 | "proc-macro2", 57 | "quote", 58 | "syn", 59 | ] 60 | 61 | [[package]] 62 | name = "deno_package_json" 63 | version = "0.9.0" 64 | dependencies = [ 65 | "boxed_error", 66 | "deno_error", 67 | "deno_path_util", 68 | "deno_semver", 69 | "indexmap", 70 | "pretty_assertions", 71 | "serde", 72 | "serde_json", 73 | "sys_traits", 74 | "thiserror", 75 | "url", 76 | ] 77 | 78 | [[package]] 79 | name = "deno_path_util" 80 | version = "0.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "516f813389095889776b81cc9108ff6f336fd9409b4b12fc0138aea23d2708e1" 83 | dependencies = [ 84 | "deno_error", 85 | "percent-encoding", 86 | "sys_traits", 87 | "thiserror", 88 | "url", 89 | ] 90 | 91 | [[package]] 92 | name = "deno_semver" 93 | version = "0.8.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "f2d807160e754edb1989b4a19cac1ac5299065a7a89ff98682a2366cbaa25795" 96 | dependencies = [ 97 | "capacity_builder", 98 | "deno_error", 99 | "ecow", 100 | "hipstr", 101 | "monch", 102 | "once_cell", 103 | "serde", 104 | "thiserror", 105 | "url", 106 | ] 107 | 108 | [[package]] 109 | name = "diff" 110 | version = "0.1.13" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 113 | 114 | [[package]] 115 | name = "displaydoc" 116 | version = "0.2.5" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 119 | dependencies = [ 120 | "proc-macro2", 121 | "quote", 122 | "syn", 123 | ] 124 | 125 | [[package]] 126 | name = "ecow" 127 | version = "0.2.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "e42fc0a93992b20c58b99e59d61eaf1635a25bfbe49e4275c34ba0aee98119ba" 130 | dependencies = [ 131 | "serde", 132 | ] 133 | 134 | [[package]] 135 | name = "equivalent" 136 | version = "1.0.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 139 | 140 | [[package]] 141 | name = "form_urlencoded" 142 | version = "1.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 145 | dependencies = [ 146 | "percent-encoding", 147 | ] 148 | 149 | [[package]] 150 | name = "hashbrown" 151 | version = "0.14.5" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 154 | 155 | [[package]] 156 | name = "hipstr" 157 | version = "0.6.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "97971ffc85d4c98de12e2608e992a43f5294ebb625fdb045b27c731b64c4c6d6" 160 | dependencies = [ 161 | "serde", 162 | "serde_bytes", 163 | "sptr", 164 | ] 165 | 166 | [[package]] 167 | name = "icu_collections" 168 | version = "1.5.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 171 | dependencies = [ 172 | "displaydoc", 173 | "yoke", 174 | "zerofrom", 175 | "zerovec", 176 | ] 177 | 178 | [[package]] 179 | name = "icu_locid" 180 | version = "1.5.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 183 | dependencies = [ 184 | "displaydoc", 185 | "litemap", 186 | "tinystr", 187 | "writeable", 188 | "zerovec", 189 | ] 190 | 191 | [[package]] 192 | name = "icu_locid_transform" 193 | version = "1.5.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 196 | dependencies = [ 197 | "displaydoc", 198 | "icu_locid", 199 | "icu_locid_transform_data", 200 | "icu_provider", 201 | "tinystr", 202 | "zerovec", 203 | ] 204 | 205 | [[package]] 206 | name = "icu_locid_transform_data" 207 | version = "1.5.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 210 | 211 | [[package]] 212 | name = "icu_normalizer" 213 | version = "1.5.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 216 | dependencies = [ 217 | "displaydoc", 218 | "icu_collections", 219 | "icu_normalizer_data", 220 | "icu_properties", 221 | "icu_provider", 222 | "smallvec", 223 | "utf16_iter", 224 | "utf8_iter", 225 | "write16", 226 | "zerovec", 227 | ] 228 | 229 | [[package]] 230 | name = "icu_normalizer_data" 231 | version = "1.5.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 234 | 235 | [[package]] 236 | name = "icu_properties" 237 | version = "1.5.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 240 | dependencies = [ 241 | "displaydoc", 242 | "icu_collections", 243 | "icu_locid_transform", 244 | "icu_properties_data", 245 | "icu_provider", 246 | "tinystr", 247 | "zerovec", 248 | ] 249 | 250 | [[package]] 251 | name = "icu_properties_data" 252 | version = "1.5.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 255 | 256 | [[package]] 257 | name = "icu_provider" 258 | version = "1.5.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 261 | dependencies = [ 262 | "displaydoc", 263 | "icu_locid", 264 | "icu_provider_macros", 265 | "stable_deref_trait", 266 | "tinystr", 267 | "writeable", 268 | "yoke", 269 | "zerofrom", 270 | "zerovec", 271 | ] 272 | 273 | [[package]] 274 | name = "icu_provider_macros" 275 | version = "1.5.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 278 | dependencies = [ 279 | "proc-macro2", 280 | "quote", 281 | "syn", 282 | ] 283 | 284 | [[package]] 285 | name = "idna" 286 | version = "1.0.3" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 289 | dependencies = [ 290 | "idna_adapter", 291 | "smallvec", 292 | "utf8_iter", 293 | ] 294 | 295 | [[package]] 296 | name = "idna_adapter" 297 | version = "1.2.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 300 | dependencies = [ 301 | "icu_normalizer", 302 | "icu_properties", 303 | ] 304 | 305 | [[package]] 306 | name = "indexmap" 307 | version = "2.2.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" 310 | dependencies = [ 311 | "equivalent", 312 | "hashbrown", 313 | "serde", 314 | ] 315 | 316 | [[package]] 317 | name = "itoa" 318 | version = "1.0.14" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 321 | 322 | [[package]] 323 | name = "libc" 324 | version = "0.2.164" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 327 | 328 | [[package]] 329 | name = "litemap" 330 | version = "0.7.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 333 | 334 | [[package]] 335 | name = "monch" 336 | version = "0.5.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea" 339 | 340 | [[package]] 341 | name = "once_cell" 342 | version = "1.19.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 345 | 346 | [[package]] 347 | name = "percent-encoding" 348 | version = "2.3.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 351 | 352 | [[package]] 353 | name = "pretty_assertions" 354 | version = "1.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" 357 | dependencies = [ 358 | "diff", 359 | "yansi", 360 | ] 361 | 362 | [[package]] 363 | name = "proc-macro2" 364 | version = "1.0.92" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 367 | dependencies = [ 368 | "unicode-ident", 369 | ] 370 | 371 | [[package]] 372 | name = "quote" 373 | version = "1.0.37" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 376 | dependencies = [ 377 | "proc-macro2", 378 | ] 379 | 380 | [[package]] 381 | name = "ryu" 382 | version = "1.0.15" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 385 | 386 | [[package]] 387 | name = "serde" 388 | version = "1.0.197" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 391 | dependencies = [ 392 | "serde_derive", 393 | ] 394 | 395 | [[package]] 396 | name = "serde_bytes" 397 | version = "0.11.15" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" 400 | dependencies = [ 401 | "serde", 402 | ] 403 | 404 | [[package]] 405 | name = "serde_derive" 406 | version = "1.0.197" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 409 | dependencies = [ 410 | "proc-macro2", 411 | "quote", 412 | "syn", 413 | ] 414 | 415 | [[package]] 416 | name = "serde_json" 417 | version = "1.0.114" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" 420 | dependencies = [ 421 | "itoa", 422 | "ryu", 423 | "serde", 424 | ] 425 | 426 | [[package]] 427 | name = "smallvec" 428 | version = "1.13.2" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 431 | 432 | [[package]] 433 | name = "sptr" 434 | version = "0.3.2" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" 437 | 438 | [[package]] 439 | name = "stable_deref_trait" 440 | version = "1.2.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 443 | 444 | [[package]] 445 | name = "syn" 446 | version = "2.0.89" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 449 | dependencies = [ 450 | "proc-macro2", 451 | "quote", 452 | "unicode-ident", 453 | ] 454 | 455 | [[package]] 456 | name = "synstructure" 457 | version = "0.13.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "syn", 464 | ] 465 | 466 | [[package]] 467 | name = "sys_traits" 468 | version = "0.1.14" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "b0f8c2c55b6b4dd67f0f8df8de9bdf00b16c8ea4fbc4be0c2133d5d3924be5d4" 471 | dependencies = [ 472 | "sys_traits_macros", 473 | ] 474 | 475 | [[package]] 476 | name = "sys_traits_macros" 477 | version = "0.1.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "181f22127402abcf8ee5c83ccd5b408933fec36a6095cf82cda545634692657e" 480 | dependencies = [ 481 | "proc-macro2", 482 | "quote", 483 | "syn", 484 | ] 485 | 486 | [[package]] 487 | name = "thiserror" 488 | version = "2.0.3" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" 491 | dependencies = [ 492 | "thiserror-impl", 493 | ] 494 | 495 | [[package]] 496 | name = "thiserror-impl" 497 | version = "2.0.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" 500 | dependencies = [ 501 | "proc-macro2", 502 | "quote", 503 | "syn", 504 | ] 505 | 506 | [[package]] 507 | name = "tinystr" 508 | version = "0.7.6" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 511 | dependencies = [ 512 | "displaydoc", 513 | "zerovec", 514 | ] 515 | 516 | [[package]] 517 | name = "unicode-ident" 518 | version = "1.0.11" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 521 | 522 | [[package]] 523 | name = "url" 524 | version = "2.5.4" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 527 | dependencies = [ 528 | "form_urlencoded", 529 | "idna", 530 | "percent-encoding", 531 | ] 532 | 533 | [[package]] 534 | name = "utf16_iter" 535 | version = "1.0.5" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 538 | 539 | [[package]] 540 | name = "utf8_iter" 541 | version = "1.0.4" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 544 | 545 | [[package]] 546 | name = "write16" 547 | version = "1.0.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 550 | 551 | [[package]] 552 | name = "writeable" 553 | version = "0.5.5" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 556 | 557 | [[package]] 558 | name = "yansi" 559 | version = "0.5.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 562 | 563 | [[package]] 564 | name = "yoke" 565 | version = "0.7.5" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 568 | dependencies = [ 569 | "serde", 570 | "stable_deref_trait", 571 | "yoke-derive", 572 | "zerofrom", 573 | ] 574 | 575 | [[package]] 576 | name = "yoke-derive" 577 | version = "0.7.5" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 580 | dependencies = [ 581 | "proc-macro2", 582 | "quote", 583 | "syn", 584 | "synstructure", 585 | ] 586 | 587 | [[package]] 588 | name = "zerofrom" 589 | version = "0.1.5" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 592 | dependencies = [ 593 | "zerofrom-derive", 594 | ] 595 | 596 | [[package]] 597 | name = "zerofrom-derive" 598 | version = "0.1.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 601 | dependencies = [ 602 | "proc-macro2", 603 | "quote", 604 | "syn", 605 | "synstructure", 606 | ] 607 | 608 | [[package]] 609 | name = "zerovec" 610 | version = "0.10.4" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 613 | dependencies = [ 614 | "yoke", 615 | "zerofrom", 616 | "zerovec-derive", 617 | ] 618 | 619 | [[package]] 620 | name = "zerovec-derive" 621 | version = "0.10.3" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 624 | dependencies = [ 625 | "proc-macro2", 626 | "quote", 627 | "syn", 628 | ] 629 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | #![deny(clippy::print_stderr)] 4 | #![deny(clippy::print_stdout)] 5 | #![deny(clippy::unused_async)] 6 | #![deny(clippy::unnecessary_wraps)] 7 | 8 | use std::path::Path; 9 | use std::path::PathBuf; 10 | 11 | use boxed_error::Boxed; 12 | use deno_error::JsError; 13 | use deno_semver::npm::NpmVersionReqParseError; 14 | use deno_semver::package::PackageReq; 15 | use deno_semver::StackString; 16 | use deno_semver::VersionReq; 17 | use deno_semver::VersionReqSpecifierParseError; 18 | use indexmap::IndexMap; 19 | use serde::Serialize; 20 | use serde_json::Map; 21 | use serde_json::Value; 22 | use sys_traits::FsRead; 23 | use thiserror::Error; 24 | use url::Url; 25 | 26 | mod sync; 27 | 28 | #[allow(clippy::disallowed_types)] 29 | pub type PackageJsonRc = crate::sync::MaybeArc; 30 | #[allow(clippy::disallowed_types)] 31 | pub type PackageJsonDepsRc = crate::sync::MaybeArc; 32 | #[allow(clippy::disallowed_types)] 33 | type PackageJsonDepsRcCell = crate::sync::MaybeOnceLock; 34 | 35 | pub trait PackageJsonCache { 36 | fn get(&self, path: &Path) -> Option; 37 | fn set(&self, path: PathBuf, package_json: PackageJsonRc); 38 | } 39 | 40 | #[derive(Debug, Clone, JsError, PartialEq, Eq, Boxed)] 41 | pub struct PackageJsonDepValueParseError( 42 | pub Box, 43 | ); 44 | 45 | #[derive(Debug, Error, Clone, JsError, PartialEq, Eq)] 46 | pub enum PackageJsonDepValueParseErrorKind { 47 | #[class(inherit)] 48 | #[error(transparent)] 49 | VersionReq(#[from] NpmVersionReqParseError), 50 | #[class(inherit)] 51 | #[error(transparent)] 52 | JsrVersionReq(#[from] VersionReqSpecifierParseError), 53 | #[class(type)] 54 | #[error("Not implemented scheme '{scheme}'")] 55 | Unsupported { scheme: String }, 56 | } 57 | 58 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 59 | pub enum PackageJsonDepWorkspaceReq { 60 | /// "workspace:~" 61 | Tilde, 62 | 63 | /// "workspace:^" 64 | Caret, 65 | 66 | /// "workspace:x.y.z", "workspace:*", "workspace:^x.y.z" 67 | VersionReq(VersionReq), 68 | } 69 | 70 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 71 | pub enum PackageJsonDepValue { 72 | File(String), 73 | Req(PackageReq), 74 | Workspace(PackageJsonDepWorkspaceReq), 75 | JsrReq(PackageReq), 76 | } 77 | 78 | impl PackageJsonDepValue { 79 | pub fn parse( 80 | key: &str, 81 | value: &str, 82 | ) -> Result { 83 | /// Gets the name and raw version constraint for a registry info or 84 | /// package.json dependency entry taking into account npm package aliases. 85 | fn parse_dep_entry_name_and_raw_version<'a>( 86 | key: &'a str, 87 | value: &'a str, 88 | ) -> (&'a str, &'a str) { 89 | if let Some(package_and_version) = value.strip_prefix("npm:") { 90 | if let Some((name, version)) = package_and_version.rsplit_once('@') { 91 | // if empty, then the name was scoped and there's no version 92 | if name.is_empty() { 93 | (package_and_version, "*") 94 | } else { 95 | (name, version) 96 | } 97 | } else { 98 | (package_and_version, "*") 99 | } 100 | } else { 101 | (key, value) 102 | } 103 | } 104 | 105 | if let Some(workspace_key) = value.strip_prefix("workspace:") { 106 | let workspace_req = match workspace_key { 107 | "~" => PackageJsonDepWorkspaceReq::Tilde, 108 | "^" => PackageJsonDepWorkspaceReq::Caret, 109 | _ => PackageJsonDepWorkspaceReq::VersionReq( 110 | VersionReq::parse_from_npm(workspace_key)?, 111 | ), 112 | }; 113 | return Ok(Self::Workspace(workspace_req)); 114 | } else if let Some(raw_jsr_req) = value.strip_prefix("jsr:") { 115 | let (name, version_req) = 116 | parse_dep_entry_name_and_raw_version(key, raw_jsr_req); 117 | let result = VersionReq::parse_from_specifier(version_req); 118 | match result { 119 | Ok(version_req) => { 120 | return Ok(Self::JsrReq(PackageReq { 121 | name: name.into(), 122 | version_req, 123 | })) 124 | } 125 | Err(err) => { 126 | return Err( 127 | PackageJsonDepValueParseErrorKind::JsrVersionReq(err).into_box(), 128 | ) 129 | } 130 | } 131 | } 132 | if value.starts_with("git:") 133 | || value.starts_with("http:") 134 | || value.starts_with("https:") 135 | { 136 | return Err( 137 | PackageJsonDepValueParseErrorKind::Unsupported { 138 | scheme: value.split(':').next().unwrap().to_string(), 139 | } 140 | .into_box(), 141 | ); 142 | } 143 | if let Some(path) = value.strip_prefix("file:") { 144 | return Ok(Self::File(path.to_string())); 145 | } 146 | let (name, version_req) = parse_dep_entry_name_and_raw_version(key, value); 147 | let result = VersionReq::parse_from_npm(version_req); 148 | match result { 149 | Ok(version_req) => Ok(Self::Req(PackageReq { 150 | name: name.into(), 151 | version_req, 152 | })), 153 | Err(err) => { 154 | Err(PackageJsonDepValueParseErrorKind::VersionReq(err).into_box()) 155 | } 156 | } 157 | } 158 | } 159 | 160 | pub type PackageJsonDepsMap = IndexMap< 161 | StackString, 162 | Result, 163 | >; 164 | 165 | #[derive(Debug, Clone)] 166 | pub struct PackageJsonDeps { 167 | pub dependencies: PackageJsonDepsMap, 168 | pub dev_dependencies: PackageJsonDepsMap, 169 | } 170 | 171 | impl PackageJsonDeps { 172 | /// Gets a package.json dependency entry by alias. 173 | pub fn get( 174 | &self, 175 | alias: &str, 176 | ) -> Option<&Result> { 177 | self 178 | .dependencies 179 | .get(alias) 180 | .or_else(|| self.dev_dependencies.get(alias)) 181 | } 182 | } 183 | 184 | #[derive(Debug, Error, JsError)] 185 | pub enum PackageJsonLoadError { 186 | #[class(inherit)] 187 | #[error("Failed reading '{}'.", .path.display())] 188 | Io { 189 | path: PathBuf, 190 | #[source] 191 | #[inherit] 192 | source: std::io::Error, 193 | }, 194 | #[class(inherit)] 195 | #[error("Malformed package.json '{}'.", .path.display())] 196 | Deserialize { 197 | path: PathBuf, 198 | #[source] 199 | #[inherit] 200 | source: serde_json::Error, 201 | }, 202 | #[error("\"exports\" cannot contains some keys starting with '.' and some not.\nThe exports object must either be an object of package subpath keys\nor an object of main entry condition name keys only.")] 203 | #[class(type)] 204 | InvalidExports, 205 | } 206 | 207 | #[derive(Clone, Debug, Serialize)] 208 | #[serde(rename_all = "camelCase")] 209 | pub struct PackageJson { 210 | pub exports: Option>, 211 | pub imports: Option>, 212 | pub bin: Option, 213 | pub main: Option, 214 | pub module: Option, 215 | pub browser: Option, 216 | pub name: Option, 217 | pub version: Option, 218 | #[serde(skip)] 219 | pub path: PathBuf, 220 | #[serde(rename = "type")] 221 | pub typ: String, 222 | pub types: Option, 223 | pub types_versions: Option>, 224 | pub dependencies: Option>, 225 | pub dev_dependencies: Option>, 226 | pub peer_dependencies: Option>, 227 | pub peer_dependencies_meta: Option, 228 | pub optional_dependencies: Option>, 229 | pub scripts: Option>, 230 | pub workspaces: Option>, 231 | pub os: Option>, 232 | pub cpu: Option>, 233 | #[serde(skip_serializing)] 234 | resolved_deps: PackageJsonDepsRcCell, 235 | } 236 | 237 | impl PackageJson { 238 | pub fn load_from_path( 239 | sys: &impl FsRead, 240 | maybe_cache: Option<&dyn PackageJsonCache>, 241 | path: &Path, 242 | ) -> Result { 243 | if let Some(item) = maybe_cache.and_then(|c| c.get(path)) { 244 | Ok(item) 245 | } else { 246 | match sys.fs_read_to_string_lossy(path) { 247 | Ok(file_text) => { 248 | let pkg_json = 249 | PackageJson::load_from_string(path.to_path_buf(), &file_text)?; 250 | let pkg_json = crate::sync::new_rc(pkg_json); 251 | if let Some(cache) = maybe_cache { 252 | cache.set(path.to_path_buf(), pkg_json.clone()); 253 | } 254 | Ok(pkg_json) 255 | } 256 | Err(err) => Err(PackageJsonLoadError::Io { 257 | path: path.to_path_buf(), 258 | source: err, 259 | }), 260 | } 261 | } 262 | } 263 | 264 | pub fn load_from_string( 265 | path: PathBuf, 266 | source: &str, 267 | ) -> Result { 268 | if source.trim().is_empty() { 269 | return Ok(PackageJson { 270 | path, 271 | main: None, 272 | name: None, 273 | version: None, 274 | module: None, 275 | browser: None, 276 | typ: "none".to_string(), 277 | types: None, 278 | types_versions: None, 279 | exports: None, 280 | imports: None, 281 | bin: None, 282 | dependencies: None, 283 | dev_dependencies: None, 284 | peer_dependencies: None, 285 | peer_dependencies_meta: None, 286 | optional_dependencies: None, 287 | scripts: None, 288 | workspaces: None, 289 | os: None, 290 | cpu: None, 291 | resolved_deps: Default::default(), 292 | }); 293 | } 294 | 295 | let package_json: Value = serde_json::from_str(source).map_err(|err| { 296 | PackageJsonLoadError::Deserialize { 297 | path: path.clone(), 298 | source: err, 299 | } 300 | })?; 301 | Self::load_from_value(path, package_json) 302 | } 303 | 304 | pub fn load_from_value( 305 | path: PathBuf, 306 | package_json: serde_json::Value, 307 | ) -> Result { 308 | fn parse_string_map( 309 | value: serde_json::Value, 310 | ) -> Option> { 311 | if let Value::Object(map) = value { 312 | let mut result = IndexMap::with_capacity(map.len()); 313 | for (k, v) in map { 314 | if let Some(v) = map_string(v) { 315 | result.insert(k, v); 316 | } 317 | } 318 | Some(result) 319 | } else { 320 | None 321 | } 322 | } 323 | 324 | fn map_object(value: serde_json::Value) -> Option> { 325 | match value { 326 | Value::Object(v) => Some(v), 327 | _ => None, 328 | } 329 | } 330 | 331 | fn map_string(value: serde_json::Value) -> Option { 332 | match value { 333 | Value::String(v) => Some(v), 334 | Value::Number(v) => Some(v.to_string()), 335 | _ => None, 336 | } 337 | } 338 | 339 | fn map_array(value: serde_json::Value) -> Option> { 340 | match value { 341 | Value::Array(v) => Some(v), 342 | _ => None, 343 | } 344 | } 345 | 346 | fn parse_string_array(value: serde_json::Value) -> Option> { 347 | let value = map_array(value)?; 348 | let mut result = Vec::with_capacity(value.len()); 349 | for v in value { 350 | if let Some(v) = map_string(v) { 351 | result.push(v); 352 | } 353 | } 354 | Some(result) 355 | } 356 | 357 | let mut package_json = match package_json { 358 | Value::Object(o) => o, 359 | _ => Default::default(), 360 | }; 361 | let imports_val = package_json.remove("imports"); 362 | let main_val = package_json.remove("main"); 363 | let module_val = package_json.remove("module"); 364 | let browser_val = package_json.remove("browser"); 365 | let name_val = package_json.remove("name"); 366 | let version_val = package_json.remove("version"); 367 | let type_val = package_json.remove("type"); 368 | let bin = package_json.remove("bin"); 369 | let exports = package_json 370 | .remove("exports") 371 | .map(|exports| { 372 | if is_conditional_exports_main_sugar(&exports)? { 373 | let mut map = Map::new(); 374 | map.insert(".".to_string(), exports.to_owned()); 375 | Ok::<_, PackageJsonLoadError>(Some(map)) 376 | } else { 377 | Ok(exports.as_object().map(|o| o.to_owned())) 378 | } 379 | }) 380 | .transpose()? 381 | .flatten(); 382 | 383 | let imports = imports_val.and_then(map_object); 384 | let main = main_val.and_then(map_string); 385 | let name = name_val.and_then(map_string); 386 | let version = version_val.and_then(map_string); 387 | let module = module_val.and_then(map_string); 388 | let browser = browser_val.and_then(map_string); 389 | 390 | let dependencies = package_json 391 | .remove("dependencies") 392 | .and_then(parse_string_map); 393 | let dev_dependencies = package_json 394 | .remove("devDependencies") 395 | .and_then(parse_string_map); 396 | let peer_dependencies = package_json 397 | .remove("peerDependencies") 398 | .and_then(parse_string_map); 399 | let peer_dependencies_meta = package_json.remove("peerDependenciesMeta"); 400 | let optional_dependencies = package_json 401 | .remove("optionalDependencies") 402 | .and_then(parse_string_map); 403 | 404 | let scripts: Option> = 405 | package_json.remove("scripts").and_then(parse_string_map); 406 | 407 | // Ignore unknown types for forwards compatibility 408 | let typ = if let Some(t) = type_val { 409 | if let Some(t) = t.as_str() { 410 | if t != "module" && t != "commonjs" { 411 | "none".to_string() 412 | } else { 413 | t.to_string() 414 | } 415 | } else { 416 | "none".to_string() 417 | } 418 | } else { 419 | "none".to_string() 420 | }; 421 | 422 | // for typescript, it looks for "typings" first, then "types" 423 | let types = package_json 424 | .remove("typings") 425 | .or_else(|| package_json.remove("types")) 426 | .and_then(map_string); 427 | let types_versions = package_json 428 | .remove("typesVersions") 429 | .and_then(|exports| exports.as_object().map(|o| o.to_owned())); 430 | let workspaces = package_json 431 | .remove("workspaces") 432 | .and_then(parse_string_array); 433 | let os = package_json.remove("os").and_then(parse_string_array); 434 | let cpu = package_json.remove("cpu").and_then(parse_string_array); 435 | 436 | Ok(PackageJson { 437 | path, 438 | main, 439 | name, 440 | version, 441 | module, 442 | browser, 443 | typ, 444 | types, 445 | types_versions, 446 | exports, 447 | imports, 448 | bin, 449 | dependencies, 450 | dev_dependencies, 451 | peer_dependencies, 452 | peer_dependencies_meta, 453 | optional_dependencies, 454 | scripts, 455 | workspaces, 456 | os, 457 | cpu, 458 | resolved_deps: Default::default(), 459 | }) 460 | } 461 | 462 | pub fn specifier(&self) -> Url { 463 | deno_path_util::url_from_file_path(&self.path).unwrap() 464 | } 465 | 466 | pub fn dir_path(&self) -> &Path { 467 | self.path.parent().unwrap() 468 | } 469 | 470 | /// Resolve the package.json's dependencies. 471 | pub fn resolve_local_package_json_deps(&self) -> &PackageJsonDepsRc { 472 | fn get_map(deps: Option<&IndexMap>) -> PackageJsonDepsMap { 473 | let Some(deps) = deps else { 474 | return Default::default(); 475 | }; 476 | let mut result = IndexMap::with_capacity(deps.len()); 477 | for (key, value) in deps { 478 | result 479 | .entry(StackString::from(key.as_str())) 480 | .or_insert_with(|| PackageJsonDepValue::parse(key, value)); 481 | } 482 | result 483 | } 484 | 485 | self.resolved_deps.get_or_init(|| { 486 | PackageJsonDepsRc::new(PackageJsonDeps { 487 | dependencies: get_map(self.dependencies.as_ref()), 488 | dev_dependencies: get_map(self.dev_dependencies.as_ref()), 489 | }) 490 | }) 491 | } 492 | } 493 | 494 | fn is_conditional_exports_main_sugar( 495 | exports: &Value, 496 | ) -> Result { 497 | if exports.is_string() || exports.is_array() { 498 | return Ok(true); 499 | } 500 | 501 | if exports.is_null() || !exports.is_object() { 502 | return Ok(false); 503 | } 504 | 505 | let exports_obj = exports.as_object().unwrap(); 506 | let mut is_conditional_sugar = false; 507 | let mut i = 0; 508 | for key in exports_obj.keys() { 509 | let cur_is_conditional_sugar = key.is_empty() || !key.starts_with('.'); 510 | if i == 0 { 511 | is_conditional_sugar = cur_is_conditional_sugar; 512 | i += 1; 513 | } else if is_conditional_sugar != cur_is_conditional_sugar { 514 | return Err(PackageJsonLoadError::InvalidExports); 515 | } 516 | } 517 | 518 | Ok(is_conditional_sugar) 519 | } 520 | 521 | #[cfg(test)] 522 | mod test { 523 | use super::*; 524 | use pretty_assertions::assert_eq; 525 | use std::error::Error; 526 | use std::path::PathBuf; 527 | 528 | #[test] 529 | fn null_exports_should_not_crash() { 530 | let package_json = PackageJson::load_from_string( 531 | PathBuf::from("/package.json"), 532 | r#"{ "exports": null }"#, 533 | ) 534 | .unwrap(); 535 | 536 | assert!(package_json.exports.is_none()); 537 | } 538 | 539 | fn get_local_package_json_version_reqs_for_tests( 540 | package_json: &PackageJson, 541 | ) -> IndexMap< 542 | String, 543 | Result, 544 | > { 545 | let deps = package_json.resolve_local_package_json_deps(); 546 | deps 547 | .dependencies 548 | .clone() 549 | .into_iter() 550 | .chain(deps.dev_dependencies.clone()) 551 | .map(|(k, v)| { 552 | ( 553 | k.to_string(), 554 | match v { 555 | Ok(v) => Ok(v), 556 | Err(err) => Err(err.into_kind()), 557 | }, 558 | ) 559 | }) 560 | .collect::>() 561 | } 562 | 563 | #[test] 564 | fn test_get_local_package_json_version_reqs() { 565 | let mut package_json = 566 | PackageJson::load_from_string(PathBuf::from("/package.json"), "{}") 567 | .unwrap(); 568 | package_json.dependencies = Some(IndexMap::from([ 569 | ("test".to_string(), "^1.2".to_string()), 570 | ("other".to_string(), "npm:package@~1.3".to_string()), 571 | ])); 572 | package_json.dev_dependencies = Some(IndexMap::from([ 573 | ("package_b".to_string(), "~2.2".to_string()), 574 | ("other".to_string(), "^3.2".to_string()), 575 | ])); 576 | let deps = package_json.resolve_local_package_json_deps(); 577 | assert_eq!( 578 | deps 579 | .dependencies 580 | .clone() 581 | .into_iter() 582 | .map(|d| (d.0, d.1.unwrap())) 583 | .collect::>(), 584 | Vec::from([ 585 | ( 586 | "test".into(), 587 | PackageJsonDepValue::Req(PackageReq::from_str("test@^1.2").unwrap()) 588 | ), 589 | ( 590 | "other".into(), 591 | PackageJsonDepValue::Req( 592 | PackageReq::from_str("package@~1.3").unwrap() 593 | ) 594 | ), 595 | ]) 596 | ); 597 | assert_eq!( 598 | deps 599 | .dev_dependencies 600 | .clone() 601 | .into_iter() 602 | .map(|d| (d.0, d.1.unwrap())) 603 | .collect::>(), 604 | Vec::from([ 605 | ( 606 | "package_b".into(), 607 | PackageJsonDepValue::Req( 608 | PackageReq::from_str("package_b@~2.2").unwrap() 609 | ) 610 | ), 611 | ( 612 | "other".into(), 613 | PackageJsonDepValue::Req(PackageReq::from_str("other@^3.2").unwrap()) 614 | ), 615 | ]) 616 | ); 617 | } 618 | 619 | #[test] 620 | fn test_get_local_package_json_version_reqs_errors_non_npm_specifier() { 621 | let mut package_json = 622 | PackageJson::load_from_string(PathBuf::from("/package.json"), "{}") 623 | .unwrap(); 624 | package_json.dependencies = Some(IndexMap::from([( 625 | "test".to_string(), 626 | "%*(#$%()".to_string(), 627 | )])); 628 | let map = get_local_package_json_version_reqs_for_tests(&package_json); 629 | assert_eq!(map.len(), 1); 630 | let err = map.get("test").unwrap().as_ref().unwrap_err(); 631 | assert_eq!(format!("{}", err), "Invalid version requirement"); 632 | assert_eq!( 633 | format!("{}", err.source().unwrap()), 634 | concat!("Unexpected character.\n", " %*(#$%()\n", " ~") 635 | ); 636 | } 637 | 638 | #[test] 639 | fn test_get_local_package_json_version_reqs_range() { 640 | let mut package_json = 641 | PackageJson::load_from_string(PathBuf::from("/package.json"), "{}") 642 | .unwrap(); 643 | package_json.dependencies = Some(IndexMap::from([( 644 | "test".to_string(), 645 | "1.x - 1.3".to_string(), 646 | )])); 647 | let map = get_local_package_json_version_reqs_for_tests(&package_json); 648 | assert_eq!( 649 | map, 650 | IndexMap::from([( 651 | "test".to_string(), 652 | Ok(PackageJsonDepValue::Req(PackageReq { 653 | name: "test".into(), 654 | version_req: VersionReq::parse_from_npm("1.x - 1.3").unwrap() 655 | })) 656 | )]) 657 | ); 658 | } 659 | 660 | #[test] 661 | fn test_get_local_package_json_version_reqs_jsr() { 662 | let mut package_json = 663 | PackageJson::load_from_string(PathBuf::from("/package.json"), "{}") 664 | .unwrap(); 665 | package_json.dependencies = Some(IndexMap::from([( 666 | "@denotest/foo".to_string(), 667 | "jsr:^1.2".to_string(), 668 | )])); 669 | let map = get_local_package_json_version_reqs_for_tests(&package_json); 670 | assert_eq!( 671 | map, 672 | IndexMap::from([( 673 | "@denotest/foo".to_string(), 674 | Ok(PackageJsonDepValue::JsrReq(PackageReq { 675 | name: "@denotest/foo".into(), 676 | version_req: VersionReq::parse_from_specifier("^1.2").unwrap() 677 | })) 678 | )]) 679 | ); 680 | } 681 | 682 | #[test] 683 | fn test_get_local_package_json_version_reqs_skips_certain_specifiers() { 684 | let mut package_json = 685 | PackageJson::load_from_string(PathBuf::from("/package.json"), "{}") 686 | .unwrap(); 687 | package_json.dependencies = Some(IndexMap::from([ 688 | ("test".to_string(), "1".to_string()), 689 | ( 690 | "work-test-version-req".to_string(), 691 | "workspace:1.1.1".to_string(), 692 | ), 693 | ("work-test-star".to_string(), "workspace:*".to_string()), 694 | ("work-test-tilde".to_string(), "workspace:~".to_string()), 695 | ("work-test-caret".to_string(), "workspace:^".to_string()), 696 | ("file-test".to_string(), "file:something".to_string()), 697 | ("git-test".to_string(), "git:something".to_string()), 698 | ("http-test".to_string(), "http://something".to_string()), 699 | ("https-test".to_string(), "https://something".to_string()), 700 | ])); 701 | let result = get_local_package_json_version_reqs_for_tests(&package_json); 702 | assert_eq!( 703 | result, 704 | IndexMap::from([ 705 | ( 706 | "test".to_string(), 707 | Ok(PackageJsonDepValue::Req( 708 | PackageReq::from_str("test@1").unwrap() 709 | )) 710 | ), 711 | ( 712 | "work-test-star".to_string(), 713 | Ok(PackageJsonDepValue::Workspace( 714 | PackageJsonDepWorkspaceReq::VersionReq( 715 | VersionReq::parse_from_npm("*").unwrap() 716 | ) 717 | )) 718 | ), 719 | ( 720 | "work-test-version-req".to_string(), 721 | Ok(PackageJsonDepValue::Workspace( 722 | PackageJsonDepWorkspaceReq::VersionReq( 723 | VersionReq::parse_from_npm("1.1.1").unwrap() 724 | ) 725 | )) 726 | ), 727 | ( 728 | "work-test-tilde".to_string(), 729 | Ok(PackageJsonDepValue::Workspace( 730 | PackageJsonDepWorkspaceReq::Tilde 731 | )) 732 | ), 733 | ( 734 | "work-test-caret".to_string(), 735 | Ok(PackageJsonDepValue::Workspace( 736 | PackageJsonDepWorkspaceReq::Caret 737 | )) 738 | ), 739 | ( 740 | "file-test".to_string(), 741 | Ok(PackageJsonDepValue::File("something".to_string())), 742 | ), 743 | ( 744 | "git-test".to_string(), 745 | Err(PackageJsonDepValueParseErrorKind::Unsupported { 746 | scheme: "git".to_string() 747 | }), 748 | ), 749 | ( 750 | "http-test".to_string(), 751 | Err(PackageJsonDepValueParseErrorKind::Unsupported { 752 | scheme: "http".to_string() 753 | }), 754 | ), 755 | ( 756 | "https-test".to_string(), 757 | Err(PackageJsonDepValueParseErrorKind::Unsupported { 758 | scheme: "https".to_string() 759 | }), 760 | ), 761 | ]) 762 | ); 763 | } 764 | 765 | #[test] 766 | fn test_deserialize_serialize() { 767 | let json_value = serde_json::json!({ 768 | "name": "test", 769 | "version": "1", 770 | "exports": { 771 | ".": "./main.js", 772 | }, 773 | "bin": "./main.js", 774 | "types": "./types.d.ts", 775 | "typesVersions": { 776 | "<4.0": { "index.d.ts": ["index.v3.d.ts"] } 777 | }, 778 | "imports": { 779 | "#test": "./main.js", 780 | }, 781 | "main": "./main.js", 782 | "module": "./module.js", 783 | "browser": "./browser.js", 784 | "type": "module", 785 | "dependencies": { 786 | "name": "1.2", 787 | }, 788 | "devDependencies": { 789 | "name": "1.2", 790 | }, 791 | "scripts": { 792 | "test": "echo \"Error: no test specified\" && exit 1", 793 | }, 794 | "workspaces": ["asdf", "asdf2"], 795 | "cpu": ["x86_64"], 796 | "os": ["win32"], 797 | "optionalDependencies": { 798 | "optional": "1.1" 799 | }, 800 | "peerDependencies": { 801 | "peer": "1.0" 802 | }, 803 | "peerDependenciesMeta": { 804 | "peer": { 805 | "optional": true 806 | } 807 | }, 808 | }); 809 | let package_json = PackageJson::load_from_value( 810 | PathBuf::from("/package.json"), 811 | json_value.clone(), 812 | ) 813 | .unwrap(); 814 | let serialized_value = serde_json::to_value(&package_json).unwrap(); 815 | assert_eq!(serialized_value, json_value); 816 | } 817 | 818 | // https://github.com/denoland/deno/issues/26031 819 | #[test] 820 | fn test_exports_error() { 821 | let json_value = serde_json::json!({ 822 | "name": "test", 823 | "version": "1", 824 | "exports": { ".": "./a", "a": "./a" }, 825 | }); 826 | assert!(matches!( 827 | PackageJson::load_from_value( 828 | PathBuf::from("/package.json"), 829 | json_value.clone(), 830 | ), 831 | Err(PackageJsonLoadError::InvalidExports) 832 | )); 833 | } 834 | } 835 | --------------------------------------------------------------------------------