├── .github └── workflows │ ├── pre-release.yaml │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── Makefile.toml ├── README.md ├── actions └── install │ └── action.yml ├── crates ├── depot-test-utils │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── depot │ ├── Cargo.toml │ ├── src │ ├── bin │ │ └── depot.rs │ ├── commands │ │ ├── build.rs │ │ ├── clean.rs │ │ ├── configs │ │ │ ├── pnpm-workspace.yaml │ │ │ └── setup.ts │ │ ├── doc.rs │ │ ├── fix.rs │ │ ├── fmt.rs │ │ ├── init.rs │ │ ├── mod.rs │ │ ├── new.rs │ │ └── test.rs │ ├── lib.rs │ ├── logger │ │ ├── mod.rs │ │ ├── ringbuffer.rs │ │ └── ui.rs │ ├── utils.rs │ └── workspace │ │ ├── dep_graph.rs │ │ ├── fingerprint.rs │ │ ├── manifest.rs │ │ ├── mod.rs │ │ ├── package.rs │ │ ├── process.rs │ │ └── runner.rs │ └── tests │ └── tests │ ├── build.rs │ ├── clean.rs │ ├── doc.rs │ ├── fix.rs │ ├── fmt.rs │ ├── main.rs │ ├── new.rs │ └── test.rs ├── flake.lock ├── flake.nix └── scripts └── install.sh /.github/workflows/pre-release.yaml: -------------------------------------------------------------------------------- 1 | name: Pre-release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | types: [opened,labeled,edited,synchronize] 8 | 9 | jobs: 10 | test-artifacts: 11 | if: contains(github.event.pull_request.labels.*.name, 'release') 12 | 13 | env: 14 | RUST_BACKTRACE: 1 15 | RUST_LIB_BACKTRACE: 1 16 | TOKIO_WORKER_THREADS: 1 17 | 18 | strategy: 19 | matrix: 20 | include: 21 | - target: x86_64-unknown-linux-gnu 22 | os: ubuntu-latest 23 | command: test 24 | - target: x86_64-apple-darwin 25 | os: macos-latest 26 | command: build 27 | - target: aarch64-apple-darwin 28 | os: macos-latest 29 | command: test 30 | # - target: x86_64-pc-windows-msvc 31 | # os: windows-latest 32 | # command: test 33 | 34 | runs-on: ${{ matrix.os }} 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | - name: Install Node 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: 20.15.0 42 | - name: Install pnpm 43 | uses: pnpm/action-setup@v4 44 | with: 45 | version: 9.13.2 46 | - name: Add target 47 | run: rustup target add ${{ matrix.target }} 48 | - name: Cross-compile 49 | run: cargo ${{ matrix.command }} --locked --target ${{ matrix.target }} 50 | 51 | publish-dry-run: 52 | if: contains(github.event.pull_request.labels.*.name, 'release') 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v4 57 | - name: Login to crates.io 58 | run: cargo login ${{ secrets.CRATES_IO_TOKEN }} 59 | - name: Dry run of crate publish 60 | run: cargo publish -p depot-js --dry-run 61 | 62 | check-pr-name: 63 | if: contains(github.event.pull_request.labels.*.name, 'release') 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Check that PR name is a release number 67 | run: echo "${{ github.event.pull_request.title }}" | grep -q -E "^v[0-9]+\.[0-9]+\.[0-9]+$" 68 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | types: [labeled,closed] 7 | 8 | jobs: 9 | build-artifacts: 10 | if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') 11 | 12 | strategy: 13 | matrix: 14 | include: 15 | - target: x86_64-unknown-linux-gnu 16 | os: ubuntu-latest 17 | - target: x86_64-apple-darwin 18 | os: macos-latest 19 | - target: aarch64-apple-darwin 20 | os: macos-latest 21 | 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | - name: Add target 27 | run: rustup target add ${{ matrix.target }} 28 | - name: Compile binary 29 | run: cargo build --locked --release --target ${{ matrix.target }} 30 | - name: Tar artifacts 31 | run: | 32 | cd target/${{ matrix.target }}/release 33 | tar -czf ${{ matrix.target }}.tar.gz depot 34 | - name: Upload artifacts 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: ${{ matrix.target }} 38 | path: target/${{ matrix.target }}/release/${{ matrix.target }}.tar.gz 39 | 40 | publish-crates: 41 | needs: build-artifacts 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Install cargo-workspaces 45 | uses: baptiste0928/cargo-install@v2 46 | with: 47 | crate: cargo-workspaces 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | - name: Login to crates.io 51 | run: cargo login ${{ secrets.CRATES_IO_TOKEN }} 52 | - name: Publish crates 53 | run: cargo ws publish --from-git --yes 54 | - name: Add a tag for the merged commit 55 | uses: christophebedard/tag-version-commit@v1 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | version_regex: 'v([0-9]+\.[0-9]+\.[0-9]+)' 59 | version_tag_prefix: 'v' 60 | 61 | publish-artifacts: 62 | needs: publish-crates 63 | runs-on: ubuntu-latest 64 | permissions: 65 | contents: write 66 | steps: 67 | - name: Download artifacts 68 | uses: actions/download-artifact@v4 69 | - name: Publish artifacts 70 | uses: softprops/action-gh-release@v2 71 | with: 72 | tag_name: ${{ github.event.pull_request.title }} 73 | files: | 74 | x86_64-unknown-linux-gnu/x86_64-unknown-linux-gnu.tar.gz 75 | x86_64-apple-darwin/x86_64-apple-darwin.tar.gz 76 | aarch64-apple-darwin/aarch64-apple-darwin.tar.gz -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | env: 9 | RUST_BACKTRACE: 1 10 | RUST_LIB_BACKTRACE: 1 11 | TOKIO_WORKER_THREADS: 1 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Install Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.15.0 19 | - name: Install pnpm 20 | uses: pnpm/action-setup@v4 21 | with: 22 | version: 9.13.2 23 | - name: Run tests 24 | run: cargo test --features dev -- --test-threads=1 25 | - name: Run lints 26 | run: cargo clippy -- -D warnings 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .envrc 3 | .direnv 4 | result 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "allocator-api2" 31 | version = "0.2.20" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" 34 | 35 | [[package]] 36 | name = "ansi-diff" 37 | version = "1.1.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0dba8285af3fa049e58504e32a9dccea44c2e425061192fa6601febae62155a0" 40 | dependencies = [ 41 | "lazy_static", 42 | "regex", 43 | ] 44 | 45 | [[package]] 46 | name = "ansi-to-tui" 47 | version = "7.0.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" 50 | dependencies = [ 51 | "nom", 52 | "ratatui", 53 | "simdutf8", 54 | "smallvec", 55 | "thiserror", 56 | ] 57 | 58 | [[package]] 59 | name = "anstream" 60 | version = "0.6.18" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 63 | dependencies = [ 64 | "anstyle", 65 | "anstyle-parse", 66 | "anstyle-query", 67 | "anstyle-wincon", 68 | "colorchoice", 69 | "is_terminal_polyfill", 70 | "utf8parse", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle" 75 | version = "1.0.10" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 78 | 79 | [[package]] 80 | name = "anstyle-parse" 81 | version = "0.2.6" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 84 | dependencies = [ 85 | "utf8parse", 86 | ] 87 | 88 | [[package]] 89 | name = "anstyle-query" 90 | version = "1.1.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 93 | dependencies = [ 94 | "windows-sys 0.59.0", 95 | ] 96 | 97 | [[package]] 98 | name = "anstyle-wincon" 99 | version = "3.0.6" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 102 | dependencies = [ 103 | "anstyle", 104 | "windows-sys 0.59.0", 105 | ] 106 | 107 | [[package]] 108 | name = "anyhow" 109 | version = "1.0.93" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" 112 | dependencies = [ 113 | "backtrace", 114 | ] 115 | 116 | [[package]] 117 | name = "async-trait" 118 | version = "0.1.83" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 121 | dependencies = [ 122 | "proc-macro2", 123 | "quote", 124 | "syn 2.0.87", 125 | ] 126 | 127 | [[package]] 128 | name = "atomic_enum" 129 | version = "0.2.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "6227a8d6fdb862bcb100c4314d0d9579e5cd73fa6df31a2e6f6e1acd3c5f1207" 132 | dependencies = [ 133 | "proc-macro2", 134 | "quote", 135 | "syn 1.0.109", 136 | ] 137 | 138 | [[package]] 139 | name = "autocfg" 140 | version = "1.4.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 143 | 144 | [[package]] 145 | name = "backtrace" 146 | version = "0.3.74" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 149 | dependencies = [ 150 | "addr2line", 151 | "cfg-if", 152 | "libc", 153 | "miniz_oxide", 154 | "object", 155 | "rustc-demangle", 156 | "windows-targets 0.52.6", 157 | ] 158 | 159 | [[package]] 160 | name = "base64" 161 | version = "0.21.7" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 164 | 165 | [[package]] 166 | name = "bimap" 167 | version = "0.6.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" 170 | 171 | [[package]] 172 | name = "bitflags" 173 | version = "1.3.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 176 | 177 | [[package]] 178 | name = "bitflags" 179 | version = "2.6.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 182 | 183 | [[package]] 184 | name = "bstr" 185 | version = "1.11.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" 188 | dependencies = [ 189 | "memchr", 190 | "serde", 191 | ] 192 | 193 | [[package]] 194 | name = "bumpalo" 195 | version = "3.16.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 198 | 199 | [[package]] 200 | name = "bytes" 201 | version = "1.8.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 204 | 205 | [[package]] 206 | name = "cassowary" 207 | version = "0.3.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 210 | 211 | [[package]] 212 | name = "castaway" 213 | version = "0.2.3" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 216 | dependencies = [ 217 | "rustversion", 218 | ] 219 | 220 | [[package]] 221 | name = "cc" 222 | version = "1.2.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 225 | dependencies = [ 226 | "shlex", 227 | ] 228 | 229 | [[package]] 230 | name = "cfg-if" 231 | version = "1.0.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 234 | 235 | [[package]] 236 | name = "chrono" 237 | version = "0.4.38" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 240 | dependencies = [ 241 | "num-traits", 242 | "serde", 243 | ] 244 | 245 | [[package]] 246 | name = "clap" 247 | version = "4.5.21" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 250 | dependencies = [ 251 | "clap_builder", 252 | "clap_derive", 253 | ] 254 | 255 | [[package]] 256 | name = "clap_builder" 257 | version = "4.5.21" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 260 | dependencies = [ 261 | "anstream", 262 | "anstyle", 263 | "clap_lex", 264 | "strsim", 265 | ] 266 | 267 | [[package]] 268 | name = "clap_derive" 269 | version = "4.5.18" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 272 | dependencies = [ 273 | "heck", 274 | "proc-macro2", 275 | "quote", 276 | "syn 2.0.87", 277 | ] 278 | 279 | [[package]] 280 | name = "clap_lex" 281 | version = "0.7.3" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 284 | 285 | [[package]] 286 | name = "colorchoice" 287 | version = "1.0.3" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 290 | 291 | [[package]] 292 | name = "compact_str" 293 | version = "0.8.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 296 | dependencies = [ 297 | "castaway", 298 | "cfg-if", 299 | "itoa", 300 | "rustversion", 301 | "ryu", 302 | "static_assertions", 303 | ] 304 | 305 | [[package]] 306 | name = "console" 307 | version = "0.15.8" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 310 | dependencies = [ 311 | "encode_unicode", 312 | "lazy_static", 313 | "libc", 314 | "unicode-width 0.1.14", 315 | "windows-sys 0.52.0", 316 | ] 317 | 318 | [[package]] 319 | name = "core-foundation" 320 | version = "0.9.4" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 323 | dependencies = [ 324 | "core-foundation-sys", 325 | "libc", 326 | ] 327 | 328 | [[package]] 329 | name = "core-foundation-sys" 330 | version = "0.8.7" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 333 | 334 | [[package]] 335 | name = "crossbeam-channel" 336 | version = "0.5.13" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 339 | dependencies = [ 340 | "crossbeam-utils", 341 | ] 342 | 343 | [[package]] 344 | name = "crossbeam-deque" 345 | version = "0.8.5" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 348 | dependencies = [ 349 | "crossbeam-epoch", 350 | "crossbeam-utils", 351 | ] 352 | 353 | [[package]] 354 | name = "crossbeam-epoch" 355 | version = "0.9.18" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 358 | dependencies = [ 359 | "crossbeam-utils", 360 | ] 361 | 362 | [[package]] 363 | name = "crossbeam-utils" 364 | version = "0.8.20" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 367 | 368 | [[package]] 369 | name = "crossterm" 370 | version = "0.26.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" 373 | dependencies = [ 374 | "bitflags 1.3.2", 375 | "crossterm_winapi", 376 | "filedescriptor", 377 | "futures-core", 378 | "libc", 379 | "mio 0.8.11", 380 | "parking_lot", 381 | "signal-hook", 382 | "signal-hook-mio", 383 | "winapi", 384 | ] 385 | 386 | [[package]] 387 | name = "crossterm" 388 | version = "0.28.1" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 391 | dependencies = [ 392 | "bitflags 2.6.0", 393 | "crossterm_winapi", 394 | "mio 1.0.2", 395 | "parking_lot", 396 | "rustix", 397 | "signal-hook", 398 | "signal-hook-mio", 399 | "winapi", 400 | ] 401 | 402 | [[package]] 403 | name = "crossterm_winapi" 404 | version = "0.9.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 407 | dependencies = [ 408 | "winapi", 409 | ] 410 | 411 | [[package]] 412 | name = "darling" 413 | version = "0.20.10" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 416 | dependencies = [ 417 | "darling_core", 418 | "darling_macro", 419 | ] 420 | 421 | [[package]] 422 | name = "darling_core" 423 | version = "0.20.10" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 426 | dependencies = [ 427 | "fnv", 428 | "ident_case", 429 | "proc-macro2", 430 | "quote", 431 | "strsim", 432 | "syn 2.0.87", 433 | ] 434 | 435 | [[package]] 436 | name = "darling_macro" 437 | version = "0.20.10" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 440 | dependencies = [ 441 | "darling_core", 442 | "quote", 443 | "syn 2.0.87", 444 | ] 445 | 446 | [[package]] 447 | name = "depot-js" 448 | version = "0.3.0" 449 | dependencies = [ 450 | "ansi-diff", 451 | "ansi-to-tui", 452 | "anyhow", 453 | "async-trait", 454 | "atomic_enum", 455 | "bimap", 456 | "cfg-if", 457 | "chrono", 458 | "clap", 459 | "crossterm 0.26.1", 460 | "depot-test-utils", 461 | "env_logger", 462 | "futures", 463 | "home", 464 | "ignore", 465 | "indexmap", 466 | "indicatif", 467 | "log", 468 | "maplit", 469 | "notify", 470 | "notify-debouncer-mini", 471 | "package_json_schema", 472 | "pathsearch", 473 | "petgraph", 474 | "ratatui", 475 | "reqwest", 476 | "serde", 477 | "serde_json", 478 | "shlex", 479 | "tempfile", 480 | "textwrap", 481 | "tokio", 482 | ] 483 | 484 | [[package]] 485 | name = "depot-test-utils" 486 | version = "0.0.1" 487 | dependencies = [ 488 | "anyhow", 489 | "either", 490 | "shlex", 491 | "snapbox", 492 | "tempfile", 493 | ] 494 | 495 | [[package]] 496 | name = "diff" 497 | version = "0.1.13" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 500 | 501 | [[package]] 502 | name = "displaydoc" 503 | version = "0.2.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 506 | dependencies = [ 507 | "proc-macro2", 508 | "quote", 509 | "syn 2.0.87", 510 | ] 511 | 512 | [[package]] 513 | name = "doc-comment" 514 | version = "0.3.3" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 517 | 518 | [[package]] 519 | name = "either" 520 | version = "1.13.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 523 | 524 | [[package]] 525 | name = "encode_unicode" 526 | version = "0.3.6" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 529 | 530 | [[package]] 531 | name = "encoding_rs" 532 | version = "0.8.35" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 535 | dependencies = [ 536 | "cfg-if", 537 | ] 538 | 539 | [[package]] 540 | name = "env_logger" 541 | version = "0.10.2" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 544 | dependencies = [ 545 | "humantime", 546 | "is-terminal", 547 | "log", 548 | "regex", 549 | "termcolor", 550 | ] 551 | 552 | [[package]] 553 | name = "equivalent" 554 | version = "1.0.1" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 557 | 558 | [[package]] 559 | name = "errno" 560 | version = "0.3.9" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 563 | dependencies = [ 564 | "libc", 565 | "windows-sys 0.52.0", 566 | ] 567 | 568 | [[package]] 569 | name = "fastrand" 570 | version = "2.2.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 573 | 574 | [[package]] 575 | name = "filedescriptor" 576 | version = "0.8.2" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" 579 | dependencies = [ 580 | "libc", 581 | "thiserror", 582 | "winapi", 583 | ] 584 | 585 | [[package]] 586 | name = "filetime" 587 | version = "0.2.25" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 590 | dependencies = [ 591 | "cfg-if", 592 | "libc", 593 | "libredox", 594 | "windows-sys 0.59.0", 595 | ] 596 | 597 | [[package]] 598 | name = "fixedbitset" 599 | version = "0.4.2" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 602 | 603 | [[package]] 604 | name = "fnv" 605 | version = "1.0.7" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 608 | 609 | [[package]] 610 | name = "foldhash" 611 | version = "0.1.3" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" 614 | 615 | [[package]] 616 | name = "form_urlencoded" 617 | version = "1.2.1" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 620 | dependencies = [ 621 | "percent-encoding", 622 | ] 623 | 624 | [[package]] 625 | name = "fsevent-sys" 626 | version = "4.1.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" 629 | dependencies = [ 630 | "libc", 631 | ] 632 | 633 | [[package]] 634 | name = "futures" 635 | version = "0.3.31" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 638 | dependencies = [ 639 | "futures-channel", 640 | "futures-core", 641 | "futures-io", 642 | "futures-sink", 643 | "futures-task", 644 | "futures-util", 645 | ] 646 | 647 | [[package]] 648 | name = "futures-channel" 649 | version = "0.3.31" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 652 | dependencies = [ 653 | "futures-core", 654 | "futures-sink", 655 | ] 656 | 657 | [[package]] 658 | name = "futures-core" 659 | version = "0.3.31" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 662 | 663 | [[package]] 664 | name = "futures-io" 665 | version = "0.3.31" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 668 | 669 | [[package]] 670 | name = "futures-macro" 671 | version = "0.3.31" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 674 | dependencies = [ 675 | "proc-macro2", 676 | "quote", 677 | "syn 2.0.87", 678 | ] 679 | 680 | [[package]] 681 | name = "futures-sink" 682 | version = "0.3.31" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 685 | 686 | [[package]] 687 | name = "futures-task" 688 | version = "0.3.31" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 691 | 692 | [[package]] 693 | name = "futures-util" 694 | version = "0.3.31" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 697 | dependencies = [ 698 | "futures-channel", 699 | "futures-core", 700 | "futures-io", 701 | "futures-macro", 702 | "futures-sink", 703 | "futures-task", 704 | "memchr", 705 | "pin-project-lite", 706 | "pin-utils", 707 | "slab", 708 | ] 709 | 710 | [[package]] 711 | name = "getrandom" 712 | version = "0.2.15" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 715 | dependencies = [ 716 | "cfg-if", 717 | "libc", 718 | "wasi", 719 | ] 720 | 721 | [[package]] 722 | name = "gimli" 723 | version = "0.31.1" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 726 | 727 | [[package]] 728 | name = "globset" 729 | version = "0.4.15" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" 732 | dependencies = [ 733 | "aho-corasick", 734 | "bstr", 735 | "log", 736 | "regex-automata", 737 | "regex-syntax", 738 | ] 739 | 740 | [[package]] 741 | name = "h2" 742 | version = "0.3.26" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 745 | dependencies = [ 746 | "bytes", 747 | "fnv", 748 | "futures-core", 749 | "futures-sink", 750 | "futures-util", 751 | "http", 752 | "indexmap", 753 | "slab", 754 | "tokio", 755 | "tokio-util", 756 | "tracing", 757 | ] 758 | 759 | [[package]] 760 | name = "hashbrown" 761 | version = "0.15.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 764 | dependencies = [ 765 | "allocator-api2", 766 | "equivalent", 767 | "foldhash", 768 | ] 769 | 770 | [[package]] 771 | name = "heck" 772 | version = "0.5.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 775 | 776 | [[package]] 777 | name = "hermit-abi" 778 | version = "0.3.9" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 781 | 782 | [[package]] 783 | name = "hermit-abi" 784 | version = "0.4.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 787 | 788 | [[package]] 789 | name = "home" 790 | version = "0.5.9" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 793 | dependencies = [ 794 | "windows-sys 0.52.0", 795 | ] 796 | 797 | [[package]] 798 | name = "http" 799 | version = "0.2.12" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 802 | dependencies = [ 803 | "bytes", 804 | "fnv", 805 | "itoa", 806 | ] 807 | 808 | [[package]] 809 | name = "http-body" 810 | version = "0.4.6" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 813 | dependencies = [ 814 | "bytes", 815 | "http", 816 | "pin-project-lite", 817 | ] 818 | 819 | [[package]] 820 | name = "httparse" 821 | version = "1.9.5" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 824 | 825 | [[package]] 826 | name = "httpdate" 827 | version = "1.0.3" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 830 | 831 | [[package]] 832 | name = "humantime" 833 | version = "2.1.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 836 | 837 | [[package]] 838 | name = "hyper" 839 | version = "0.14.31" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" 842 | dependencies = [ 843 | "bytes", 844 | "futures-channel", 845 | "futures-core", 846 | "futures-util", 847 | "h2", 848 | "http", 849 | "http-body", 850 | "httparse", 851 | "httpdate", 852 | "itoa", 853 | "pin-project-lite", 854 | "socket2", 855 | "tokio", 856 | "tower-service", 857 | "tracing", 858 | "want", 859 | ] 860 | 861 | [[package]] 862 | name = "hyper-rustls" 863 | version = "0.24.2" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 866 | dependencies = [ 867 | "futures-util", 868 | "http", 869 | "hyper", 870 | "rustls", 871 | "tokio", 872 | "tokio-rustls", 873 | ] 874 | 875 | [[package]] 876 | name = "icu_collections" 877 | version = "1.5.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 880 | dependencies = [ 881 | "displaydoc", 882 | "yoke", 883 | "zerofrom", 884 | "zerovec", 885 | ] 886 | 887 | [[package]] 888 | name = "icu_locid" 889 | version = "1.5.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 892 | dependencies = [ 893 | "displaydoc", 894 | "litemap", 895 | "tinystr", 896 | "writeable", 897 | "zerovec", 898 | ] 899 | 900 | [[package]] 901 | name = "icu_locid_transform" 902 | version = "1.5.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 905 | dependencies = [ 906 | "displaydoc", 907 | "icu_locid", 908 | "icu_locid_transform_data", 909 | "icu_provider", 910 | "tinystr", 911 | "zerovec", 912 | ] 913 | 914 | [[package]] 915 | name = "icu_locid_transform_data" 916 | version = "1.5.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 919 | 920 | [[package]] 921 | name = "icu_normalizer" 922 | version = "1.5.0" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 925 | dependencies = [ 926 | "displaydoc", 927 | "icu_collections", 928 | "icu_normalizer_data", 929 | "icu_properties", 930 | "icu_provider", 931 | "smallvec", 932 | "utf16_iter", 933 | "utf8_iter", 934 | "write16", 935 | "zerovec", 936 | ] 937 | 938 | [[package]] 939 | name = "icu_normalizer_data" 940 | version = "1.5.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 943 | 944 | [[package]] 945 | name = "icu_properties" 946 | version = "1.5.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 949 | dependencies = [ 950 | "displaydoc", 951 | "icu_collections", 952 | "icu_locid_transform", 953 | "icu_properties_data", 954 | "icu_provider", 955 | "tinystr", 956 | "zerovec", 957 | ] 958 | 959 | [[package]] 960 | name = "icu_properties_data" 961 | version = "1.5.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 964 | 965 | [[package]] 966 | name = "icu_provider" 967 | version = "1.5.0" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 970 | dependencies = [ 971 | "displaydoc", 972 | "icu_locid", 973 | "icu_provider_macros", 974 | "stable_deref_trait", 975 | "tinystr", 976 | "writeable", 977 | "yoke", 978 | "zerofrom", 979 | "zerovec", 980 | ] 981 | 982 | [[package]] 983 | name = "icu_provider_macros" 984 | version = "1.5.0" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 987 | dependencies = [ 988 | "proc-macro2", 989 | "quote", 990 | "syn 2.0.87", 991 | ] 992 | 993 | [[package]] 994 | name = "ident_case" 995 | version = "1.0.1" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 998 | 999 | [[package]] 1000 | name = "idna" 1001 | version = "1.0.3" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1004 | dependencies = [ 1005 | "idna_adapter", 1006 | "smallvec", 1007 | "utf8_iter", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "idna_adapter" 1012 | version = "1.2.0" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1015 | dependencies = [ 1016 | "icu_normalizer", 1017 | "icu_properties", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "ignore" 1022 | version = "0.4.23" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 1025 | dependencies = [ 1026 | "crossbeam-deque", 1027 | "globset", 1028 | "log", 1029 | "memchr", 1030 | "regex-automata", 1031 | "same-file", 1032 | "walkdir", 1033 | "winapi-util", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "indexmap" 1038 | version = "2.6.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 1041 | dependencies = [ 1042 | "equivalent", 1043 | "hashbrown", 1044 | "serde", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "indicatif" 1049 | version = "0.17.9" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" 1052 | dependencies = [ 1053 | "console", 1054 | "number_prefix", 1055 | "portable-atomic", 1056 | "unicode-width 0.2.0", 1057 | "web-time", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "indoc" 1062 | version = "2.0.5" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 1065 | 1066 | [[package]] 1067 | name = "inotify" 1068 | version = "0.9.6" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" 1071 | dependencies = [ 1072 | "bitflags 1.3.2", 1073 | "inotify-sys", 1074 | "libc", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "inotify-sys" 1079 | version = "0.1.5" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 1082 | dependencies = [ 1083 | "libc", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "instability" 1088 | version = "0.3.3" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" 1091 | dependencies = [ 1092 | "darling", 1093 | "indoc", 1094 | "pretty_assertions", 1095 | "proc-macro2", 1096 | "quote", 1097 | "syn 2.0.87", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "ipnet" 1102 | version = "2.10.1" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 1105 | 1106 | [[package]] 1107 | name = "is-terminal" 1108 | version = "0.4.13" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 1111 | dependencies = [ 1112 | "hermit-abi 0.4.0", 1113 | "libc", 1114 | "windows-sys 0.52.0", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "is_terminal_polyfill" 1119 | version = "1.70.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1122 | 1123 | [[package]] 1124 | name = "itertools" 1125 | version = "0.13.0" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 1128 | dependencies = [ 1129 | "either", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "itoa" 1134 | version = "1.0.11" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 1137 | 1138 | [[package]] 1139 | name = "js-sys" 1140 | version = "0.3.72" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 1143 | dependencies = [ 1144 | "wasm-bindgen", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "kqueue" 1149 | version = "1.0.8" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" 1152 | dependencies = [ 1153 | "kqueue-sys", 1154 | "libc", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "kqueue-sys" 1159 | version = "1.0.4" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" 1162 | dependencies = [ 1163 | "bitflags 1.3.2", 1164 | "libc", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "lazy_static" 1169 | version = "1.5.0" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1172 | 1173 | [[package]] 1174 | name = "libc" 1175 | version = "0.2.164" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 1178 | 1179 | [[package]] 1180 | name = "libredox" 1181 | version = "0.1.3" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1184 | dependencies = [ 1185 | "bitflags 2.6.0", 1186 | "libc", 1187 | "redox_syscall", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "linux-raw-sys" 1192 | version = "0.4.14" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1195 | 1196 | [[package]] 1197 | name = "litemap" 1198 | version = "0.7.3" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 1201 | 1202 | [[package]] 1203 | name = "lock_api" 1204 | version = "0.4.12" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1207 | dependencies = [ 1208 | "autocfg", 1209 | "scopeguard", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "log" 1214 | version = "0.4.22" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 1217 | 1218 | [[package]] 1219 | name = "lru" 1220 | version = "0.12.5" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1223 | dependencies = [ 1224 | "hashbrown", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "maplit" 1229 | version = "1.0.2" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 1232 | 1233 | [[package]] 1234 | name = "memchr" 1235 | version = "2.7.4" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1238 | 1239 | [[package]] 1240 | name = "mime" 1241 | version = "0.3.17" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1244 | 1245 | [[package]] 1246 | name = "minimal-lexical" 1247 | version = "0.2.1" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1250 | 1251 | [[package]] 1252 | name = "miniz_oxide" 1253 | version = "0.8.0" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 1256 | dependencies = [ 1257 | "adler2", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "mio" 1262 | version = "0.8.11" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1265 | dependencies = [ 1266 | "libc", 1267 | "log", 1268 | "wasi", 1269 | "windows-sys 0.48.0", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "mio" 1274 | version = "1.0.2" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 1277 | dependencies = [ 1278 | "hermit-abi 0.3.9", 1279 | "libc", 1280 | "log", 1281 | "wasi", 1282 | "windows-sys 0.52.0", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "nom" 1287 | version = "7.1.3" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1290 | dependencies = [ 1291 | "memchr", 1292 | "minimal-lexical", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "normalize-line-endings" 1297 | version = "0.3.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1300 | 1301 | [[package]] 1302 | name = "notify" 1303 | version = "6.1.1" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" 1306 | dependencies = [ 1307 | "bitflags 2.6.0", 1308 | "crossbeam-channel", 1309 | "filetime", 1310 | "fsevent-sys", 1311 | "inotify", 1312 | "kqueue", 1313 | "libc", 1314 | "log", 1315 | "mio 0.8.11", 1316 | "walkdir", 1317 | "windows-sys 0.48.0", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "notify-debouncer-mini" 1322 | version = "0.3.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "e55ee272914f4563a2f8b8553eb6811f3c0caea81c756346bad15b7e3ef969f0" 1325 | dependencies = [ 1326 | "notify", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "num-traits" 1331 | version = "0.2.19" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1334 | dependencies = [ 1335 | "autocfg", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "number_prefix" 1340 | version = "0.4.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 1343 | 1344 | [[package]] 1345 | name = "object" 1346 | version = "0.36.5" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 1349 | dependencies = [ 1350 | "memchr", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "once_cell" 1355 | version = "1.20.2" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1358 | 1359 | [[package]] 1360 | name = "package_json_schema" 1361 | version = "0.2.2" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "08cd444dc63e73faa89053937a9a02ba646b6e9ba3a45e069b85e10cf6bd7114" 1364 | dependencies = [ 1365 | "cfg-if", 1366 | "doc-comment", 1367 | "indexmap", 1368 | "lazy_static", 1369 | "regex", 1370 | "semver", 1371 | "serde", 1372 | "serde_json", 1373 | "thiserror", 1374 | "typed-builder", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "parking_lot" 1379 | version = "0.12.3" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1382 | dependencies = [ 1383 | "lock_api", 1384 | "parking_lot_core", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "parking_lot_core" 1389 | version = "0.9.10" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1392 | dependencies = [ 1393 | "cfg-if", 1394 | "libc", 1395 | "redox_syscall", 1396 | "smallvec", 1397 | "windows-targets 0.52.6", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "paste" 1402 | version = "1.0.15" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1405 | 1406 | [[package]] 1407 | name = "pathsearch" 1408 | version = "0.2.0" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "da983bc5e582ab17179c190b4b66c7d76c5943a69c6d34df2a2b6bf8a2977b05" 1411 | dependencies = [ 1412 | "anyhow", 1413 | "libc", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "percent-encoding" 1418 | version = "2.3.1" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1421 | 1422 | [[package]] 1423 | name = "petgraph" 1424 | version = "0.6.5" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 1427 | dependencies = [ 1428 | "fixedbitset", 1429 | "indexmap", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "pin-project-lite" 1434 | version = "0.2.15" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1437 | 1438 | [[package]] 1439 | name = "pin-utils" 1440 | version = "0.1.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1443 | 1444 | [[package]] 1445 | name = "portable-atomic" 1446 | version = "1.9.0" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 1449 | 1450 | [[package]] 1451 | name = "pretty_assertions" 1452 | version = "1.4.1" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 1455 | dependencies = [ 1456 | "diff", 1457 | "yansi", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "proc-macro2" 1462 | version = "1.0.89" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 1465 | dependencies = [ 1466 | "unicode-ident", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "quote" 1471 | version = "1.0.37" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1474 | dependencies = [ 1475 | "proc-macro2", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "ratatui" 1480 | version = "0.29.0" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 1483 | dependencies = [ 1484 | "bitflags 2.6.0", 1485 | "cassowary", 1486 | "compact_str", 1487 | "crossterm 0.28.1", 1488 | "indoc", 1489 | "instability", 1490 | "itertools", 1491 | "lru", 1492 | "paste", 1493 | "strum", 1494 | "unicode-segmentation", 1495 | "unicode-truncate", 1496 | "unicode-width 0.2.0", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "redox_syscall" 1501 | version = "0.5.7" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1504 | dependencies = [ 1505 | "bitflags 2.6.0", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "regex" 1510 | version = "1.11.1" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1513 | dependencies = [ 1514 | "aho-corasick", 1515 | "memchr", 1516 | "regex-automata", 1517 | "regex-syntax", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "regex-automata" 1522 | version = "0.4.9" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1525 | dependencies = [ 1526 | "aho-corasick", 1527 | "memchr", 1528 | "regex-syntax", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "regex-syntax" 1533 | version = "0.8.5" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1536 | 1537 | [[package]] 1538 | name = "reqwest" 1539 | version = "0.11.27" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1542 | dependencies = [ 1543 | "base64", 1544 | "bytes", 1545 | "encoding_rs", 1546 | "futures-core", 1547 | "futures-util", 1548 | "h2", 1549 | "http", 1550 | "http-body", 1551 | "hyper", 1552 | "hyper-rustls", 1553 | "ipnet", 1554 | "js-sys", 1555 | "log", 1556 | "mime", 1557 | "once_cell", 1558 | "percent-encoding", 1559 | "pin-project-lite", 1560 | "rustls", 1561 | "rustls-pemfile", 1562 | "serde", 1563 | "serde_json", 1564 | "serde_urlencoded", 1565 | "sync_wrapper", 1566 | "system-configuration", 1567 | "tokio", 1568 | "tokio-rustls", 1569 | "tokio-util", 1570 | "tower-service", 1571 | "url", 1572 | "wasm-bindgen", 1573 | "wasm-bindgen-futures", 1574 | "wasm-streams", 1575 | "web-sys", 1576 | "webpki-roots", 1577 | "winreg", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "ring" 1582 | version = "0.17.8" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1585 | dependencies = [ 1586 | "cc", 1587 | "cfg-if", 1588 | "getrandom", 1589 | "libc", 1590 | "spin", 1591 | "untrusted", 1592 | "windows-sys 0.52.0", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "rustc-demangle" 1597 | version = "0.1.24" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1600 | 1601 | [[package]] 1602 | name = "rustix" 1603 | version = "0.38.41" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 1606 | dependencies = [ 1607 | "bitflags 2.6.0", 1608 | "errno", 1609 | "libc", 1610 | "linux-raw-sys", 1611 | "windows-sys 0.52.0", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "rustls" 1616 | version = "0.21.12" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1619 | dependencies = [ 1620 | "log", 1621 | "ring", 1622 | "rustls-webpki", 1623 | "sct", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "rustls-pemfile" 1628 | version = "1.0.4" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1631 | dependencies = [ 1632 | "base64", 1633 | ] 1634 | 1635 | [[package]] 1636 | name = "rustls-webpki" 1637 | version = "0.101.7" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1640 | dependencies = [ 1641 | "ring", 1642 | "untrusted", 1643 | ] 1644 | 1645 | [[package]] 1646 | name = "rustversion" 1647 | version = "1.0.18" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 1650 | 1651 | [[package]] 1652 | name = "ryu" 1653 | version = "1.0.18" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1656 | 1657 | [[package]] 1658 | name = "same-file" 1659 | version = "1.0.6" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1662 | dependencies = [ 1663 | "winapi-util", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "scopeguard" 1668 | version = "1.2.0" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1671 | 1672 | [[package]] 1673 | name = "sct" 1674 | version = "0.7.1" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1677 | dependencies = [ 1678 | "ring", 1679 | "untrusted", 1680 | ] 1681 | 1682 | [[package]] 1683 | name = "semver" 1684 | version = "1.0.23" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1687 | 1688 | [[package]] 1689 | name = "serde" 1690 | version = "1.0.215" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 1693 | dependencies = [ 1694 | "serde_derive", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "serde_derive" 1699 | version = "1.0.215" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 1702 | dependencies = [ 1703 | "proc-macro2", 1704 | "quote", 1705 | "syn 2.0.87", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "serde_json" 1710 | version = "1.0.133" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 1713 | dependencies = [ 1714 | "indexmap", 1715 | "itoa", 1716 | "memchr", 1717 | "ryu", 1718 | "serde", 1719 | ] 1720 | 1721 | [[package]] 1722 | name = "serde_urlencoded" 1723 | version = "0.7.1" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1726 | dependencies = [ 1727 | "form_urlencoded", 1728 | "itoa", 1729 | "ryu", 1730 | "serde", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "shlex" 1735 | version = "1.3.0" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1738 | 1739 | [[package]] 1740 | name = "signal-hook" 1741 | version = "0.3.17" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1744 | dependencies = [ 1745 | "libc", 1746 | "signal-hook-registry", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "signal-hook-mio" 1751 | version = "0.2.4" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1754 | dependencies = [ 1755 | "libc", 1756 | "mio 0.8.11", 1757 | "mio 1.0.2", 1758 | "signal-hook", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "signal-hook-registry" 1763 | version = "1.4.2" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1766 | dependencies = [ 1767 | "libc", 1768 | ] 1769 | 1770 | [[package]] 1771 | name = "simdutf8" 1772 | version = "0.1.5" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 1775 | 1776 | [[package]] 1777 | name = "similar" 1778 | version = "2.6.0" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" 1781 | 1782 | [[package]] 1783 | name = "slab" 1784 | version = "0.4.9" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1787 | dependencies = [ 1788 | "autocfg", 1789 | ] 1790 | 1791 | [[package]] 1792 | name = "smallvec" 1793 | version = "1.13.2" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1796 | 1797 | [[package]] 1798 | name = "smawk" 1799 | version = "0.3.2" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" 1802 | 1803 | [[package]] 1804 | name = "snapbox" 1805 | version = "0.4.17" 1806 | source = "registry+https://github.com/rust-lang/crates.io-index" 1807 | checksum = "4b831b6e80fbcd2889efa75b185d24005f85981431495f995292b25836519d84" 1808 | dependencies = [ 1809 | "anstream", 1810 | "anstyle", 1811 | "normalize-line-endings", 1812 | "similar", 1813 | "snapbox-macros", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "snapbox-macros" 1818 | version = "0.3.10" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" 1821 | dependencies = [ 1822 | "anstream", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "socket2" 1827 | version = "0.5.7" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1830 | dependencies = [ 1831 | "libc", 1832 | "windows-sys 0.52.0", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "spin" 1837 | version = "0.9.8" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1840 | 1841 | [[package]] 1842 | name = "stable_deref_trait" 1843 | version = "1.2.0" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1846 | 1847 | [[package]] 1848 | name = "static_assertions" 1849 | version = "1.1.0" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1852 | 1853 | [[package]] 1854 | name = "strsim" 1855 | version = "0.11.1" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1858 | 1859 | [[package]] 1860 | name = "strum" 1861 | version = "0.26.3" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1864 | dependencies = [ 1865 | "strum_macros", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "strum_macros" 1870 | version = "0.26.4" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1873 | dependencies = [ 1874 | "heck", 1875 | "proc-macro2", 1876 | "quote", 1877 | "rustversion", 1878 | "syn 2.0.87", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "syn" 1883 | version = "1.0.109" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1886 | dependencies = [ 1887 | "proc-macro2", 1888 | "quote", 1889 | "unicode-ident", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "syn" 1894 | version = "2.0.87" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 1897 | dependencies = [ 1898 | "proc-macro2", 1899 | "quote", 1900 | "unicode-ident", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "sync_wrapper" 1905 | version = "0.1.2" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1908 | 1909 | [[package]] 1910 | name = "synstructure" 1911 | version = "0.13.1" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1914 | dependencies = [ 1915 | "proc-macro2", 1916 | "quote", 1917 | "syn 2.0.87", 1918 | ] 1919 | 1920 | [[package]] 1921 | name = "system-configuration" 1922 | version = "0.5.1" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1925 | dependencies = [ 1926 | "bitflags 1.3.2", 1927 | "core-foundation", 1928 | "system-configuration-sys", 1929 | ] 1930 | 1931 | [[package]] 1932 | name = "system-configuration-sys" 1933 | version = "0.5.0" 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" 1935 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1936 | dependencies = [ 1937 | "core-foundation-sys", 1938 | "libc", 1939 | ] 1940 | 1941 | [[package]] 1942 | name = "tempfile" 1943 | version = "3.14.0" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 1946 | dependencies = [ 1947 | "cfg-if", 1948 | "fastrand", 1949 | "once_cell", 1950 | "rustix", 1951 | "windows-sys 0.59.0", 1952 | ] 1953 | 1954 | [[package]] 1955 | name = "termcolor" 1956 | version = "1.4.1" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1959 | dependencies = [ 1960 | "winapi-util", 1961 | ] 1962 | 1963 | [[package]] 1964 | name = "textwrap" 1965 | version = "0.16.1" 1966 | source = "registry+https://github.com/rust-lang/crates.io-index" 1967 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1968 | dependencies = [ 1969 | "smawk", 1970 | "unicode-linebreak", 1971 | "unicode-width 0.1.14", 1972 | ] 1973 | 1974 | [[package]] 1975 | name = "thiserror" 1976 | version = "1.0.69" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1979 | dependencies = [ 1980 | "thiserror-impl", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "thiserror-impl" 1985 | version = "1.0.69" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1988 | dependencies = [ 1989 | "proc-macro2", 1990 | "quote", 1991 | "syn 2.0.87", 1992 | ] 1993 | 1994 | [[package]] 1995 | name = "tinystr" 1996 | version = "0.7.6" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1999 | dependencies = [ 2000 | "displaydoc", 2001 | "zerovec", 2002 | ] 2003 | 2004 | [[package]] 2005 | name = "tokio" 2006 | version = "1.41.1" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" 2009 | dependencies = [ 2010 | "backtrace", 2011 | "bytes", 2012 | "libc", 2013 | "mio 1.0.2", 2014 | "pin-project-lite", 2015 | "signal-hook-registry", 2016 | "socket2", 2017 | "tokio-macros", 2018 | "windows-sys 0.52.0", 2019 | ] 2020 | 2021 | [[package]] 2022 | name = "tokio-macros" 2023 | version = "2.4.0" 2024 | source = "registry+https://github.com/rust-lang/crates.io-index" 2025 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 2026 | dependencies = [ 2027 | "proc-macro2", 2028 | "quote", 2029 | "syn 2.0.87", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "tokio-rustls" 2034 | version = "0.24.1" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2037 | dependencies = [ 2038 | "rustls", 2039 | "tokio", 2040 | ] 2041 | 2042 | [[package]] 2043 | name = "tokio-util" 2044 | version = "0.7.12" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 2047 | dependencies = [ 2048 | "bytes", 2049 | "futures-core", 2050 | "futures-sink", 2051 | "pin-project-lite", 2052 | "tokio", 2053 | ] 2054 | 2055 | [[package]] 2056 | name = "tower-service" 2057 | version = "0.3.3" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2060 | 2061 | [[package]] 2062 | name = "tracing" 2063 | version = "0.1.40" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2066 | dependencies = [ 2067 | "pin-project-lite", 2068 | "tracing-core", 2069 | ] 2070 | 2071 | [[package]] 2072 | name = "tracing-core" 2073 | version = "0.1.32" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2076 | dependencies = [ 2077 | "once_cell", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "try-lock" 2082 | version = "0.2.5" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2085 | 2086 | [[package]] 2087 | name = "typed-builder" 2088 | version = "0.20.0" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" 2091 | dependencies = [ 2092 | "typed-builder-macro", 2093 | ] 2094 | 2095 | [[package]] 2096 | name = "typed-builder-macro" 2097 | version = "0.20.0" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" 2100 | dependencies = [ 2101 | "proc-macro2", 2102 | "quote", 2103 | "syn 2.0.87", 2104 | ] 2105 | 2106 | [[package]] 2107 | name = "unicode-ident" 2108 | version = "1.0.13" 2109 | source = "registry+https://github.com/rust-lang/crates.io-index" 2110 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 2111 | 2112 | [[package]] 2113 | name = "unicode-linebreak" 2114 | version = "0.1.5" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 2117 | 2118 | [[package]] 2119 | name = "unicode-segmentation" 2120 | version = "1.12.0" 2121 | source = "registry+https://github.com/rust-lang/crates.io-index" 2122 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2123 | 2124 | [[package]] 2125 | name = "unicode-truncate" 2126 | version = "1.1.0" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 2129 | dependencies = [ 2130 | "itertools", 2131 | "unicode-segmentation", 2132 | "unicode-width 0.1.14", 2133 | ] 2134 | 2135 | [[package]] 2136 | name = "unicode-width" 2137 | version = "0.1.14" 2138 | source = "registry+https://github.com/rust-lang/crates.io-index" 2139 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2140 | 2141 | [[package]] 2142 | name = "unicode-width" 2143 | version = "0.2.0" 2144 | source = "registry+https://github.com/rust-lang/crates.io-index" 2145 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 2146 | 2147 | [[package]] 2148 | name = "untrusted" 2149 | version = "0.9.0" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2152 | 2153 | [[package]] 2154 | name = "url" 2155 | version = "2.5.3" 2156 | source = "registry+https://github.com/rust-lang/crates.io-index" 2157 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 2158 | dependencies = [ 2159 | "form_urlencoded", 2160 | "idna", 2161 | "percent-encoding", 2162 | ] 2163 | 2164 | [[package]] 2165 | name = "utf16_iter" 2166 | version = "1.0.5" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2169 | 2170 | [[package]] 2171 | name = "utf8_iter" 2172 | version = "1.0.4" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2175 | 2176 | [[package]] 2177 | name = "utf8parse" 2178 | version = "0.2.2" 2179 | source = "registry+https://github.com/rust-lang/crates.io-index" 2180 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2181 | 2182 | [[package]] 2183 | name = "walkdir" 2184 | version = "2.5.0" 2185 | source = "registry+https://github.com/rust-lang/crates.io-index" 2186 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2187 | dependencies = [ 2188 | "same-file", 2189 | "winapi-util", 2190 | ] 2191 | 2192 | [[package]] 2193 | name = "want" 2194 | version = "0.3.1" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2197 | dependencies = [ 2198 | "try-lock", 2199 | ] 2200 | 2201 | [[package]] 2202 | name = "wasi" 2203 | version = "0.11.0+wasi-snapshot-preview1" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2206 | 2207 | [[package]] 2208 | name = "wasm-bindgen" 2209 | version = "0.2.95" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 2212 | dependencies = [ 2213 | "cfg-if", 2214 | "once_cell", 2215 | "wasm-bindgen-macro", 2216 | ] 2217 | 2218 | [[package]] 2219 | name = "wasm-bindgen-backend" 2220 | version = "0.2.95" 2221 | source = "registry+https://github.com/rust-lang/crates.io-index" 2222 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 2223 | dependencies = [ 2224 | "bumpalo", 2225 | "log", 2226 | "once_cell", 2227 | "proc-macro2", 2228 | "quote", 2229 | "syn 2.0.87", 2230 | "wasm-bindgen-shared", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "wasm-bindgen-futures" 2235 | version = "0.4.45" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 2238 | dependencies = [ 2239 | "cfg-if", 2240 | "js-sys", 2241 | "wasm-bindgen", 2242 | "web-sys", 2243 | ] 2244 | 2245 | [[package]] 2246 | name = "wasm-bindgen-macro" 2247 | version = "0.2.95" 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" 2249 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 2250 | dependencies = [ 2251 | "quote", 2252 | "wasm-bindgen-macro-support", 2253 | ] 2254 | 2255 | [[package]] 2256 | name = "wasm-bindgen-macro-support" 2257 | version = "0.2.95" 2258 | source = "registry+https://github.com/rust-lang/crates.io-index" 2259 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 2260 | dependencies = [ 2261 | "proc-macro2", 2262 | "quote", 2263 | "syn 2.0.87", 2264 | "wasm-bindgen-backend", 2265 | "wasm-bindgen-shared", 2266 | ] 2267 | 2268 | [[package]] 2269 | name = "wasm-bindgen-shared" 2270 | version = "0.2.95" 2271 | source = "registry+https://github.com/rust-lang/crates.io-index" 2272 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 2273 | 2274 | [[package]] 2275 | name = "wasm-streams" 2276 | version = "0.4.2" 2277 | source = "registry+https://github.com/rust-lang/crates.io-index" 2278 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2279 | dependencies = [ 2280 | "futures-util", 2281 | "js-sys", 2282 | "wasm-bindgen", 2283 | "wasm-bindgen-futures", 2284 | "web-sys", 2285 | ] 2286 | 2287 | [[package]] 2288 | name = "web-sys" 2289 | version = "0.3.72" 2290 | source = "registry+https://github.com/rust-lang/crates.io-index" 2291 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 2292 | dependencies = [ 2293 | "js-sys", 2294 | "wasm-bindgen", 2295 | ] 2296 | 2297 | [[package]] 2298 | name = "web-time" 2299 | version = "1.1.0" 2300 | source = "registry+https://github.com/rust-lang/crates.io-index" 2301 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2302 | dependencies = [ 2303 | "js-sys", 2304 | "wasm-bindgen", 2305 | ] 2306 | 2307 | [[package]] 2308 | name = "webpki-roots" 2309 | version = "0.25.4" 2310 | source = "registry+https://github.com/rust-lang/crates.io-index" 2311 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2312 | 2313 | [[package]] 2314 | name = "winapi" 2315 | version = "0.3.9" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2318 | dependencies = [ 2319 | "winapi-i686-pc-windows-gnu", 2320 | "winapi-x86_64-pc-windows-gnu", 2321 | ] 2322 | 2323 | [[package]] 2324 | name = "winapi-i686-pc-windows-gnu" 2325 | version = "0.4.0" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2328 | 2329 | [[package]] 2330 | name = "winapi-util" 2331 | version = "0.1.9" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2334 | dependencies = [ 2335 | "windows-sys 0.59.0", 2336 | ] 2337 | 2338 | [[package]] 2339 | name = "winapi-x86_64-pc-windows-gnu" 2340 | version = "0.4.0" 2341 | source = "registry+https://github.com/rust-lang/crates.io-index" 2342 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2343 | 2344 | [[package]] 2345 | name = "windows-sys" 2346 | version = "0.48.0" 2347 | source = "registry+https://github.com/rust-lang/crates.io-index" 2348 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2349 | dependencies = [ 2350 | "windows-targets 0.48.5", 2351 | ] 2352 | 2353 | [[package]] 2354 | name = "windows-sys" 2355 | version = "0.52.0" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2358 | dependencies = [ 2359 | "windows-targets 0.52.6", 2360 | ] 2361 | 2362 | [[package]] 2363 | name = "windows-sys" 2364 | version = "0.59.0" 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" 2366 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2367 | dependencies = [ 2368 | "windows-targets 0.52.6", 2369 | ] 2370 | 2371 | [[package]] 2372 | name = "windows-targets" 2373 | version = "0.48.5" 2374 | source = "registry+https://github.com/rust-lang/crates.io-index" 2375 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2376 | dependencies = [ 2377 | "windows_aarch64_gnullvm 0.48.5", 2378 | "windows_aarch64_msvc 0.48.5", 2379 | "windows_i686_gnu 0.48.5", 2380 | "windows_i686_msvc 0.48.5", 2381 | "windows_x86_64_gnu 0.48.5", 2382 | "windows_x86_64_gnullvm 0.48.5", 2383 | "windows_x86_64_msvc 0.48.5", 2384 | ] 2385 | 2386 | [[package]] 2387 | name = "windows-targets" 2388 | version = "0.52.6" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2391 | dependencies = [ 2392 | "windows_aarch64_gnullvm 0.52.6", 2393 | "windows_aarch64_msvc 0.52.6", 2394 | "windows_i686_gnu 0.52.6", 2395 | "windows_i686_gnullvm", 2396 | "windows_i686_msvc 0.52.6", 2397 | "windows_x86_64_gnu 0.52.6", 2398 | "windows_x86_64_gnullvm 0.52.6", 2399 | "windows_x86_64_msvc 0.52.6", 2400 | ] 2401 | 2402 | [[package]] 2403 | name = "windows_aarch64_gnullvm" 2404 | version = "0.48.5" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2407 | 2408 | [[package]] 2409 | name = "windows_aarch64_gnullvm" 2410 | version = "0.52.6" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2413 | 2414 | [[package]] 2415 | name = "windows_aarch64_msvc" 2416 | version = "0.48.5" 2417 | source = "registry+https://github.com/rust-lang/crates.io-index" 2418 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2419 | 2420 | [[package]] 2421 | name = "windows_aarch64_msvc" 2422 | version = "0.52.6" 2423 | source = "registry+https://github.com/rust-lang/crates.io-index" 2424 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2425 | 2426 | [[package]] 2427 | name = "windows_i686_gnu" 2428 | version = "0.48.5" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2431 | 2432 | [[package]] 2433 | name = "windows_i686_gnu" 2434 | version = "0.52.6" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2437 | 2438 | [[package]] 2439 | name = "windows_i686_gnullvm" 2440 | version = "0.52.6" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2443 | 2444 | [[package]] 2445 | name = "windows_i686_msvc" 2446 | version = "0.48.5" 2447 | source = "registry+https://github.com/rust-lang/crates.io-index" 2448 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2449 | 2450 | [[package]] 2451 | name = "windows_i686_msvc" 2452 | version = "0.52.6" 2453 | source = "registry+https://github.com/rust-lang/crates.io-index" 2454 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2455 | 2456 | [[package]] 2457 | name = "windows_x86_64_gnu" 2458 | version = "0.48.5" 2459 | source = "registry+https://github.com/rust-lang/crates.io-index" 2460 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2461 | 2462 | [[package]] 2463 | name = "windows_x86_64_gnu" 2464 | version = "0.52.6" 2465 | source = "registry+https://github.com/rust-lang/crates.io-index" 2466 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2467 | 2468 | [[package]] 2469 | name = "windows_x86_64_gnullvm" 2470 | version = "0.48.5" 2471 | source = "registry+https://github.com/rust-lang/crates.io-index" 2472 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2473 | 2474 | [[package]] 2475 | name = "windows_x86_64_gnullvm" 2476 | version = "0.52.6" 2477 | source = "registry+https://github.com/rust-lang/crates.io-index" 2478 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2479 | 2480 | [[package]] 2481 | name = "windows_x86_64_msvc" 2482 | version = "0.48.5" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2485 | 2486 | [[package]] 2487 | name = "windows_x86_64_msvc" 2488 | version = "0.52.6" 2489 | source = "registry+https://github.com/rust-lang/crates.io-index" 2490 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2491 | 2492 | [[package]] 2493 | name = "winreg" 2494 | version = "0.50.0" 2495 | source = "registry+https://github.com/rust-lang/crates.io-index" 2496 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2497 | dependencies = [ 2498 | "cfg-if", 2499 | "windows-sys 0.48.0", 2500 | ] 2501 | 2502 | [[package]] 2503 | name = "write16" 2504 | version = "1.0.0" 2505 | source = "registry+https://github.com/rust-lang/crates.io-index" 2506 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2507 | 2508 | [[package]] 2509 | name = "writeable" 2510 | version = "0.5.5" 2511 | source = "registry+https://github.com/rust-lang/crates.io-index" 2512 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2513 | 2514 | [[package]] 2515 | name = "yansi" 2516 | version = "1.0.1" 2517 | source = "registry+https://github.com/rust-lang/crates.io-index" 2518 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 2519 | 2520 | [[package]] 2521 | name = "yoke" 2522 | version = "0.7.4" 2523 | source = "registry+https://github.com/rust-lang/crates.io-index" 2524 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 2525 | dependencies = [ 2526 | "serde", 2527 | "stable_deref_trait", 2528 | "yoke-derive", 2529 | "zerofrom", 2530 | ] 2531 | 2532 | [[package]] 2533 | name = "yoke-derive" 2534 | version = "0.7.4" 2535 | source = "registry+https://github.com/rust-lang/crates.io-index" 2536 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 2537 | dependencies = [ 2538 | "proc-macro2", 2539 | "quote", 2540 | "syn 2.0.87", 2541 | "synstructure", 2542 | ] 2543 | 2544 | [[package]] 2545 | name = "zerofrom" 2546 | version = "0.1.4" 2547 | source = "registry+https://github.com/rust-lang/crates.io-index" 2548 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 2549 | dependencies = [ 2550 | "zerofrom-derive", 2551 | ] 2552 | 2553 | [[package]] 2554 | name = "zerofrom-derive" 2555 | version = "0.1.4" 2556 | source = "registry+https://github.com/rust-lang/crates.io-index" 2557 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 2558 | dependencies = [ 2559 | "proc-macro2", 2560 | "quote", 2561 | "syn 2.0.87", 2562 | "synstructure", 2563 | ] 2564 | 2565 | [[package]] 2566 | name = "zerovec" 2567 | version = "0.10.4" 2568 | source = "registry+https://github.com/rust-lang/crates.io-index" 2569 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2570 | dependencies = [ 2571 | "yoke", 2572 | "zerofrom", 2573 | "zerovec-derive", 2574 | ] 2575 | 2576 | [[package]] 2577 | name = "zerovec-derive" 2578 | version = "0.10.3" 2579 | source = "registry+https://github.com/rust-lang/crates.io-index" 2580 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2581 | dependencies = [ 2582 | "proc-macro2", 2583 | "quote", 2584 | "syn 2.0.87", 2585 | ] 2586 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*"] 4 | 5 | [profile.release] 6 | strip = true 7 | lto = true 8 | opt-level = "z" -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | pre-build = [ 3 | "curl -fsSL https://deb.nodesource.com/setup_16.x | DEBIAN_FRONTEND=noninteractive bash", 4 | "DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs" 5 | ] 6 | 7 | [build.env] 8 | passthrough = ["GRACO_HOME=/tmp"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Will Crichton 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. -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | skip_core_tasks = true 3 | default_to_workspace = false 4 | 5 | [tasks.watch] 6 | script = "cargo watch -x 'install --path crates/depot --debug --locked --offline'" 7 | 8 | [tasks.install] 9 | script = "cargo install --path crates/depot --locked" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Depot: A Javascript devtool orchestrator 2 | 3 | Screen Shot 2023-07-18 at 11 26 06 AM 4 | 5 | Depot (formerly Graco) is a tool for orchestrating other Javascript devtools. As an analogy: 6 | * Depot is like [Cargo], but for Javascript. 7 | * Depot is like [create-react-app], but for people who like software engineering. 8 | * Depot is like the [`"scripts"` field of package.json][package.json], but with more power and flexibility. 9 | 10 | Depot works on Javascript workspaces that have been created by Depot, specifically those using the [model JS workspace] format. Depot supports the following commands: 11 | 12 | * `depot new` - creates a new workspace or package with devtools preinstalled 13 | * `depot init` - installs workspace dependencies with [pnpm] 14 | * `depot build` - type-checks with [Typescript], lints with [Biome], and: 15 | * For libraries, transpiles with [Typescript] 16 | * For scripts and websites, bundles with [Vite] 17 | * `depot test` - runs tests with [Vitest] 18 | * `depot fmt` - formats source files with [Biome] 19 | * `depot doc` - generates documentation with [Typedoc] 20 | 21 | A few benefits of using Depot: 22 | * Depot works with either browser or Node packages. 23 | * Depot automatically runs command dependencies. For example, `depot test` will run `depot build`, and `depot build` will run `depot init`. 24 | * Depot provides an interactive terminal interface for showing the running output of processes when building in watch mode. 25 | 26 | 27 | ## Installation 28 | 29 | As prerequisites, you must have [NodeJS][node-install] (≥20) and [pnpm][pnpm-install] (≥9.9) installed on your computer. 30 | 31 | The [install script] will download a prebuilt binary if possible. Run the script as follows: 32 | 33 | ``` 34 | curl https://raw.githubusercontent.com/cognitive-engineering-lab/depot/main/scripts/install.sh | sh 35 | ``` 36 | 37 | Alternatively, you can follow one of these installation methods: 38 | 39 | ### From crates.io 40 | 41 | ``` 42 | cargo install depot-js --locked 43 | ``` 44 | 45 | ### From source 46 | 47 | ``` 48 | git clone https://github.com/cognitive-engineering-lab/depot 49 | cd depot 50 | cargo install --path crates/depot --locked 51 | ``` 52 | 53 | ## Usage 54 | 55 | To get started, create a new package: 56 | 57 | ``` 58 | depot new my-lib 59 | ``` 60 | 61 | You can specify `--target ` to indicate that the package is a library (a Javascript package used by other packages), a website (an HTML site that uses Javascript), or a script (a Javascript program that would be either run on the CLI or included as a ` 600 | 601 | "# 602 | ) 603 | } 604 | 605 | fn new_package(self, root: &Path) -> Result<()> { 606 | let NewArgs { 607 | name, 608 | target, 609 | platform, 610 | .. 611 | } = &self.args; 612 | 613 | let src_dir = root.join("src"); 614 | utils::create_dir(src_dir)?; 615 | 616 | let tests_dir = root.join("tests"); 617 | utils::create_dir(tests_dir)?; 618 | 619 | let mut manifest = pj::PackageJson::builder().build(); 620 | manifest.name = Some(name.to_string()); 621 | manifest.version = Some(String::from("0.1.0")); 622 | manifest.type_ = Some(pj::Type::Module); 623 | 624 | let mut other: IndexMap = IndexMap::new(); 625 | let pkg_config = PackageDepotConfig { 626 | platform: *platform, 627 | target: Some(*target), 628 | ..Default::default() 629 | }; 630 | let ws_config = WorkspaceDepotConfig { 631 | depot_version: DEPOT_VERSION.to_string(), 632 | }; 633 | let mut config = serde_json::to_value(pkg_config)?; 634 | json_merge(&mut config, serde_json::to_value(ws_config)?); 635 | other.insert("depot".into(), config); 636 | 637 | // STUPID HACK: 638 | // - This npm bug (and I guess pnpm bug) causes platform-specific rollup packages to not be installed: 639 | // https://github.com/npm/cli/issues/4828 640 | // - A stupid patch is to use the Wasm build of Rollup: 641 | // https://github.com/vitejs/vite/issues/15167 642 | if self.ws_opt.is_none() { 643 | other.insert( 644 | "pnpm".into(), 645 | json!({ 646 | "overrides": { 647 | "rollup": "npm:@rollup/wasm-node" 648 | } 649 | }), 650 | ); 651 | } 652 | 653 | let mut files: FileVec = Vec::new(); 654 | 655 | let mut peer_dependencies: Vec<&str> = Vec::new(); 656 | let mut dev_dependencies: Vec<&str> = vec![]; 657 | 658 | if platform.is_browser() { 659 | dev_dependencies.extend(["jsdom"]); 660 | } 661 | 662 | if self.args.react { 663 | dev_dependencies.extend([ 664 | "react", 665 | "react-dom", 666 | "@types/react", 667 | "@types/react-dom", 668 | "@vitejs/plugin-react", 669 | "@testing-library/react", 670 | ]); 671 | } 672 | 673 | if self.args.vike { 674 | ensure!( 675 | target.is_site(), 676 | "--vike can only be used with --target site" 677 | ); 678 | 679 | dev_dependencies.push("vike"); 680 | 681 | if self.args.react { 682 | dev_dependencies.push("vike-react"); 683 | } 684 | } 685 | 686 | if self.args.sass { 687 | dev_dependencies.push("sass"); 688 | } 689 | 690 | let entry_point = match target { 691 | Target::Site => { 692 | ensure!( 693 | platform.is_browser(), 694 | "Must have platform=browser when target=site" 695 | ); 696 | 697 | dev_dependencies.push("normalize.css"); 698 | 699 | let css_name = if self.args.vike { "base" } else { "index" }; 700 | let css_path = format!("{css_name}.{}", if self.args.sass { "scss" } else { "css" }); 701 | 702 | if self.args.vike { 703 | ensure!(self.args.react, "Currently must use --react with --vike"); 704 | const CONFIG_SRC: &str = r#"import vikeReact from "vike-react/config"; 705 | import type { Config } from "vike/types"; 706 | 707 | export let config: Config = { 708 | extends: vikeReact, 709 | lang: "en-US" 710 | }; 711 | "#; 712 | files.push(("src/+config.ts".into(), CONFIG_SRC.into())); 713 | 714 | let head_src = format!( 715 | r#"import React from "react"; 716 | import "./{css_path}"; 717 | 718 | export let Head = () => ( 719 | <> 720 | 721 | 722 | ); 723 | "# 724 | ); 725 | files.push(("src/+Head.tsx".into(), head_src.into())); 726 | files.push((format!("src/{css_path}").into(), CSS.into())); 727 | 728 | const INDEX_SRC: &str = r#"import React from "react"; 729 | 730 | export default () => { 731 | return

Hello, world!

; 732 | }; 733 | "#; 734 | files.push(("src/index/+Page.tsx".into(), INDEX_SRC.into())); 735 | 736 | const TITLE_SRC: &str = r#"export let title = "Example Site"; 737 | "#; 738 | files.push(("src/index/+title.tsx".into(), TITLE_SRC.into())); 739 | } else { 740 | let (js_path, js_contents) = if self.args.react { 741 | ("index.tsx", REACT_INDEX) 742 | } else { 743 | ("index.ts", BASIC_INDEX) 744 | }; 745 | 746 | files.push(( 747 | "index.html".into(), 748 | Self::make_index_html(js_path, &css_path).into(), 749 | )); 750 | 751 | utils::create_dir(root.join("styles"))?; 752 | files.push((format!("styles/{css_path}").into(), CSS.into())); 753 | files.push((format!("src/{js_path}").into(), js_contents.into())); 754 | } 755 | 756 | None 757 | } 758 | Target::Script => { 759 | if platform.is_node() { 760 | manifest.bin = Some(pj::Binary::Object(indexmap! { 761 | name.name.clone() => format!("dist/{}.cjs", self.args.name) 762 | })); 763 | dev_dependencies.push("vite"); 764 | } 765 | let filename = if self.args.react { 766 | "main.tsx" 767 | } else { 768 | "main.ts" 769 | }; 770 | files.push((format!("src/{filename}").into(), MAIN.into())); 771 | 772 | Some(filename) 773 | } 774 | Target::Lib => { 775 | manifest.main = Some(String::from("dist/lib.js")); 776 | manifest.files = Some(vec![String::from("dist")]); 777 | 778 | if self.args.react { 779 | peer_dependencies.push("react"); 780 | } 781 | 782 | let main_export = pj::ExportsObject::builder() 783 | .default("./dist/lib.js") 784 | .build(); 785 | let sub_exports = pj::ExportsObject::builder().default("./dist/*.js").build(); 786 | manifest.exports = Some(pj::Exports::Nested(indexmap! { 787 | ".".into() => main_export, 788 | "./*".into() => sub_exports, 789 | })); 790 | 791 | files.push(("tests/add.test.ts".into(), TEST.into())); 792 | 793 | match &self.ws_opt { 794 | Some(ws) => self.update_typedoc_config(ws)?, 795 | None => files.extend(self.make_typedoc_config()?), 796 | } 797 | 798 | let filename = if self.args.react { "lib.tsx" } else { "lib.ts" }; 799 | files.push((format!("src/{filename}").into(), LIB.into())); 800 | 801 | Some(filename) 802 | } 803 | }; 804 | 805 | manifest.other = Some(other); 806 | 807 | files.push(( 808 | "package.json".into(), 809 | serde_json::to_string_pretty(&manifest)?.into(), 810 | )); 811 | files.extend(self.make_tsconfig()?); 812 | files.extend(self.make_biome_config()?); 813 | files.extend(self.make_vite_config(entry_point)); 814 | 815 | if self.ws_opt.is_none() { 816 | files.extend(Self::make_gitignore()); 817 | } 818 | 819 | for (rel_path, contents) in files { 820 | let abs_path = root.join(rel_path); 821 | utils::create_dir_if_missing(abs_path.parent().unwrap())?; 822 | utils::write(abs_path, contents.as_bytes())?; 823 | } 824 | 825 | if !peer_dependencies.is_empty() { 826 | self.run_pnpm(|pnpm| { 827 | pnpm 828 | .args(["add", "--save-peer"]) 829 | .args(&peer_dependencies) 830 | .current_dir(root); 831 | })?; 832 | } 833 | 834 | if !dev_dependencies.is_empty() { 835 | self.run_pnpm(|pnpm| { 836 | pnpm 837 | .args(["add", "--save-dev"]) 838 | .args(&dev_dependencies) 839 | .current_dir(root); 840 | })?; 841 | } 842 | 843 | match &self.ws_opt { 844 | Some(ws) => self.install_ws_dependencies(&ws.root, true)?, 845 | None => self.install_ws_dependencies(root, false)?, 846 | } 847 | 848 | Ok(()) 849 | } 850 | 851 | pub fn run(self) -> Result<()> { 852 | ensure!( 853 | !(self.ws_opt.is_some() && self.args.workspace), 854 | "Cannot create a new workspace inside an existing workspace" 855 | ); 856 | 857 | let name = &self.args.name; 858 | let parent_dir = match &self.ws_opt { 859 | Some(ws) => ws.root.join("packages"), 860 | None => env::current_dir()?, 861 | }; 862 | let root = parent_dir.join(&name.name); 863 | utils::create_dir(&root)?; 864 | 865 | if self.args.workspace { 866 | self.new_workspace(&root) 867 | } else { 868 | self.new_package(&root) 869 | } 870 | } 871 | } 872 | -------------------------------------------------------------------------------- /crates/depot/src/commands/test.rs: -------------------------------------------------------------------------------- 1 | use super::build::{BuildArgs, BuildCommand}; 2 | use crate::workspace::{Command, CommandRuntime, CoreCommand, PackageCommand, package::Package}; 3 | use anyhow::{Context, Result}; 4 | 5 | /// Run tests via vitest 6 | #[derive(clap::Parser, Default, Debug)] 7 | pub struct TestArgs { 8 | /// If true, then rerun tests when files change 9 | #[clap(short, long, action)] 10 | watch: bool, 11 | 12 | /// Additional arguments to pass to vitest 13 | #[arg(last = true)] 14 | pub vitest_args: Option, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct TestCommand { 19 | args: TestArgs, 20 | } 21 | 22 | impl CoreCommand for TestCommand { 23 | fn name(&self) -> String { 24 | "test".into() 25 | } 26 | } 27 | 28 | #[async_trait::async_trait] 29 | impl PackageCommand for TestCommand { 30 | async fn run_pkg(&self, pkg: &Package) -> Result<()> { 31 | if !pkg.root.join("tests").exists() { 32 | return Ok(()); 33 | } 34 | 35 | let vitest_args = match &self.args.vitest_args { 36 | Some(vitest_args) => Some(shlex::split(vitest_args).context("Failed to parse vitest args")?), 37 | None => None, 38 | }; 39 | 40 | pkg 41 | .exec("vitest", |cmd| { 42 | let subcmd = if self.args.watch { "watch" } else { "run" }; 43 | cmd.arg(subcmd); 44 | 45 | cmd.arg("--passWithNoTests"); 46 | 47 | if let Some(vitest_args) = vitest_args { 48 | cmd.args(vitest_args); 49 | } 50 | }) 51 | .await 52 | } 53 | 54 | fn deps(&self) -> Vec { 55 | vec![BuildCommand::new(BuildArgs::default()).kind()] 56 | } 57 | 58 | fn runtime(&self) -> CommandRuntime { 59 | if self.args.watch { 60 | CommandRuntime::RunForever 61 | } else { 62 | CommandRuntime::WaitForDependencies 63 | } 64 | } 65 | } 66 | 67 | impl TestCommand { 68 | pub fn new(args: TestArgs) -> Self { 69 | TestCommand { args } 70 | } 71 | 72 | pub fn kind(self) -> Command { 73 | Command::package(self) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/depot/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow( 3 | clippy::format_collect, 4 | clippy::similar_names, 5 | clippy::module_name_repetitions, 6 | clippy::single_match_else, 7 | clippy::items_after_statements 8 | )] 9 | 10 | use self::commands::Command; 11 | use anyhow::{Result, bail}; 12 | use clap::Parser; 13 | use commands::{ 14 | build::BuildCommand, clean::CleanCommand, doc::DocCommand, fix::FixCommand, fmt::FmtCommand, 15 | init::InitCommand, new::NewCommand, test::TestCommand, 16 | }; 17 | use workspace::{Workspace, package::PackageName}; 18 | 19 | mod commands; 20 | mod logger; 21 | mod utils; 22 | mod workspace; 23 | 24 | #[derive(clap::Parser, Default)] 25 | pub struct CommonArgs { 26 | /// Only run the command for a given package and its dependencies 27 | #[clap(short, long)] 28 | package: Option, 29 | 30 | /// Enable incremental compilation 31 | #[clap(long)] 32 | incremental: bool, 33 | 34 | /// Disable fullscreen UI 35 | #[clap(long)] 36 | no_fullscreen: bool, 37 | } 38 | 39 | #[derive(clap::Parser)] 40 | #[command(name = "depot", author, version, about, long_about = None)] 41 | struct Args { 42 | #[command(subcommand)] 43 | command: Command, 44 | 45 | #[command(flatten)] 46 | common: CommonArgs, 47 | } 48 | 49 | #[allow(clippy::missing_errors_doc)] 50 | pub async fn run() -> Result<()> { 51 | let Args { command, common } = Args::parse(); 52 | 53 | if utils::find_node().is_none() { 54 | bail!( 55 | "Failed to find `node` installed on your path. Depot requires NodeJS to be installed. See: https://nodejs.org/en/download/package-manager" 56 | ); 57 | } 58 | 59 | if utils::find_pnpm(None).is_none() { 60 | bail!( 61 | "Failed to find `pnpm` installed on your path. Depot requires pnpm to be installed. See: https://pnpm.io/installation" 62 | ) 63 | } 64 | 65 | let command = match command { 66 | Command::New(args) => return NewCommand::new(args).await.run(), 67 | command => command, 68 | }; 69 | 70 | let ws = Workspace::load(None, common).await?; 71 | 72 | // TODO: merge all tasks into a single task graph like Cargo 73 | let command = match command { 74 | Command::Init(args) => InitCommand::new(args).kind(), 75 | Command::Build(args) => BuildCommand::new(args).kind(), 76 | Command::Test(args) => TestCommand::new(args).kind(), 77 | Command::Fmt(args) => FmtCommand::new(args).kind(), 78 | Command::Clean(args) => CleanCommand::new(args).kind(), 79 | Command::Doc(args) => DocCommand::new(args).kind(), 80 | Command::Fix(args) => FixCommand::new(args).kind(), 81 | Command::New(..) => unreachable!(), 82 | }; 83 | 84 | ws.run(command).await?; 85 | 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /crates/depot/src/logger/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ringbuffer; 2 | pub mod ui; 3 | -------------------------------------------------------------------------------- /crates/depot/src/logger/ringbuffer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{VecDeque, vec_deque}; 2 | 3 | pub struct RingBuffer { 4 | data: VecDeque, 5 | max_capacity: usize, 6 | } 7 | 8 | const DEFAULT_MAX_CAPACITY: usize = 1024; 9 | 10 | #[allow(unused)] 11 | impl RingBuffer { 12 | pub fn new() -> Self { 13 | RingBuffer { 14 | data: VecDeque::new(), 15 | max_capacity: DEFAULT_MAX_CAPACITY, 16 | } 17 | } 18 | 19 | pub fn with_max_capacity(max_capacity: usize) -> Self { 20 | RingBuffer { 21 | data: VecDeque::new(), 22 | max_capacity, 23 | } 24 | } 25 | 26 | pub fn push(&mut self, log: T) { 27 | if self.data.len() == self.max_capacity { 28 | self.data.pop_front(); 29 | } 30 | self.data.push_back(log); 31 | } 32 | 33 | pub fn iter(&self) -> vec_deque::Iter<'_, T> { 34 | self.data.iter() 35 | } 36 | 37 | pub fn clear(&mut self) { 38 | self.data.clear(); 39 | } 40 | 41 | pub fn len(&self) -> usize { 42 | self.data.len() 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_log_buffer() { 48 | let mut buffer = RingBuffer::with_max_capacity(4); 49 | 50 | macro_rules! extend { 51 | ($in:expr) => { 52 | for x in $in { 53 | buffer.push(x); 54 | } 55 | }; 56 | } 57 | 58 | macro_rules! contents { 59 | () => { 60 | buffer.iter().copied().collect::>() 61 | }; 62 | } 63 | 64 | extend!([0, 1]); 65 | assert_eq!(contents!(), vec![0, 1]); 66 | 67 | extend!([2]); 68 | assert_eq!(contents!(), vec![0, 1, 2]); 69 | 70 | extend!([3, 4]); 71 | assert_eq!(contents!(), vec![1, 2, 3, 4]); 72 | 73 | extend!([5, 6]); 74 | assert_eq!(contents!(), vec![3, 4, 5, 6]); 75 | 76 | extend!([7, 8, 9, 10, 11]); 77 | assert_eq!(contents!(), vec![8, 9, 10, 11]) 78 | } 79 | -------------------------------------------------------------------------------- /crates/depot/src/logger/ui.rs: -------------------------------------------------------------------------------- 1 | use ansi_to_tui::IntoText; 2 | use anyhow::{Context, Result}; 3 | use crossterm::{ 4 | event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers}, 5 | execute, 6 | style::{Color, ResetColor, SetForegroundColor}, 7 | terminal::{EnterAlternateScreen, LeaveAlternateScreen}, 8 | }; 9 | use futures::StreamExt; 10 | use ratatui::{ 11 | layout::{Constraint, Direction, Layout}, 12 | prelude::Rect, 13 | style::{Modifier, Style}, 14 | text::{Line, Span, Text}, 15 | widgets::{Block, Borders, Paragraph, Tabs, Wrap}, 16 | }; 17 | use std::{ 18 | io::{Stdout, Write}, 19 | sync::{ 20 | Arc, Mutex, 21 | atomic::{AtomicIsize, Ordering}, 22 | }, 23 | time::Duration, 24 | }; 25 | use tokio::sync::Notify; 26 | 27 | use crate::workspace::{Workspace, process::Process}; 28 | 29 | pub struct FullscreenRenderer { 30 | terminal: Mutex, 31 | selected: AtomicIsize, 32 | } 33 | 34 | const TICK_RATE: Duration = Duration::from_millis(33); 35 | 36 | pub type TerminalBackend = ratatui::backend::CrosstermBackend; 37 | pub type Terminal = ratatui::Terminal; 38 | 39 | impl FullscreenRenderer { 40 | pub fn new() -> Result { 41 | let stdout = std::io::stdout(); 42 | let backend = ratatui::backend::CrosstermBackend::new(stdout); 43 | let mut terminal = ratatui::Terminal::new(backend).context("Failed to initialize terminal")?; 44 | 45 | crossterm::terminal::enable_raw_mode()?; 46 | execute!( 47 | terminal.backend_mut(), 48 | EnterAlternateScreen, 49 | EnableMouseCapture 50 | )?; 51 | terminal.clear()?; 52 | 53 | Ok(FullscreenRenderer { 54 | terminal: Mutex::new(terminal), 55 | selected: AtomicIsize::new(0), 56 | }) 57 | } 58 | 59 | fn build_tabs(ws: &Workspace, selected: usize) -> Option { 60 | ws.monorepo.then(|| { 61 | let titles = ws 62 | .package_display_order() 63 | .enumerate() 64 | .map(|(i, pkg)| { 65 | let pkg_name = pkg.name.to_string(); 66 | let mut style = Style::default(); 67 | if i == selected { 68 | style = style.add_modifier(Modifier::BOLD); 69 | } 70 | Span::styled(pkg_name, style) 71 | }) 72 | .collect::>(); 73 | Tabs::new(titles) 74 | }) 75 | } 76 | 77 | fn render_process_pane(f: &mut ratatui::Frame, process: &Process, slot: Rect) { 78 | let mut spans = Vec::new(); 79 | let height = slot.bottom() as usize; 80 | let stdout = process.stdout(); 81 | let last_lines = stdout.iter().rev().take(height).rev(); 82 | for line in last_lines { 83 | // TODO: distinguish stdout from stderr 84 | match line.line.into_text() { 85 | Ok(text) => spans.extend(text.lines), 86 | Err(e) => spans.push(Line::from(Span::raw(format!( 87 | "failed to parse line with error: {e:?}" 88 | )))), 89 | } 90 | } 91 | let p = Paragraph::new(Text::from(spans)) 92 | .block( 93 | Block::default() 94 | .title(process.script()) 95 | .borders(Borders::ALL), 96 | ) 97 | .wrap(Wrap { trim: false }); 98 | f.render_widget(p, slot); 99 | } 100 | } 101 | 102 | #[async_trait::async_trait] 103 | impl Renderer for FullscreenRenderer { 104 | fn render(&self, ws: &Workspace) -> Result<()> { 105 | let n = isize::try_from(ws.pkg_graph.nodes().count()).unwrap(); 106 | let selected_unbounded = self.selected.load(Ordering::SeqCst); 107 | let selected = usize::try_from((n + selected_unbounded % n) % n).unwrap(); 108 | let pkg = ws.package_display_order().nth(selected).unwrap(); 109 | let processes = pkg.processes(); 110 | 111 | let tabs = Self::build_tabs(ws, selected); 112 | 113 | let mut terminal = self.terminal.lock().unwrap(); 114 | terminal.draw(|f| { 115 | let size = f.area(); 116 | let constraints = if tabs.is_some() { 117 | vec![Constraint::Min(0), Constraint::Length(2)] 118 | } else { 119 | vec![Constraint::Min(0)] 120 | }; 121 | let canvas = Layout::default() 122 | .direction(Direction::Vertical) 123 | .constraints(constraints) 124 | .split(size); 125 | 126 | if let Some(tabs) = tabs { 127 | f.render_widget(tabs, canvas[1]); 128 | } 129 | 130 | let log_halves = Layout::default() 131 | .direction(Direction::Vertical) 132 | .constraints([Constraint::Ratio(7, 10), Constraint::Ratio(3, 10)]) 133 | .split(canvas[0]); 134 | let log_slots = log_halves 135 | .iter() 136 | .flat_map(|half| { 137 | Layout::default() 138 | .direction(Direction::Horizontal) 139 | .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) 140 | .split(*half) 141 | .to_vec() 142 | }) 143 | .collect::>(); 144 | 145 | for (process, slot) in processes.iter().zip(log_slots) { 146 | Self::render_process_pane(f, process, slot); 147 | } 148 | })?; 149 | 150 | Ok(()) 151 | } 152 | 153 | // TODO: This still occasionally drops inputs, seems to conflict with async-process. 154 | // See the note on `crossterm` dependency in Cargo.toml. 155 | // Maybe we should try to spawn this future in a separate thread? 156 | async fn handle_input(&self) -> Result { 157 | let mut reader = crossterm::event::EventStream::new(); 158 | while let Some(event) = reader.next().await { 159 | if let Event::Key(key) = event? { 160 | match key.code { 161 | KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => return Ok(true), 162 | KeyCode::Left => { 163 | self.selected.fetch_sub(1, Ordering::SeqCst); 164 | } 165 | KeyCode::Right => { 166 | self.selected.fetch_add(1, Ordering::SeqCst); 167 | } 168 | _ => {} 169 | } 170 | } 171 | } 172 | Ok(false) 173 | } 174 | 175 | fn complete(self, ws: &Workspace) -> Result<()> { 176 | let mut terminal = self.terminal.into_inner()?; 177 | 178 | crossterm::terminal::disable_raw_mode()?; 179 | execute!( 180 | terminal.backend_mut(), 181 | LeaveAlternateScreen, 182 | DisableMouseCapture 183 | )?; 184 | terminal.show_cursor()?; 185 | 186 | let inline_renderer = InlineRenderer::new(); 187 | inline_renderer.complete(ws)?; 188 | 189 | Ok(()) 190 | } 191 | } 192 | 193 | #[async_trait::async_trait] 194 | pub trait Renderer: Sized + Send + Sync { 195 | fn render(&self, ws: &Workspace) -> Result<()>; 196 | fn complete(self, ws: &Workspace) -> Result<()>; 197 | 198 | async fn handle_input(&self) -> Result { 199 | loop { 200 | tokio::time::sleep(Duration::MAX).await; 201 | } 202 | } 203 | 204 | async fn render_loop(mut self, ws: &Workspace, should_exit: &Arc) -> Result { 205 | let exit_early = { 206 | let this = &self; 207 | 208 | let input_future = this.handle_input(); 209 | tokio::pin!(input_future); 210 | 211 | let draw_future = async move { 212 | loop { 213 | this.render(ws).unwrap(); 214 | tokio::time::sleep(TICK_RATE).await; 215 | } 216 | }; 217 | tokio::pin!(draw_future); 218 | 219 | let exit_future = should_exit.notified(); 220 | tokio::pin!(exit_future); 221 | 222 | loop { 223 | tokio::select! { biased; 224 | () = &mut exit_future => break false, 225 | result = &mut input_future => { 226 | if result? { 227 | break true; 228 | } 229 | }, 230 | () = &mut draw_future => {} 231 | } 232 | } 233 | }; 234 | 235 | self.complete(ws)?; 236 | 237 | Ok(exit_early) 238 | } 239 | } 240 | 241 | // Clone of pnpm output format 242 | pub struct InlineRenderer { 243 | diff: Mutex, 244 | } 245 | 246 | impl InlineRenderer { 247 | pub fn new() -> Self { 248 | // TODO: do we need a different rendering strategy if there's no tty? 249 | let (w, h) = crossterm::terminal::size().unwrap_or((80, 40)); 250 | let diff = Mutex::new(ansi_diff::Diff::new((u32::from(w), u32::from(h)))); 251 | InlineRenderer { diff } 252 | } 253 | 254 | fn build_output(ws: &Workspace) -> Result { 255 | let mut output = Vec::new(); 256 | 257 | macro_rules! meta { 258 | ($($arg:tt)*) => { 259 | execute!(output, SetForegroundColor(Color::Magenta))?; 260 | write!(output, $($arg),*)?; 261 | execute!(output, ResetColor)?; 262 | } 263 | } 264 | 265 | let ws_processes = ws.processes(); 266 | if !ws_processes.is_empty() { 267 | // TODO: this repeats a lot of code with the block below 268 | for process in ws_processes.iter() { 269 | writeln!(&mut output, "ws/{}", process.script())?; 270 | 271 | let stdout = process.stdout(); 272 | for line in stdout.iter() { 273 | meta!("│ "); 274 | // TODO: distinguish stdout from stderr 275 | writeln!(&mut output, "{}", line.line)?; 276 | } 277 | let status = if process.finished() { 278 | "finished" 279 | } else { 280 | "running..." 281 | }; 282 | 283 | meta!("└─ {status}\n"); 284 | } 285 | } 286 | 287 | for pkg in ws.package_display_order() { 288 | let pkg_processes = pkg.processes(); 289 | if pkg_processes.is_empty() { 290 | continue; 291 | } 292 | 293 | if ws.monorepo { 294 | writeln!(&mut output, "{}", pkg.name)?; 295 | } 296 | 297 | for (j, process) in pkg_processes.iter().enumerate() { 298 | let last_process = j == pkg_processes.len() - 1; 299 | if ws.monorepo { 300 | if last_process { 301 | meta!("└─ "); 302 | } else { 303 | meta!("├─ "); 304 | } 305 | } 306 | writeln!(&mut output, "{}", process.script())?; 307 | 308 | let monorepo_prefix = if ws.monorepo { 309 | if last_process { " " } else { "│ " } 310 | } else { 311 | "" 312 | }; 313 | 314 | let stdout = process.stdout(); 315 | for line in stdout.iter() { 316 | meta!("{monorepo_prefix}│ "); 317 | // TODO: distinguish stdout from stderr 318 | writeln!(&mut output, "{}", line.line)?; 319 | } 320 | let status = if process.finished() { 321 | "finished" 322 | } else { 323 | "running..." 324 | }; 325 | 326 | meta!("{monorepo_prefix}└─ {status}\n"); 327 | } 328 | } 329 | 330 | Ok(String::from_utf8(output)?) 331 | } 332 | } 333 | 334 | impl Renderer for InlineRenderer { 335 | fn render(&self, ws: &Workspace) -> Result<()> { 336 | let output = Self::build_output(ws)?; 337 | print!("{}", self.diff.lock().unwrap().update(&output)); 338 | std::io::stdout().flush()?; 339 | Ok(()) 340 | } 341 | 342 | fn complete(self, ws: &Workspace) -> Result<()> { 343 | self.render(ws) 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /crates/depot/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{ 4 | fs, 5 | path::{Path, PathBuf}, 6 | process::Command, 7 | }; 8 | 9 | pub fn create_dir(path: impl AsRef) -> Result<()> { 10 | let path = path.as_ref(); 11 | fs::create_dir(path).with_context(|| format!("Could not create directory: {}", path.display())) 12 | } 13 | 14 | pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { 15 | let path = path.as_ref(); 16 | fs::write(path, contents).with_context(|| format!("Could not write to file: {}", path.display())) 17 | } 18 | 19 | pub fn create_dir_if_missing(p: impl AsRef) -> Result<()> { 20 | let p = p.as_ref(); 21 | if p.exists() { 22 | return Ok(()); 23 | } 24 | fs::create_dir_all(p).with_context(|| format!("Could not create directory: {}", p.display())) 25 | } 26 | 27 | pub fn get_git_root(cwd: &Path) -> Option { 28 | let mut cmd = Command::new("git"); 29 | cmd.args(["rev-parse", "--show-toplevel"]).current_dir(cwd); 30 | let output = cmd.output().ok()?; 31 | output 32 | .status 33 | .success() 34 | .then(|| PathBuf::from(String::from_utf8(output.stdout).unwrap().trim())) 35 | } 36 | 37 | pub fn remove_dir_all_if_exists(dir: impl AsRef) -> Result<()> { 38 | let dir = dir.as_ref(); 39 | if !dir.exists() { 40 | return Ok(()); 41 | } 42 | fs::remove_dir_all(dir).with_context(|| format!("Could not remove dir: {}", dir.display())) 43 | } 44 | 45 | #[macro_export] 46 | macro_rules! test_packages { 47 | ($($manifest:tt),*) => {{ 48 | use $crate::workspace::package::{Package, PackageManifest, Target, Platform, PackageDepotConfig}; 49 | let index = std::cell::Cell::new(0); 50 | [$({ 51 | let mut manifest: package_json_schema::PackageJson = 52 | serde_json::from_value(serde_json::json!($manifest)).expect("Manifest failed to parse"); 53 | let other = manifest.other.as_mut().unwrap(); 54 | if !other.contains_key("depot") { 55 | other.insert(String::from("depot"), serde_json::to_value(PackageDepotConfig { 56 | platform: Platform::Browser, 57 | ..Default::default() 58 | }).unwrap()); 59 | } 60 | let manifest = PackageManifest::from_json(manifest, std::path::Path::new("dummy.rs")).expect("Manifest failed to convert to Depot format"); 61 | let pkg = Package::from_parts("dummy.rs".into(), manifest, index.get(), Target::Lib).expect("Package failed to build"); 62 | index.set(index.get() + 1); 63 | pkg 64 | }),*] 65 | }}; 66 | } 67 | 68 | #[macro_export] 69 | macro_rules! shareable { 70 | ($name:ident, $inner:ty) => { 71 | #[derive(Clone)] 72 | pub struct $name(std::sync::Arc<$inner>); 73 | 74 | impl std::ops::Deref for $name { 75 | type Target = $inner; 76 | 77 | fn deref(&self) -> &Self::Target { 78 | &self.0 79 | } 80 | } 81 | 82 | impl std::hash::Hash for $name { 83 | fn hash(&self, hasher: &mut H) { 84 | std::ptr::hash(&*self.0, hasher) 85 | } 86 | } 87 | 88 | impl PartialEq for $name { 89 | fn eq(&self, other: &Self) -> bool { 90 | std::ptr::eq(&*self.0, &*other.0) 91 | } 92 | } 93 | 94 | impl Eq for $name {} 95 | 96 | impl $name { 97 | pub fn new(inner: $inner) -> Self { 98 | $name(Arc::new(inner)) 99 | } 100 | } 101 | }; 102 | } 103 | 104 | pub fn find_node() -> Option { 105 | pathsearch::find_executable_in_path("node") 106 | } 107 | 108 | pub fn find_pnpm(root: Option<&Path>) -> Option { 109 | let pnpm_in_root = root 110 | .map(|root| root.join("bin").join("pnpm")) 111 | .filter(|root| root.exists()); 112 | pnpm_in_root.or_else(|| pathsearch::find_executable_in_path("pnpm")) 113 | } 114 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/dep_graph.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, bail}; 2 | use bimap::BiHashMap; 3 | use petgraph::{ 4 | graph::DiGraph, 5 | prelude::NodeIndex, 6 | visit::{DfsPostOrder, Walker}, 7 | }; 8 | use std::hash::Hash; 9 | 10 | /// Generic data structure for representing dependencies between objects. 11 | pub struct DepGraph { 12 | graph: DiGraph<(), ()>, 13 | nodes: BiHashMap, 14 | roots: Vec, 15 | } 16 | 17 | impl DepGraph { 18 | /// Creates a [`DepGraph`] starting with a set of `roots`, and then finds the dependencies 19 | /// by iteratively calling `compute_deps` for node. 20 | /// 21 | /// Returns an error if the graph contains a cycle. 22 | pub fn build( 23 | roots: Vec, 24 | stringify: impl Fn(&T) -> String, 25 | compute_deps: impl Fn(&T) -> Vec, 26 | ) -> Result { 27 | let mut graph = DiGraph::new(); 28 | let mut nodes = BiHashMap::new(); 29 | let mut stack = vec![]; 30 | 31 | for root in &roots { 32 | let idx = graph.add_node(()); 33 | nodes.insert(root.clone(), idx); 34 | stack.push((idx, root.clone())); 35 | } 36 | 37 | while let Some((idx, el)) = stack.pop() { 38 | for dep in compute_deps(&el) { 39 | let dep_idx = match nodes.get_by_left(&dep) { 40 | Some(dep_idx) => *dep_idx, 41 | None => { 42 | let dep_idx = graph.add_node(()); 43 | nodes.insert(dep.clone(), dep_idx); 44 | stack.push((dep_idx, dep)); 45 | dep_idx 46 | } 47 | }; 48 | 49 | graph.add_edge(idx, dep_idx, ()); 50 | } 51 | } 52 | 53 | let sort = petgraph::algo::toposort(&graph, None); 54 | match sort { 55 | Ok(_) => Ok(DepGraph { 56 | graph, 57 | nodes, 58 | roots, 59 | }), 60 | Err(cycle) => { 61 | bail!( 62 | "Cycle detected in dependency graph involving node: {}", 63 | stringify(nodes.get_by_right(&cycle.node_id()).unwrap()) 64 | ) 65 | } 66 | } 67 | } 68 | 69 | fn index(&self, el: &T) -> NodeIndex { 70 | *self.nodes.get_by_left(el).unwrap() 71 | } 72 | 73 | fn value(&self, index: NodeIndex) -> &T { 74 | self.nodes.get_by_right(&index).unwrap() 75 | } 76 | 77 | pub fn nodes(&self) -> impl Iterator { 78 | self.nodes.iter().map(|(node, _)| node) 79 | } 80 | 81 | pub fn is_dependent_on(&self, el: &T, dep: &T) -> bool { 82 | self.all_deps_for(el).any(|dep2| dep == dep2) 83 | } 84 | 85 | pub fn immediate_deps_for<'a>(&'a self, el: &T) -> impl Iterator + 'a { 86 | self 87 | .graph 88 | .neighbors_directed(self.index(el), petgraph::Direction::Outgoing) 89 | .map(|node| self.value(node)) 90 | } 91 | 92 | pub fn all_deps_for<'a>(&'a self, el: &T) -> impl Iterator + 'a { 93 | let index = self.index(el); 94 | DfsPostOrder::new(&self.graph, index) 95 | .iter(&self.graph) 96 | .filter(move |dep| *dep != index) 97 | .map(|idx| self.value(idx)) 98 | } 99 | 100 | pub fn roots(&self) -> impl Iterator { 101 | self.roots.iter() 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | use super::*; 108 | use maplit::hashset; 109 | use std::collections::HashSet; 110 | 111 | #[test] 112 | fn dep_graph_basic() { 113 | let dg = DepGraph::build( 114 | vec![0, 1], 115 | |_| panic!(), 116 | |i| match i { 117 | 0 => vec![2], 118 | 1 => vec![2], 119 | 2 => vec![3], 120 | 3 => vec![], 121 | _ => unreachable!(), 122 | }, 123 | ) 124 | .unwrap(); 125 | 126 | let z_idx = dg.index(&0); 127 | assert_eq!(*dg.value(z_idx), 0); 128 | 129 | assert_eq!( 130 | dg.nodes().copied().collect::>(), 131 | hashset! { 0, 1, 2, 3 } 132 | ); 133 | 134 | assert!(dg.is_dependent_on(&0, &2)); 135 | assert!(dg.is_dependent_on(&0, &3)); 136 | assert!(!dg.is_dependent_on(&0, &1)); 137 | 138 | assert_eq!( 139 | dg.immediate_deps_for(&0).copied().collect::>(), 140 | hashset! { 2 } 141 | ); 142 | 143 | assert_eq!( 144 | dg.all_deps_for(&0).copied().collect::>(), 145 | hashset! { 2, 3 } 146 | ); 147 | 148 | assert_eq!( 149 | dg.roots().copied().collect::>(), 150 | hashset! { 0, 1 } 151 | ) 152 | } 153 | 154 | #[test] 155 | fn dep_graph_cycle() { 156 | let dg = DepGraph::build( 157 | vec![0], 158 | |i| i.to_string(), 159 | |i| match i { 160 | 0 => vec![1], 161 | 1 => vec![0], 162 | _ => unreachable!(), 163 | }, 164 | ); 165 | assert!(dg.is_err()); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/fingerprint.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use log::warn; 3 | use std::{ 4 | collections::HashMap, 5 | fs::{self, File}, 6 | io::{BufReader, BufWriter}, 7 | path::{Path, PathBuf}, 8 | time::SystemTime, 9 | }; 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | use crate::utils; 14 | 15 | /// Data structure for tracking when Depot commands were last executed. 16 | #[derive(Serialize, Deserialize, PartialEq, Eq)] 17 | pub struct Fingerprints { 18 | fingerprints: HashMap, 19 | } 20 | 21 | impl Fingerprints { 22 | pub fn new() -> Self { 23 | Fingerprints { 24 | fingerprints: HashMap::new(), 25 | } 26 | } 27 | 28 | /// Returns true if there is a recorded timestamp for `key`, and that timestamp is 29 | /// later than the modified time for all `files`. 30 | pub fn can_skip(&self, key: &str, files: impl IntoIterator) -> bool { 31 | match self.fingerprints.get(key) { 32 | None => false, 33 | Some(stored_time) => files 34 | .into_iter() 35 | .map(|path| fs::metadata(path)?.modified()) 36 | .filter_map(|res| match res { 37 | Ok(time) => Some(time), 38 | Err(e) => { 39 | warn!("Could not test for staleness: {e}"); 40 | None 41 | } 42 | }) 43 | .all(|time| time <= *stored_time), 44 | } 45 | } 46 | 47 | /// Sets the timestamp for `key` to the current time. 48 | pub fn update_time(&mut self, key: String) { 49 | self.fingerprints.insert(key, SystemTime::now()); 50 | } 51 | 52 | fn file_path(root: &Path) -> PathBuf { 53 | root.join("node_modules").join(".depot-fingerprints.json") 54 | } 55 | 56 | pub fn load(root: &Path) -> Result { 57 | let path = Self::file_path(root); 58 | if path.exists() { 59 | let f = File::open(path)?; 60 | let reader = BufReader::new(f); 61 | Ok(serde_json::from_reader(reader)?) 62 | } else { 63 | Ok(Fingerprints::new()) 64 | } 65 | } 66 | 67 | pub fn save(&self, root: &Path) -> Result<()> { 68 | let path = Self::file_path(root); 69 | utils::create_dir_if_missing(path.parent().unwrap())?; 70 | let f = File::create(path)?; 71 | let writer = BufWriter::new(f); 72 | serde_json::to_writer(writer, &self)?; 73 | Ok(()) 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod test { 79 | use super::*; 80 | use std::fs; 81 | use tempfile::TempDir; 82 | 83 | #[test] 84 | #[ignore = "Flaky or system-dependent test, not passing in CI"] 85 | fn fingerprints() -> Result<()> { 86 | let dir = TempDir::new()?; 87 | let dir = dir.path(); 88 | assert!(Fingerprints::load(dir)? == Fingerprints::new()); 89 | 90 | let file = dir.join("file.txt"); 91 | fs::write(&file, "Hello")?; 92 | 93 | let mut fingerprints = Fingerprints::new(); 94 | assert!(!fingerprints.can_skip("file.txt", vec![file.clone()])); 95 | 96 | fingerprints.update_time("file.txt".into()); 97 | assert!(fingerprints.can_skip("file.txt", vec![file.clone()])); 98 | 99 | fs::write(&file, "World")?; 100 | assert!(!fingerprints.can_skip("file.txt", vec![file.clone()])); 101 | 102 | fingerprints.save(dir)?; 103 | assert!(Fingerprints::load(dir)? == fingerprints); 104 | 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/manifest.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::{fs, path::Path}; 3 | 4 | use package_json_schema::PackageJson; 5 | use serde::de::DeserializeOwned; 6 | 7 | pub struct DepotManifest { 8 | pub manifest: PackageJson, 9 | pub config: Config, 10 | } 11 | 12 | impl DepotManifest { 13 | pub fn load(path: &Path) -> Result { 14 | let contents = fs::read_to_string(path) 15 | .with_context(|| format!("Missing manifest at: `{}`", path.display()))?; 16 | let manifest = PackageJson::try_from(contents)?; 17 | Self::from_json(manifest, path) 18 | } 19 | 20 | pub fn from_json(mut manifest: PackageJson, path: &Path) -> Result { 21 | let error_msg = || format!("Missing \"depot\" key from manifest: `{}`", path.display()); 22 | let other = manifest.other.as_mut().with_context(error_msg)?; 23 | let config_value = other.shift_remove("depot").with_context(error_msg)?; 24 | let config: Config = serde_json::from_value(config_value)?; 25 | Ok(DepotManifest { manifest, config }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/mod.rs: -------------------------------------------------------------------------------- 1 | use self::{ 2 | dep_graph::DepGraph, 3 | fingerprint::Fingerprints, 4 | package::{PackageGraph, PackageIndex}, 5 | process::Process, 6 | }; 7 | use crate::{CommonArgs, shareable, utils}; 8 | 9 | use anyhow::{Context, Result, anyhow}; 10 | use futures::{ 11 | StreamExt, 12 | stream::{self, TryStreamExt}, 13 | }; 14 | use log::{debug, warn}; 15 | use manifest::DepotManifest; 16 | use package::Package; 17 | use std::{ 18 | cmp::Ordering, 19 | env, 20 | fmt::{self, Debug}, 21 | iter, 22 | path::{Path, PathBuf}, 23 | sync::{Arc, RwLock, RwLockReadGuard}, 24 | }; 25 | 26 | mod dep_graph; 27 | mod fingerprint; 28 | mod manifest; 29 | pub mod package; 30 | pub mod process; 31 | mod runner; 32 | 33 | #[derive(serde::Serialize, serde::Deserialize)] 34 | #[serde(rename_all = "kebab-case")] 35 | pub struct WorkspaceDepotConfig { 36 | pub depot_version: String, 37 | } 38 | 39 | pub type WorkspaceManifest = DepotManifest; 40 | 41 | /// Represents an entire Depot workspace. 42 | /// 43 | /// This is a central data structure that is held by many parts of the application, 44 | /// wrapped in an [`Arc`] by [`Workspace`]. 45 | pub struct WorkspaceInner { 46 | /// The root directory of the workspace containing `package.json`. 47 | pub root: PathBuf, 48 | 49 | /// All the packages in the workspace. 50 | pub packages: Vec, 51 | 52 | /// The dependencies between packages. 53 | pub pkg_graph: PackageGraph, 54 | 55 | /// True if this workspace is structured as a monorepo with a `packages/` directory. 56 | pub monorepo: bool, 57 | 58 | /// CLI arguments that apply to the whole workspace. 59 | pub common: CommonArgs, 60 | 61 | roots: Vec, 62 | package_display_order: Vec, 63 | processes: RwLock>>, 64 | fingerprints: RwLock, 65 | } 66 | 67 | shareable!(Workspace, WorkspaceInner); 68 | 69 | fn find_workspace_root(max_ancestor: &Path, cwd: &Path) -> Result { 70 | let rel_path_to_cwd = cwd.strip_prefix(max_ancestor).unwrap_or_else(|_| { 71 | panic!( 72 | "Internal error: Max ancestor `{}` is not a prefix of cwd `{}`", 73 | max_ancestor.display(), 74 | cwd.display() 75 | ) 76 | }); 77 | let components = rel_path_to_cwd.iter().collect::>(); 78 | (0..=components.len()) 79 | .map(|i| { 80 | iter::once(max_ancestor.as_os_str()) 81 | .chain(components[..i].iter().copied()) 82 | .collect::() 83 | }) 84 | .find(|path| path.join("package.json").exists()) 85 | .with_context(|| { 86 | format!( 87 | "Could not find workspace root in working dir: {}", 88 | cwd.display() 89 | ) 90 | }) 91 | } 92 | 93 | pub enum CommandInner { 94 | Package(Box), 95 | Workspace(Box), 96 | } 97 | 98 | impl CommandInner { 99 | pub fn name(&self) -> String { 100 | match self { 101 | CommandInner::Package(cmd) => cmd.name(), 102 | CommandInner::Workspace(cmd) => cmd.name(), 103 | } 104 | } 105 | 106 | pub fn deps(&self) -> Vec { 107 | match self { 108 | CommandInner::Package(cmd) => cmd.deps(), 109 | CommandInner::Workspace(_) => Vec::new(), 110 | } 111 | } 112 | } 113 | 114 | impl Command { 115 | pub async fn run_pkg(self, package: Package) -> Result<()> { 116 | match &*self { 117 | CommandInner::Package(cmd) => cmd.run_pkg(&package).await, 118 | CommandInner::Workspace(_) => panic!("run_pkg on workspace command"), 119 | } 120 | } 121 | 122 | pub async fn run_ws(self, ws: Workspace) -> Result<()> { 123 | match &*self { 124 | CommandInner::Workspace(cmd) => cmd.run_ws(&ws).await, 125 | CommandInner::Package(_) => panic!("run_ws on package command"), 126 | } 127 | } 128 | 129 | pub fn runtime(&self) -> Option { 130 | match &**self { 131 | CommandInner::Package(cmd) => Some(cmd.runtime()), 132 | CommandInner::Workspace(_) => None, 133 | } 134 | } 135 | } 136 | 137 | impl fmt::Debug for CommandInner { 138 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 139 | match self { 140 | CommandInner::Package(cmd) => write!(f, "{cmd:?}"), 141 | CommandInner::Workspace(cmd) => write!(f, "{cmd:?}"), 142 | } 143 | } 144 | } 145 | 146 | shareable!(Command, CommandInner); 147 | 148 | impl Command { 149 | pub fn package(cmd: impl PackageCommand) -> Self { 150 | Self::new(CommandInner::Package(Box::new(cmd))) 151 | } 152 | 153 | pub fn workspace(cmd: impl WorkspaceCommand + 'static) -> Self { 154 | Self::new(CommandInner::Workspace(Box::new(cmd))) 155 | } 156 | } 157 | 158 | pub trait CoreCommand { 159 | fn name(&self) -> String; 160 | } 161 | 162 | #[derive(Clone, Copy)] 163 | pub enum CommandRuntime { 164 | WaitForDependencies, 165 | RunImmediately, 166 | RunForever, 167 | } 168 | 169 | #[async_trait::async_trait] 170 | pub trait PackageCommand: CoreCommand + Debug + Send + Sync + 'static { 171 | async fn run_pkg(&self, package: &Package) -> Result<()>; 172 | 173 | fn pkg_key(&self, package: &Package) -> String { 174 | format!("{}-{}", self.name(), package.name) 175 | } 176 | 177 | fn deps(&self) -> Vec { 178 | Vec::new() 179 | } 180 | 181 | fn runtime(&self) -> CommandRuntime { 182 | CommandRuntime::RunImmediately 183 | } 184 | } 185 | 186 | #[async_trait::async_trait] 187 | pub trait WorkspaceCommand: CoreCommand + Debug + Send + Sync + 'static { 188 | async fn run_ws(&self, ws: &Workspace) -> Result<()>; 189 | 190 | fn ws_key(&self) -> String { 191 | self.name() 192 | } 193 | 194 | fn input_files(&self, _ws: &Workspace) -> Option> { 195 | None 196 | } 197 | } 198 | 199 | pub const DEPOT_VERSION: &str = env!("CARGO_PKG_VERSION"); 200 | 201 | impl Workspace { 202 | pub async fn load(cwd: Option, common: CommonArgs) -> Result { 203 | let cwd = match cwd { 204 | Some(cwd) => cwd, 205 | None => env::current_dir()?, 206 | }; 207 | let fs_root = cwd.ancestors().last().unwrap().to_path_buf(); 208 | let git_root = utils::get_git_root(&cwd); 209 | let max_ancestor: &Path = git_root.as_deref().unwrap_or(&fs_root); 210 | let root = find_workspace_root(max_ancestor, &cwd)?; 211 | debug!("Workspace root: `{}`", root.display()); 212 | 213 | let pkg_dir = root.join("packages"); 214 | let monorepo = pkg_dir.exists(); 215 | debug!("Workspace is monorepo: {monorepo}"); 216 | 217 | let manifest = WorkspaceManifest::load(&root.join("package.json"))?; 218 | let created_version = &manifest.config.depot_version; 219 | if DEPOT_VERSION != created_version { 220 | warn!( 221 | "Depot binary is v{DEPOT_VERSION} but workspace was created with v{created_version}. 222 | 223 | Double-check that this workspace is compatible and update depot.depot_version in package.json." 224 | ); 225 | } 226 | 227 | let pkg_roots = if monorepo { 228 | pkg_dir 229 | .read_dir()? 230 | .map(|entry| Ok(entry?.path())) 231 | .collect::>>()? 232 | } else { 233 | vec![root.clone()] 234 | }; 235 | 236 | let packages: Vec<_> = stream::iter(pkg_roots) 237 | .enumerate() 238 | .then(|(index, pkg_root)| async move { Package::load(&pkg_root, index) }) 239 | .try_collect() 240 | .await?; 241 | 242 | let roots = match &common.package { 243 | Some(name) => { 244 | let pkg = packages 245 | .iter() 246 | .find(|pkg| &pkg.name == name) 247 | .with_context(|| format!("Could not find package with name: {name}"))?; 248 | vec![pkg.clone()] 249 | } 250 | None => packages.clone(), 251 | }; 252 | 253 | let pkg_graph = package::build_package_graph(&packages, &roots)?; 254 | 255 | let package_display_order = { 256 | let mut order = pkg_graph.nodes().map(|pkg| pkg.index).collect::>(); 257 | 258 | order.sort_by(|n1, n2| { 259 | if pkg_graph.is_dependent_on(&packages[*n2], &packages[*n1]) { 260 | Ordering::Less 261 | } else if pkg_graph.is_dependent_on(&packages[*n1], &packages[*n2]) { 262 | Ordering::Greater 263 | } else { 264 | Ordering::Equal 265 | } 266 | }); 267 | 268 | order.sort_by(|n1, n2| { 269 | if pkg_graph.is_dependent_on(&packages[*n2], &packages[*n1]) { 270 | Ordering::Less 271 | } else if pkg_graph.is_dependent_on(&packages[*n1], &packages[*n2]) { 272 | Ordering::Greater 273 | } else { 274 | packages[*n1].name.cmp(&packages[*n2].name) 275 | } 276 | }); 277 | 278 | order 279 | }; 280 | 281 | let fingerprints = RwLock::new(Fingerprints::load(&root)?); 282 | 283 | let ws = Workspace::new(WorkspaceInner { 284 | root, 285 | packages, 286 | package_display_order, 287 | monorepo, 288 | pkg_graph, 289 | common, 290 | roots, 291 | processes: RwLock::default(), 292 | fingerprints, 293 | }); 294 | 295 | for pkg in &ws.packages { 296 | pkg.set_workspace(&ws); 297 | } 298 | 299 | Ok(ws) 300 | } 301 | } 302 | 303 | impl WorkspaceInner { 304 | pub fn package_display_order(&self) -> impl Iterator { 305 | self 306 | .package_display_order 307 | .iter() 308 | .map(|idx| &self.packages[*idx]) 309 | } 310 | 311 | pub fn start_process( 312 | &self, 313 | script: &'static str, 314 | configure: impl FnOnce(&mut tokio::process::Command), 315 | ) -> Result> { 316 | log::trace!("Starting process: {script}"); 317 | 318 | let pnpm = 319 | utils::find_pnpm(Some(&self.root)).ok_or(anyhow!("could not find pnpm on your system"))?; 320 | 321 | let mut cmd = tokio::process::Command::new(pnpm); 322 | cmd.current_dir(&self.root); 323 | cmd.env("NODE_PATH", self.root.join("node_modules")); 324 | 325 | if script != "pnpm" { 326 | cmd.args(["exec", script]); 327 | } 328 | 329 | configure(&mut cmd); 330 | 331 | Ok(Arc::new(Process::new(script.to_owned(), cmd)?)) 332 | } 333 | 334 | pub async fn exec( 335 | &self, 336 | script: &'static str, 337 | configure: impl FnOnce(&mut tokio::process::Command), 338 | ) -> Result<()> { 339 | let process = self.start_process(script, configure)?; 340 | self.processes.write().unwrap().push(process.clone()); 341 | process.wait_for_success().await 342 | } 343 | 344 | pub fn processes(&self) -> RwLockReadGuard<'_, Vec>> { 345 | self.processes.read().unwrap() 346 | } 347 | 348 | pub fn all_files(&self) -> impl Iterator + '_ { 349 | self.packages.iter().flat_map(|pkg| pkg.all_files()) 350 | } 351 | } 352 | 353 | pub type CommandGraph = DepGraph; 354 | 355 | pub fn build_command_graph(root: &Command) -> CommandGraph { 356 | DepGraph::build(vec![root.clone()], |_| unreachable!(), |cmd| cmd.deps()).unwrap() 357 | } 358 | 359 | #[cfg(test)] 360 | mod test { 361 | use crate::commands::test::{TestArgs, TestCommand}; 362 | 363 | use super::*; 364 | 365 | #[test] 366 | fn test_command_graph() { 367 | let root = TestCommand::new(TestArgs::default()).kind(); 368 | let _cmd_graph = build_command_graph(&root); 369 | // TODO: finish this test 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/package.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error, Result, bail, ensure}; 2 | 3 | use ignore::Walk; 4 | use maplit::hashset; 5 | use std::{ 6 | collections::HashSet, 7 | fmt::{self, Debug}, 8 | hash::Hash, 9 | path::{Path, PathBuf}, 10 | str::FromStr, 11 | sync::{Arc, OnceLock, RwLock, RwLockReadGuard}, 12 | }; 13 | 14 | use crate::{shareable, workspace::process::Process}; 15 | 16 | use super::{Workspace, dep_graph::DepGraph, manifest::DepotManifest}; 17 | 18 | #[derive(Copy, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)] 19 | pub enum Platform { 20 | #[serde(rename = "browser")] 21 | Browser, 22 | #[serde(rename = "node")] 23 | Node, 24 | } 25 | 26 | #[allow(unused)] 27 | impl Platform { 28 | pub fn is_browser(self) -> bool { 29 | matches!(self, Platform::Browser) 30 | } 31 | 32 | pub fn is_node(self) -> bool { 33 | matches!(self, Platform::Node) 34 | } 35 | } 36 | 37 | #[derive(Copy, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)] 38 | pub enum Target { 39 | #[serde(rename = "lib")] 40 | Lib, 41 | #[serde(rename = "site")] 42 | Site, 43 | #[serde(rename = "script")] 44 | Script, 45 | } 46 | 47 | impl Target { 48 | pub fn is_lib(self) -> bool { 49 | matches!(self, Target::Lib) 50 | } 51 | 52 | pub fn is_site(self) -> bool { 53 | matches!(self, Target::Site) 54 | } 55 | 56 | pub fn is_script(self) -> bool { 57 | matches!(self, Target::Script) 58 | } 59 | } 60 | 61 | #[derive(Clone, PartialEq, Eq, Hash, Debug, Ord, PartialOrd)] 62 | pub struct PackageName { 63 | pub name: String, 64 | pub scope: Option, 65 | } 66 | 67 | impl PackageName { 68 | pub fn as_global_var(&self) -> String { 69 | self 70 | .name 71 | .split('-') 72 | .map(|substr| { 73 | let mut chars = substr.chars(); 74 | let first = chars.next().unwrap().to_uppercase().to_string(); 75 | first + &chars.collect::() 76 | }) 77 | .collect::() 78 | } 79 | } 80 | 81 | impl fmt::Display for PackageName { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | match &self.scope { 84 | Some(scope) => write!(f, "@{}/{}", scope, self.name), 85 | None => write!(f, "{}", self.name), 86 | } 87 | } 88 | } 89 | 90 | impl FromStr for PackageName { 91 | type Err = Error; 92 | 93 | fn from_str(s: &str) -> Result { 94 | match s.strip_prefix('@') { 95 | Some(rest) => { 96 | let components = rest.split('/').collect::>(); 97 | ensure!(components.len() == 2, "Invalid package name"); 98 | 99 | Ok(PackageName { 100 | scope: Some(components[0].to_string()), 101 | name: components[1].to_string(), 102 | }) 103 | } 104 | None => Ok(PackageName { 105 | name: s.to_string(), 106 | scope: None, 107 | }), 108 | } 109 | } 110 | } 111 | 112 | #[derive(serde::Serialize, serde::Deserialize)] 113 | #[serde(rename_all = "kebab-case")] 114 | pub struct PackageDepotConfig { 115 | pub platform: Platform, 116 | 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | pub target: Option, 119 | 120 | #[serde(skip_serializing_if = "Option::is_none")] 121 | pub no_server: Option, 122 | 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | pub asset_extensions: Option>, 125 | 126 | #[serde(skip_serializing_if = "Option::is_none")] 127 | pub source_extensions: Option>, 128 | } 129 | 130 | impl Default for PackageDepotConfig { 131 | fn default() -> Self { 132 | PackageDepotConfig { 133 | platform: Platform::Browser, 134 | target: None, 135 | no_server: None, 136 | asset_extensions: None, 137 | source_extensions: None, 138 | } 139 | } 140 | } 141 | 142 | pub type PackageManifest = DepotManifest; 143 | 144 | pub type PackageIndex = usize; 145 | 146 | pub struct PackageInner { 147 | // Metadata 148 | pub root: PathBuf, 149 | pub manifest: PackageManifest, 150 | #[allow(unused)] 151 | pub platform: Platform, 152 | pub target: Target, 153 | pub name: PackageName, 154 | pub index: PackageIndex, 155 | 156 | // Internals 157 | ws: OnceLock, 158 | processes: RwLock>>, 159 | } 160 | 161 | shareable!(Package, PackageInner); 162 | 163 | impl Package { 164 | fn find_source_file(root: &Path, base: &str) -> Option { 165 | ["tsx", "ts", "js"] 166 | .into_iter() 167 | .map(|ext| root.join("src").join(format!("{base}.{ext}"))) 168 | .find(|path| path.exists()) 169 | } 170 | 171 | pub fn processes(&self) -> RwLockReadGuard<'_, Vec>> { 172 | self.processes.read().unwrap() 173 | } 174 | 175 | pub fn from_parts( 176 | root: PathBuf, 177 | manifest: PackageManifest, 178 | index: PackageIndex, 179 | target: Target, 180 | ) -> Result { 181 | let platform = manifest.config.platform; 182 | let name_str = manifest 183 | .manifest 184 | .name 185 | .as_deref() 186 | .unwrap_or_else(|| root.file_name().unwrap().to_str().unwrap()); 187 | let name = PackageName::from_str(name_str)?; 188 | 189 | Ok(Package::new(PackageInner { 190 | root, 191 | manifest, 192 | target, 193 | platform, 194 | name, 195 | index, 196 | ws: OnceLock::default(), 197 | processes: RwLock::default(), 198 | })) 199 | } 200 | 201 | fn infer_target(root: &Path, manifest: &PackageManifest) -> Result { 202 | if let Some(target) = manifest.config.target { 203 | Ok(target) 204 | } else if Self::find_source_file(root, "lib").is_some() { 205 | Ok(Target::Lib) 206 | } else if Self::find_source_file(root, "main").is_some() { 207 | Ok(Target::Script) 208 | } else if Self::find_source_file(root, "index").is_some() { 209 | Ok(Target::Site) 210 | } else { 211 | bail!( 212 | "Could not infer target. Consider adding a \"target\" entry under \"depot\" to: {}", 213 | root.join("package.json").display() 214 | ) 215 | } 216 | } 217 | 218 | pub fn load(root: &Path, index: PackageIndex) -> Result { 219 | let root = root 220 | .canonicalize() 221 | .with_context(|| format!("Could not find package root: `{}`", root.display()))?; 222 | let manifest_path = root.join("package.json"); 223 | let manifest = PackageManifest::load(&manifest_path)?; 224 | let target = Self::infer_target(&root, &manifest)?; 225 | Self::from_parts(root, manifest, index, target) 226 | } 227 | 228 | pub fn start_process( 229 | &self, 230 | script: &'static str, 231 | configure: impl FnOnce(&mut tokio::process::Command), 232 | ) -> Result> { 233 | let process = self.workspace().start_process(script, |cmd| { 234 | cmd.current_dir(&self.root); 235 | configure(cmd); 236 | })?; 237 | self.processes.write().unwrap().push(process.clone()); 238 | Ok(process) 239 | } 240 | 241 | pub async fn exec( 242 | &self, 243 | script: &'static str, 244 | configure: impl FnOnce(&mut tokio::process::Command), 245 | ) -> Result<()> { 246 | self 247 | .start_process(script, configure)? 248 | .wait_for_success() 249 | .await 250 | } 251 | } 252 | 253 | impl PackageInner { 254 | pub fn all_dependencies(&self) -> impl Iterator + '_ { 255 | let manifest = &self.manifest.manifest; 256 | let manifest_deps = [ 257 | &manifest.dependencies, 258 | &manifest.dev_dependencies, 259 | &manifest.peer_dependencies, 260 | ]; 261 | manifest_deps 262 | .into_iter() 263 | .flatten() 264 | .flat_map(|deps| deps.keys()) 265 | .filter_map(|s| PackageName::from_str(s).ok()) 266 | } 267 | 268 | pub fn workspace(&self) -> &Workspace { 269 | self.ws.get().unwrap() 270 | } 271 | 272 | pub(super) fn set_workspace(&self, ws: &Workspace) { 273 | self 274 | .ws 275 | .set(ws.clone()) 276 | .unwrap_or_else(|_| panic!("Called set_workspace twice!")); 277 | } 278 | 279 | fn iter_files(&self, rel_path: impl AsRef) -> impl Iterator { 280 | Walk::new(self.root.join(rel_path)).filter_map(|entry| { 281 | let entry = entry.ok()?; 282 | let is_file = match entry.file_type() { 283 | Some(file_type) => file_type.is_file(), 284 | None => false, 285 | }; 286 | is_file.then(|| entry.into_path()) 287 | }) 288 | } 289 | 290 | pub fn asset_files(&self) -> impl Iterator + '_ { 291 | let mut asset_extensions: HashSet<&str> = 292 | hashset! { "scss", "css", "jpeg", "jpg", "png", "svg" }; 293 | if let Some(exts) = &self.manifest.config.asset_extensions { 294 | asset_extensions.extend(exts.iter().map(String::as_str)); 295 | } 296 | 297 | self 298 | .iter_files("src") 299 | .filter_map(move |path| { 300 | let ext = path.extension()?; 301 | asset_extensions 302 | .contains(ext.to_str().unwrap()) 303 | .then_some(path) 304 | }) 305 | .chain(self.iter_files("src/assets")) 306 | .collect::>() // dedup 307 | .into_iter() 308 | } 309 | 310 | pub fn source_files(&self) -> impl Iterator + '_ { 311 | let mut source_extensions = hashset! { "ts", "tsx", "html" }; 312 | if let Some(exts) = &self.manifest.config.source_extensions { 313 | source_extensions.extend(exts.iter().map(String::as_str)); 314 | } 315 | 316 | const CONFIG_FILES: &[&str] = &[ 317 | "vite.config.ts", 318 | "vite.config.mts", 319 | "vitest.config.ts", 320 | "vitest.config.mts", 321 | ]; 322 | 323 | ["src", "tests"] 324 | .into_iter() 325 | .flat_map(|dir| self.iter_files(dir)) 326 | .chain(CONFIG_FILES.iter().map(PathBuf::from)) 327 | .filter_map(move |path| { 328 | if !path.exists() { 329 | return None; 330 | } 331 | let ext = path.extension()?; 332 | source_extensions 333 | .contains(ext.to_str().unwrap()) 334 | .then_some(path) 335 | }) 336 | } 337 | 338 | pub fn all_files(&self) -> impl Iterator + '_ { 339 | self.iter_files("src") 340 | } 341 | } 342 | 343 | impl Debug for Package { 344 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 345 | write!(f, "{}", self.name) 346 | } 347 | } 348 | 349 | pub type PackageGraph = DepGraph; 350 | 351 | pub fn build_package_graph(packages: &[Package], roots: &[Package]) -> Result { 352 | DepGraph::build( 353 | roots.to_vec(), 354 | |pkg| pkg.name.to_string(), 355 | |pkg| { 356 | pkg 357 | .all_dependencies() 358 | .filter_map(|name| packages.iter().find(|other_pkg| other_pkg.name == name)) 359 | .cloned() 360 | .collect() 361 | }, 362 | ) 363 | } 364 | 365 | #[cfg(test)] 366 | mod test { 367 | use super::*; 368 | use maplit::hashset; 369 | 370 | use std::collections::HashSet; 371 | 372 | #[test] 373 | fn test_package_name() { 374 | let s = "foo"; 375 | let name = PackageName::from_str(s).unwrap(); 376 | assert_eq!( 377 | name, 378 | PackageName { 379 | name: "foo".into(), 380 | scope: None 381 | } 382 | ); 383 | 384 | let s = "@foo/bar"; 385 | let name = PackageName::from_str(s).unwrap(); 386 | assert_eq!( 387 | name, 388 | PackageName { 389 | name: "bar".into(), 390 | scope: Some("foo".into()) 391 | } 392 | ); 393 | assert_eq!("@foo/bar", format!("{}", name)); 394 | 395 | let s = "@what/is/this"; 396 | assert!(PackageName::from_str(s).is_err()); 397 | } 398 | 399 | #[test] 400 | fn test_package_graph() { 401 | let pkgs = crate::test_packages! [ 402 | {"name": "a", "dependencies": {"b": "0.1.0"}}, 403 | {"name": "b", "dependencies": {"c": "0.1.0"}}, 404 | {"name": "c"} 405 | ]; 406 | 407 | let [a, b, c] = &pkgs; 408 | 409 | let dg = build_package_graph(&pkgs, &pkgs).unwrap(); 410 | let deps_for = |p| dg.all_deps_for(p).collect::>(); 411 | assert_eq!(deps_for(a), hashset! {b, c}); 412 | assert_eq!(deps_for(b), hashset! {c}); 413 | assert_eq!(deps_for(c), hashset! {}); 414 | 415 | let imm_deps_for = |p| dg.immediate_deps_for(p).collect::>(); 416 | assert_eq!(imm_deps_for(a), hashset! {b}); 417 | assert_eq!(imm_deps_for(b), hashset! {c}); 418 | assert_eq!(imm_deps_for(c), hashset! {}); 419 | 420 | assert!(dg.is_dependent_on(a, b)); 421 | assert!(dg.is_dependent_on(a, c)); 422 | assert!(!dg.is_dependent_on(b, a)); 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/process.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | process::{ExitStatus, Stdio}, 3 | sync::{ 4 | Arc, Mutex, MutexGuard, 5 | atomic::{AtomicBool, Ordering}, 6 | }, 7 | }; 8 | use tokio::{ 9 | io::{AsyncBufReadExt, AsyncRead, BufReader}, 10 | task::JoinHandle, 11 | }; 12 | 13 | use anyhow::{Context, Result, bail, ensure}; 14 | 15 | use crate::logger::ringbuffer::RingBuffer; 16 | 17 | /// Indicates the provenance of a given [`LogLine`]. 18 | #[derive(Copy, Clone)] 19 | pub enum OutputChannel { 20 | Stdout, 21 | Stderr, 22 | } 23 | 24 | /// A string emitted by a shell command on a given [`OutputChannel`]. 25 | pub struct LogLine { 26 | pub line: String, 27 | #[allow(unused)] // We may eventually want to distinguish stdout/stderr in the logs 28 | pub channel: OutputChannel, 29 | } 30 | 31 | pub type LogBuffer = RingBuffer; 32 | 33 | /// Encapsulates shell commands. 34 | /// 35 | /// Wrapper around [`tokio::process::Command`] that deals with I/O. 36 | pub struct Process { 37 | script: String, 38 | child: Mutex>, 39 | logs: Arc>, 40 | finished: AtomicBool, 41 | 42 | // TODO: is it necessary to abort these handles? 43 | #[allow(unused)] 44 | pipe_handles: Mutex>>, 45 | } 46 | 47 | impl Process { 48 | pub fn new(script: String, mut cmd: tokio::process::Command) -> Result { 49 | cmd.kill_on_drop(true); 50 | cmd.stdout(Stdio::piped()); 51 | cmd.stderr(Stdio::piped()); 52 | 53 | let mut child = cmd 54 | .spawn() 55 | .with_context(|| format!("Failed to spawn process: `{script}`"))?; 56 | 57 | let logs: Arc>> = Arc::new(Mutex::new(RingBuffer::new())); 58 | let pipe_handles = vec![ 59 | tokio::spawn(Self::pipe_stdio( 60 | child.stdout.take().unwrap(), 61 | logs.clone(), 62 | OutputChannel::Stdout, 63 | )), 64 | tokio::spawn(Self::pipe_stdio( 65 | child.stderr.take().unwrap(), 66 | logs.clone(), 67 | OutputChannel::Stderr, 68 | )), 69 | ]; 70 | 71 | Ok(Process { 72 | script, 73 | child: Mutex::new(Some(child)), 74 | logs, 75 | finished: AtomicBool::new(false), 76 | pipe_handles: Mutex::new(pipe_handles), 77 | }) 78 | } 79 | 80 | async fn pipe_stdio( 81 | stdio: impl AsyncRead + Unpin, 82 | buffer: Arc>, 83 | channel: OutputChannel, 84 | ) { 85 | let mut lines = BufReader::new(stdio).lines(); 86 | while let Some(line) = lines.next_line().await.unwrap() { 87 | let mut buffer = buffer.lock().unwrap(); 88 | let line = match line.strip_prefix("\u{1b}c") { 89 | Some(rest) => { 90 | buffer.clear(); 91 | rest.to_string() 92 | } 93 | None => line, 94 | }; 95 | buffer.push(LogLine { line, channel }); 96 | } 97 | } 98 | 99 | pub fn script(&self) -> &str { 100 | &self.script 101 | } 102 | 103 | pub fn stdout(&self) -> MutexGuard<'_, LogBuffer> { 104 | self.logs.lock().unwrap() 105 | } 106 | 107 | pub fn finished(&self) -> bool { 108 | self.finished.load(Ordering::SeqCst) 109 | } 110 | 111 | pub async fn wait(&self) -> Result { 112 | let mut child = self.child.lock().unwrap().take().unwrap(); 113 | 114 | let status_res = child 115 | .wait() 116 | .await 117 | .with_context(|| format!("Process `{}` failed", self.script)); 118 | 119 | self.finished.store(true, Ordering::SeqCst); 120 | 121 | status_res 122 | } 123 | 124 | pub async fn wait_for_success(&self) -> Result<()> { 125 | let status = self.wait().await?; 126 | match status.code() { 127 | Some(code) => ensure!( 128 | status.success(), 129 | "Process `{}` exited with non-zero exit code: {code}", 130 | self.script 131 | ), 132 | None => bail!("Process `{}` exited due to signal", self.script), 133 | } 134 | Ok(()) 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod test { 140 | use tokio::process::Command; 141 | 142 | use super::*; 143 | 144 | #[tokio::test] 145 | async fn process_ok() -> Result<()> { 146 | let mut cmd = Command::new("echo"); 147 | cmd.arg("Hello world"); 148 | 149 | let process = Process::new("echo".to_string(), cmd)?; 150 | assert_eq!(process.script(), "echo"); 151 | 152 | let status = process.wait().await?; 153 | assert!(status.success()); 154 | 155 | let stdout = process 156 | .stdout() 157 | .iter() 158 | .map(|line| line.line.clone()) 159 | .collect::>() 160 | .join("\n"); 161 | assert_eq!(stdout, "Hello world"); 162 | 163 | Ok(()) 164 | } 165 | 166 | #[tokio::test] 167 | async fn process_fail() -> Result<()> { 168 | let cmd = Command::new("false"); 169 | let process = Process::new("false".to_string(), cmd)?; 170 | let status = process.wait().await?; 171 | assert!(!status.success()); 172 | Ok(()) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /crates/depot/src/workspace/runner.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use futures::{FutureExt, future::BoxFuture}; 4 | use log::debug; 5 | use std::{ 6 | cell::RefCell, 7 | collections::HashMap, 8 | future::Future, 9 | sync::{Arc, atomic::Ordering}, 10 | }; 11 | use tokio::sync::Notify; 12 | 13 | use crate::{ 14 | logger::ui::{FullscreenRenderer, InlineRenderer, Renderer}, 15 | shareable, 16 | }; 17 | 18 | use super::{ 19 | Command, CommandGraph, CommandInner, CommandRuntime, Workspace, build_command_graph, 20 | dep_graph::DepGraph, 21 | }; 22 | 23 | #[atomic_enum::atomic_enum] 24 | #[derive(PartialEq)] 25 | enum TaskStatus { 26 | Pending = 0, 27 | Running, 28 | Finished, 29 | } 30 | 31 | type TaskFuture = Box BoxFuture<'static, (Result<()>, Task)>>; 32 | 33 | pub struct TaskInner { 34 | key: String, 35 | command: Command, 36 | deps: Vec, 37 | status: AtomicTaskStatus, 38 | can_skip: bool, 39 | } 40 | 41 | shareable!(Task, TaskInner); 42 | 43 | impl Task { 44 | fn make> + Send + 'static>( 45 | key: String, 46 | command: Command, 47 | fut: F, 48 | deps: Vec, 49 | can_skip: bool, 50 | ) -> (Self, TaskFuture) { 51 | let task = Task::new(TaskInner { 52 | key, 53 | command, 54 | deps, 55 | can_skip, 56 | status: AtomicTaskStatus::new(TaskStatus::Pending), 57 | }); 58 | let task2 = task.clone(); 59 | let boxed_fut = Box::new(move || { 60 | async move { 61 | let result = fut.await; 62 | (result, task2) 63 | } 64 | .boxed() 65 | }); 66 | (task, boxed_fut) 67 | } 68 | } 69 | 70 | impl TaskInner { 71 | fn key(&self) -> &str { 72 | &self.key 73 | } 74 | 75 | fn status(&self) -> TaskStatus { 76 | self.status.load(Ordering::SeqCst) 77 | } 78 | } 79 | 80 | type TaskGraph = DepGraph; 81 | 82 | impl Workspace { 83 | fn spawn_log_thread( 84 | &self, 85 | log_should_exit: &Arc, 86 | runner_should_exit: &Arc, 87 | runtime: Option, 88 | ) -> impl Future { 89 | let ws = self.clone(); 90 | let log_should_exit = Arc::clone(log_should_exit); 91 | let runner_should_exit = Arc::clone(runner_should_exit); 92 | let use_fullscreen_renderer = 93 | !ws.common.no_fullscreen && matches!(runtime, Some(CommandRuntime::RunForever)); 94 | tokio::spawn(async move { 95 | let result = if use_fullscreen_renderer { 96 | FullscreenRenderer::new() 97 | .unwrap() 98 | .render_loop(&ws, &log_should_exit) 99 | .await 100 | } else { 101 | InlineRenderer::new() 102 | .render_loop(&ws, &log_should_exit) 103 | .await 104 | }; 105 | match result { 106 | Ok(true) => runner_should_exit.notify_one(), 107 | Ok(false) => {} 108 | Err(e) => { 109 | eprintln!("{e}"); 110 | runner_should_exit.notify_one(); 111 | } 112 | } 113 | }) 114 | } 115 | 116 | fn build_task_graph( 117 | &self, 118 | cmd_graph: &CommandGraph, 119 | runtime: Option, 120 | ) -> (TaskGraph, HashMap) { 121 | let futures = RefCell::new(HashMap::new()); 122 | let task_pool = RefCell::new(HashMap::new()); 123 | 124 | let tasks_for = |cmd: &Command| -> Vec { 125 | macro_rules! add_task { 126 | ($key:expr, $task:expr, $deps:expr, $files:expr) => {{ 127 | task_pool 128 | .borrow_mut() 129 | .entry($key.clone()) 130 | .or_insert_with(|| { 131 | let can_skip = self.common.incremental 132 | && !matches!(runtime, Some(CommandRuntime::RunForever)) 133 | && match $files { 134 | Some(files) => { 135 | let fingerprints = self.fingerprints.read().unwrap(); 136 | fingerprints.can_skip(&$key, files) 137 | } 138 | None => false, 139 | }; 140 | 141 | let (task, future) = Task::make($key, cmd.clone(), $task, $deps, can_skip); 142 | futures.borrow_mut().insert(task.clone(), future); 143 | task 144 | }) 145 | .clone() 146 | }}; 147 | } 148 | 149 | match &**cmd { 150 | CommandInner::Package(pkg_cmd) => self 151 | .roots 152 | .iter() 153 | .flat_map(|pkg| { 154 | self.pkg_graph.all_deps_for(pkg).chain([pkg]).map(|pkg| { 155 | let pkg = pkg.clone(); 156 | let key = pkg_cmd.pkg_key(&pkg); 157 | let deps = self 158 | .pkg_graph 159 | .immediate_deps_for(&pkg) 160 | .map(|pkg| pkg_cmd.pkg_key(pkg)) 161 | .collect(); 162 | let files = pkg.all_files().collect::>(); 163 | add_task!(key, cmd.clone().run_pkg(pkg), deps, Some(files)) 164 | }) 165 | }) 166 | .collect(), 167 | CommandInner::Workspace(ws_cmd) => { 168 | let this = self.clone(); 169 | let key = ws_cmd.ws_key(); 170 | let deps = vec![]; 171 | let files = ws_cmd.input_files(self); 172 | vec![add_task!(key, cmd.clone().run_ws(this), deps, files)] 173 | } 174 | } 175 | }; 176 | 177 | let task_graph = DepGraph::build( 178 | cmd_graph.roots().flat_map(tasks_for).collect(), 179 | |t| t.key.clone(), 180 | |task: &Task| { 181 | let mut deps = cmd_graph 182 | .immediate_deps_for(&task.command) 183 | .flat_map(tasks_for) 184 | .collect::>(); 185 | let runtime = task.command.runtime(); 186 | if let Some(CommandRuntime::WaitForDependencies) = runtime { 187 | deps.extend(task.deps.iter().map(|key| task_pool.borrow()[key].clone())); 188 | } 189 | deps 190 | }, 191 | ) 192 | .unwrap(); 193 | 194 | (task_graph, futures.into_inner()) 195 | } 196 | 197 | pub async fn run(&self, root: Command) -> Result<()> { 198 | let runtime = root.runtime(); 199 | let cmd_graph = build_command_graph(&root); 200 | let (task_graph, mut task_futures) = self.build_task_graph(&cmd_graph, runtime); 201 | 202 | let log_should_exit: Arc = Arc::new(Notify::new()); 203 | let runner_should_exit: Arc = Arc::new(Notify::new()); 204 | 205 | let runner_should_exit_fut = runner_should_exit.notified(); 206 | tokio::pin!(runner_should_exit_fut); 207 | 208 | let cleanup_logs = self.spawn_log_thread(&log_should_exit, &runner_should_exit, runtime); 209 | 210 | let mut running_futures = Vec::new(); 211 | let result = loop { 212 | let finished = task_graph 213 | .nodes() 214 | .all(|task| task.status() == TaskStatus::Finished); 215 | if finished { 216 | break Ok(()); 217 | } 218 | 219 | let pending = task_graph 220 | .nodes() 221 | .filter(|task| task.status() == TaskStatus::Pending); 222 | for task in pending { 223 | let imm_deps = task_graph.immediate_deps_for(task).collect::>(); 224 | let deps_finished = imm_deps 225 | .iter() 226 | .all(|dep| dep.status() == TaskStatus::Finished); 227 | if deps_finished { 228 | let can_skip = task.can_skip && imm_deps.iter().all(|dep| dep.can_skip); 229 | let task_fut = task_futures.remove(task).unwrap(); 230 | if can_skip { 231 | task.status.store(TaskStatus::Finished, Ordering::SeqCst); 232 | } else { 233 | debug!("Starting task for: {}", task.key()); 234 | task.status.store(TaskStatus::Running, Ordering::SeqCst); 235 | running_futures.push(tokio::spawn(task_fut())); 236 | } 237 | } 238 | } 239 | 240 | if running_futures.is_empty() { 241 | continue; 242 | } 243 | 244 | let one_output = futures::future::select_all(&mut running_futures); 245 | let (result, idx, _) = tokio::select! { biased; 246 | () = &mut runner_should_exit_fut => break Ok(()), 247 | output = one_output => output, 248 | }; 249 | 250 | running_futures.remove(idx); 251 | 252 | let (result, completed_task) = result?; 253 | 254 | if result.is_err() { 255 | break result; 256 | } 257 | 258 | debug!("Finishing task for: {}", completed_task.key()); 259 | completed_task 260 | .status 261 | .store(TaskStatus::Finished, Ordering::SeqCst); 262 | self 263 | .fingerprints 264 | .write() 265 | .unwrap() 266 | .update_time(completed_task.key().to_string()); 267 | }; 268 | 269 | for fut in &mut running_futures { 270 | fut.abort(); 271 | } 272 | 273 | for fut in &mut running_futures { 274 | let _ = fut.await; 275 | } 276 | 277 | log::debug!("All tasks complete, waiting for log thread to exit"); 278 | log_should_exit.notify_one(); 279 | cleanup_logs.await; 280 | 281 | if root.name() != "clean" { 282 | self.fingerprints.read().unwrap().save(&self.root)?; 283 | } 284 | 285 | result 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use depot_test_utils::{custom_project_for, project, project_for, workspace}; 4 | 5 | #[test] 6 | fn basic_lib_browser() { 7 | let p = project_for("lib", "browser"); 8 | p.file("src/nested/foobar.css", ".red { color: red; }"); 9 | p.depot("build --lint-fail"); 10 | assert!(p.exists("dist/lib.js")); 11 | assert!(p.exists("dist/lib.d.ts")); 12 | assert!(p.exists("dist/lib.js.map")); 13 | assert!(p.exists("dist/nested/foobar.css")); 14 | } 15 | 16 | #[test] 17 | fn basic_lib_browser_react() { 18 | let p = custom_project_for("lib", "browser", "--react").persist(); 19 | p.depot("build --lint-fail"); 20 | assert!(p.exists("dist/lib.js")); 21 | assert!(p.exists("dist/lib.d.ts")); 22 | assert!(p.exists("dist/lib.js.map")); 23 | } 24 | 25 | #[test] 26 | fn basic_lib_node() { 27 | let p = project_for("lib", "node"); 28 | p.depot("build --lint-fail"); 29 | assert!(p.exists("dist/lib.js")); 30 | assert!(p.exists("dist/lib.js.map")); 31 | } 32 | 33 | #[test] 34 | fn basic_script_browser() { 35 | let p = project_for("script", "browser"); 36 | p.depot("build --lint-fail"); 37 | assert!(p.exists("dist/foo.iife.js")); 38 | assert!(p.exists("dist/foo.iife.js.map")); 39 | } 40 | 41 | #[test] 42 | fn basic_script_node() { 43 | let p = project_for("script", "node"); 44 | p.depot("build --lint-fail"); 45 | assert!(p.exists("dist/foo.cjs")); 46 | assert!(p.exists("dist/foo.cjs.map")); 47 | } 48 | 49 | #[test] 50 | fn basic_site_browser() { 51 | let project = project_for("site", "browser"); 52 | project.depot("build --lint-fail"); 53 | assert!(project.exists("dist/index.html")); 54 | } 55 | 56 | #[test] 57 | fn basic_site_browser_sass() { 58 | let project = custom_project_for("site", "browser", "--sass"); 59 | project.depot("build --lint-fail"); 60 | assert!(project.exists("dist/index.html")); 61 | } 62 | 63 | #[test] 64 | fn copy_assets() { 65 | let p = project(); 66 | p.file("src/assets/foo.txt", ""); 67 | p.file("src/styles/bar.css", ""); 68 | p.depot("build"); 69 | assert!(p.exists("dist/assets/foo.txt")); 70 | assert!(p.exists("dist/styles/bar.css")); 71 | } 72 | 73 | #[test] 74 | fn release() { 75 | let p = project(); 76 | p.depot("build --release"); 77 | assert!(p.exists("dist/lib.js")); 78 | assert!(p.exists("dist/lib.d.ts")); 79 | // Shouldn't generate source maps in release mode 80 | assert!(!p.exists("dist/lib.js.map")); 81 | } 82 | 83 | #[test] 84 | fn workspace_() { 85 | let ws = workspace(); 86 | ws.depot("new foo"); 87 | ws.depot("new bar"); 88 | 89 | // TODO: nicer way of editing package.json 90 | ws.file( 91 | "packages/bar/package.json", 92 | r#"{ 93 | "dependencies": {"foo": "workspace:^0.1.0"}, 94 | "depot": {"platform": "browser"} 95 | }"#, 96 | ); 97 | 98 | ws.depot("init -- --no-frozen-lockfile"); 99 | 100 | ws.depot("build"); 101 | assert!(ws.exists("packages/foo/dist/lib.js")); 102 | assert!(ws.exists("packages/bar/dist/lib.js")); 103 | } 104 | 105 | #[test] 106 | fn lint_basic() { 107 | let p = project(); 108 | p.file("src/foo.ts", "export let x = 1;"); 109 | assert!(p.maybe_depot("build --lint-fail").is_err()); 110 | } 111 | 112 | #[test] 113 | fn lint_gitignore_basic() { 114 | let p = project(); 115 | p.file("src/foo.ts", "export let x = 1;"); 116 | p.file(".gitignore", "foo.ts"); 117 | let mut git = Command::new("git"); 118 | assert!( 119 | git 120 | .current_dir(p.root()) 121 | .arg("init") 122 | .status() 123 | .unwrap() 124 | .success() 125 | ); 126 | p.depot("build"); 127 | assert!(p.maybe_depot("build --lint-fail").is_ok()); 128 | } 129 | 130 | #[test] 131 | fn lint_gitignore_nested() { 132 | let p = project(); 133 | p.file("src/foo.ts", "export let x = 1;"); 134 | p.file("src/.gitignore", "foo.ts"); 135 | let mut git = Command::new("git"); 136 | assert!( 137 | git 138 | .current_dir(p.root()) 139 | .arg("init") 140 | .status() 141 | .unwrap() 142 | .success() 143 | ); 144 | p.depot("build"); 145 | assert!(p.maybe_depot("build --lint-fail").is_ok()); 146 | } 147 | 148 | #[test] 149 | fn vite_imports() { 150 | let p = project(); 151 | p.file("src/foo.txt", "Hello world"); 152 | p.file("src/lib.ts", r#"import _contents from "./foo.txt?raw";"#); 153 | p.depot("build"); 154 | } 155 | 156 | #[test] 157 | fn react_import() { 158 | let p = custom_project_for("lib", "browser", "--react"); 159 | p.file("src/lib.tsx", r#"import ReactDOM from "react-dom/client";"#); 160 | p.depot("build"); 161 | } 162 | 163 | #[test] 164 | #[ignore = "Not working in CI currently, FIXME"] 165 | fn vike() { 166 | let p = custom_project_for("site", "browser", "--react --vike"); 167 | p.depot("build --lint-fail"); 168 | } 169 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/clean.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::{project, workspace_single_lib}; 2 | 3 | #[test] 4 | fn basic() { 5 | let p = project(); 6 | p.depot("build"); 7 | assert!(p.exists("dist")); 8 | p.depot("clean"); 9 | assert!(!p.exists("dist")); 10 | } 11 | 12 | #[test] 13 | fn workspace() { 14 | let ws = workspace_single_lib(); 15 | ws.depot("build"); 16 | assert!(ws.exists("packages/bar/dist")); 17 | ws.depot("clean"); 18 | assert!(!ws.exists("packages/bar/dist")); 19 | } 20 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/doc.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::{project, workspace_single_lib}; 2 | 3 | #[test] 4 | fn basic() { 5 | let p = project(); 6 | p.depot("doc"); 7 | assert!(p.exists("docs/index.html")); 8 | assert!(p.exists("docs/functions/add.html")); 9 | } 10 | 11 | #[test] 12 | fn workspace() { 13 | let ws = workspace_single_lib(); 14 | ws.depot("doc"); 15 | assert!(ws.exists("docs/index.html")); 16 | assert!(ws.exists("docs/functions/add.html")); 17 | } 18 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/fix.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::project; 2 | 3 | #[test] 4 | fn basic() { 5 | let p = project(); 6 | p.file("src/lib.ts", r#"import {x} from "./foo";"#); 7 | p.file("src/foo.ts", "export let x = 0;"); 8 | p.depot("fix"); 9 | assert_eq!(p.read("src/lib.ts"), ""); 10 | } 11 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/fmt.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::{project, workspace_single_lib}; 2 | 3 | #[test] 4 | fn basic() { 5 | let p = project().persist(); 6 | p.file("src/lib.ts", "let x = 1 + 2;"); 7 | p.depot("fmt"); 8 | assert_eq!(p.read("src/lib.ts"), "let x = 1 + 2;\n"); 9 | } 10 | 11 | #[test] 12 | fn workspace() { 13 | let ws = workspace_single_lib(); 14 | ws.file("packages/bar/src/lib.ts", "let x = 1 + 2;"); 15 | ws.depot("fmt"); 16 | assert_eq!(ws.read("packages/bar/src/lib.ts"), "let x = 1 + 2;\n"); 17 | } 18 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/main.rs: -------------------------------------------------------------------------------- 1 | mod build; 2 | mod clean; 3 | mod doc; 4 | mod fix; 5 | mod fmt; 6 | mod new; 7 | mod test; 8 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/new.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::project; 2 | 3 | #[test] 4 | fn formatting() { 5 | let p = project(); 6 | p.depot("fmt --check"); 7 | } 8 | -------------------------------------------------------------------------------- /crates/depot/tests/tests/test.rs: -------------------------------------------------------------------------------- 1 | use depot_test_utils::{project, workspace_single_lib}; 2 | 3 | #[test] 4 | fn basic() { 5 | let p = project(); 6 | p.depot("test"); 7 | } 8 | 9 | #[test] 10 | #[should_panic] 11 | fn should_fail() { 12 | let p = project(); 13 | p.file( 14 | "tests/fail.test.ts", 15 | r#" 16 | import { add } from "bar"; 17 | 18 | test("add", () => expect(add(1, 2)).toBe(100)) 19 | "#, 20 | ); 21 | p.depot("test"); 22 | } 23 | 24 | #[test] 25 | fn workspace() { 26 | let ws = workspace_single_lib(); 27 | ws.depot("init -- --no-frozen-lockfile"); 28 | ws.depot("test"); 29 | } 30 | 31 | #[test] 32 | #[should_panic] 33 | fn workspace_should_fail() { 34 | let ws = workspace_single_lib(); 35 | ws.file( 36 | "packages/bar/tests/fail.test.ts", 37 | r#" 38 | import { add } from "bar"; 39 | 40 | test("add", () => expect(add(1, 2)).toBe(100)) 41 | "#, 42 | ); 43 | ws.depot("test"); 44 | } 45 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1741851582, 24 | "narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "6607cf789e541e7873d40d3a8f7815ea92204f32", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A JS devtool orchestrator"; 3 | inputs = { 4 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | }; 7 | 8 | outputs = { self, nixpkgs, flake-utils }: 9 | flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | manifest = (pkgs.lib.importTOML ./crates/depot/Cargo.toml).package; 12 | pkgs = import nixpkgs { inherit system; }; 13 | depot-js = pkgs.rustPlatform.buildRustPackage rec { 14 | pname = manifest.name; 15 | version = manifest.version; 16 | cargoLock.lockFile = ./Cargo.lock; 17 | src = pkgs.lib.cleanSource ./.; 18 | nativeBuildInputs = [ pkgs.git ]; 19 | # NOTE: depot's tests have a lot of external dependencies: node, biome, ... 20 | # We'll ignore them for now and figure it out later ;) 21 | doCheck = false; 22 | }; 23 | in { 24 | packages = { 25 | default = depot-js; 26 | }; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASE_URL="https://github.com/cognitive-engineering-lab/depot/releases/latest/download" 5 | INSTALL_DIR=$HOME/.local/bin 6 | 7 | download() { 8 | cd $(mktemp -d) 9 | 10 | echo 'Downloading prebuilt binary from Github...' 11 | wget "${BASE_URL}/$1.tar.gz" 12 | tar -xf $1.tar.gz 13 | 14 | mkdir -p $INSTALL_DIR 15 | mv depot $INSTALL_DIR/depot 16 | } 17 | 18 | ARCH=$(uname -m) 19 | OS=$(uname) 20 | 21 | pick_target() { 22 | echo "Selecting target for $ARCH / $OS..." 23 | 24 | if [ -n "$1" ]; then 25 | cargo install depot-js --locked --git https://github.com/cognitive-engineering-lab/depot/ --rev $1 26 | return 27 | elif [ "$OS" = "Linux" ]; then 28 | if [ "$ARCH" = "x86_64" ]; then 29 | download "x86_64-unknown-linux-gnu" 30 | return 31 | fi 32 | elif [ "$OS" = "Darwin" ]; then 33 | if [ "$ARCH" = "arm64" ]; then 34 | download "aarch64-apple-darwin" 35 | return 36 | elif [ "$ARCH" = "x86_64" ]; then 37 | download "x86_64-apple-darwin" 38 | return 39 | fi 40 | fi 41 | 42 | echo 'Prebuilt binary not available, installing from source. This may take a few minutes.' 43 | cargo install depot-js --locked 44 | } 45 | 46 | pick_target $1 47 | 48 | echo 'Depot installation is complete.' --------------------------------------------------------------------------------