From 7f8b2761ab0b63ef7af55f6beb7d6e42dabde9fe Mon Sep 17 00:00:00 2001 From: daimond113 <72147841+daimond113@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:47:53 +0200 Subject: [PATCH] feat: self managed versioning --- .github/workflows/release.yaml | 70 ++-- Cargo.lock | 234 +++++++------- Cargo.toml | 32 +- src/cli/auth.rs | 75 +++++ src/cli/auth/mod.rs | 49 --- src/cli/{ => commands}/auth/login.rs | 9 +- src/cli/{ => commands}/auth/logout.rs | 2 +- src/cli/commands/auth/mod.rs | 27 ++ src/cli/{ => commands}/auth/whoami.rs | 9 +- .../{ => commands}/config/default_index.rs | 2 +- src/cli/{ => commands}/config/mod.rs | 0 src/cli/{ => commands}/config/scripts_repo.rs | 2 +- src/cli/{ => commands}/init.rs | 2 +- src/cli/{ => commands}/install.rs | 29 +- src/cli/commands/mod.rs | 76 +++++ src/cli/{ => commands}/patch.rs | 11 +- src/cli/{ => commands}/patch_commit.rs | 0 src/cli/{ => commands}/publish.rs | 0 src/cli/{ => commands}/run.rs | 0 src/cli/commands/self_install.rs | 88 +++++ src/cli/commands/self_upgrade.rs | 20 ++ src/cli/config.rs | 60 ++++ src/cli/files.rs | 19 ++ src/cli/mod.rs | 301 +----------------- src/cli/scripts.rs | 94 ++++++ src/cli/self_install.rs | 20 -- src/cli/version.rs | 218 +++++++++++++ src/main.rs | 131 ++++++-- src/manifest/mod.rs | 2 + 29 files changed, 1011 insertions(+), 571 deletions(-) create mode 100644 src/cli/auth.rs delete mode 100644 src/cli/auth/mod.rs rename src/cli/{ => commands}/auth/login.rs (96%) rename src/cli/{ => commands}/auth/logout.rs (89%) create mode 100644 src/cli/commands/auth/mod.rs rename src/cli/{ => commands}/auth/whoami.rs (57%) rename src/cli/{ => commands}/config/default_index.rs (94%) rename src/cli/{ => commands}/config/mod.rs (100%) rename src/cli/{ => commands}/config/scripts_repo.rs (94%) rename src/cli/{ => commands}/init.rs (99%) rename src/cli/{ => commands}/install.rs (89%) create mode 100644 src/cli/commands/mod.rs rename src/cli/{ => commands}/patch.rs (85%) rename src/cli/{ => commands}/patch_commit.rs (100%) rename src/cli/{ => commands}/publish.rs (100%) rename src/cli/{ => commands}/run.rs (100%) create mode 100644 src/cli/commands/self_install.rs create mode 100644 src/cli/commands/self_upgrade.rs create mode 100644 src/cli/config.rs create mode 100644 src/cli/files.rs create mode 100644 src/cli/scripts.rs delete mode 100644 src/cli/self_install.rs create mode 100644 src/cli/version.rs diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5df7a1f..362fb76 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,6 +3,8 @@ on: push: tags: - v* +env: + BIN_NAME: pesde jobs: # Better to check first, runners other than ubuntu-latest take up more free minutes check: @@ -14,29 +16,29 @@ jobs: run: cargo check --all-features --locked build: - needs: [check] + needs: [ check ] strategy: matrix: - include: - - os: ubuntu-latest - host: linux - arch: x86_64 - target: x86_64-unknown-linux-gnu + include: + - os: ubuntu-latest + host: linux + arch: x86_64 + target: x86_64-unknown-linux-gnu - - os: windows-latest - host: windows - arch: x86_64 - target: x86_64-pc-windows-msvc + - os: windows-latest + host: windows + arch: x86_64 + target: x86_64-pc-windows-msvc - - os: macos-13 - host: macos - arch: x86_64 - target: x86_64-apple-darwin + - os: macos-13 + host: macos + arch: x86_64 + target: x86_64-apple-darwin - - os: macos-latest - host: macos - arch: aarch64 - target: aarch64-apple-darwin + - os: macos-latest + host: macos + arch: aarch64 + target: aarch64-apple-darwin runs-on: ${{ matrix.os }} name: Build for ${{ matrix.host }}-${{ matrix.arch }} steps: @@ -44,10 +46,8 @@ jobs: - name: Set env shell: bash run: | - BIN_NAME=pesde - ARCHIVE_NAME=$BIN_NAME-$(echo ${{ github.ref_name }} | cut -c 2-)-${{ matrix.host }}-${{ matrix.arch }}.zip - - echo "BIN_NAME=$BIN_NAME" >> $GITHUB_ENV + ARCHIVE_NAME=${{ env.BIN_NAME }}-$(echo ${{ github.ref_name }} | cut -c 2-)-${{ matrix.host }}-${{ matrix.arch }} + echo "ARCHIVE_NAME=$ARCHIVE_NAME" >> $GITHUB_ENV - name: Build @@ -57,23 +57,31 @@ jobs: shell: bash run: | if [ ${{ matrix.host }} = "windows" ]; then - cp target/${{ matrix.target }}/release/${{ env.BIN_NAME }}.exe ${{ env.BIN_NAME }}.exe - 7z a ${{ env.ARCHIVE_NAME }} ${{ env.BIN_NAME }}.exe + mv target/${{ matrix.target }}/release/${{ env.BIN_NAME }}.exe ${{ env.BIN_NAME }}.exe + 7z a ${{ env.ARCHIVE_NAME }}.zip ${{ env.BIN_NAME }}.exe + tar -czf ${{ env.ARCHIVE_NAME }}.tar.gz ${{ env.BIN_NAME }}.exe else - cp target/${{ matrix.target }}/release/${{ env.BIN_NAME }} ${{ env.BIN_NAME }} - zip -r ${{ env.ARCHIVE_NAME }} ${{ env.BIN_NAME }} + mv target/${{ matrix.target }}/release/${{ env.BIN_NAME }} ${{ env.BIN_NAME }} + zip -r ${{ env.ARCHIVE_NAME }}.zip ${{ env.BIN_NAME }} + tar -czf ${{ env.ARCHIVE_NAME }}.tar.gz ${{ env.BIN_NAME }} fi - - name: Upload assets + - name: Upload zip artifact uses: actions/upload-artifact@v4 with: - name: ${{ env.ARCHIVE_NAME }} - path: ${{ env.ARCHIVE_NAME }} + name: ${{ env.ARCHIVE_NAME }}.zip + path: ${{ env.ARCHIVE_NAME }}.zip + + - name: Upload tar.gz artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARCHIVE_NAME }}.tar.gz + path: ${{ env.ARCHIVE_NAME }}.tar.gz publish: name: Publish to crates.io runs-on: ubuntu-latest - needs: [build] + needs: [ build ] steps: - uses: actions/checkout@v4 - name: Publish @@ -85,7 +93,7 @@ jobs: permissions: contents: write pull-requests: read - needs: [build, publish] + needs: [ build, publish ] steps: - uses: actions/checkout@v4 with: diff --git a/Cargo.lock b/Cargo.lock index ad1628d..4109a41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -87,33 +87,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -222,7 +222,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -257,7 +257,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -338,9 +338,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", @@ -450,9 +450,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", "clap_derive", @@ -460,9 +460,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -472,21 +472,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clru" @@ -496,9 +496,9 @@ checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -608,7 +608,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -655,7 +655,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -666,7 +666,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "dbus-secret-service" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3ae92ba157c1fd7e3453bf8fad9cd2e62783fe5f65047fd3b52fcbf6594aef" +checksum = "1caa0c241c01ad8d99a78d553567d38f873dd3ac16eca33a5370d650ab25584e" dependencies = [ "aes", "block-padding", @@ -722,7 +722,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -735,7 +735,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -750,10 +750,10 @@ dependencies = [ ] [[package]] -name = "directories" +name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] @@ -778,7 +778,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -832,7 +832,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1004,7 +1004,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1161,7 +1161,7 @@ dependencies = [ "gix-utils", "itoa", "thiserror", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -1243,7 +1243,7 @@ dependencies = [ "smallvec", "thiserror", "unicode-bom", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -1261,9 +1261,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.24.3" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b446df0841c9d74b3f98f21657b892581a4af78904a22e0cbc6144da972eea" +checksum = "198588f532e4d1202e04e6c3f50e4d7c060dffc66801c6f53cc246f1d234739e" dependencies = [ "bstr", "gix-command", @@ -1461,7 +1461,7 @@ checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1496,7 +1496,7 @@ dependencies = [ "itoa", "smallvec", "thiserror", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -1619,7 +1619,7 @@ dependencies = [ "gix-utils", "maybe-async", "thiserror", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -1651,7 +1651,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -1728,9 +1728,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "14.0.0" +version = "14.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b0e276cd08eb2a22e9f286a4f13a222a01be2defafa8621367515375644b99" +checksum = "006acf5a613e0b5cf095d8e4b3f48c12a60d9062aa2b2dd105afaf8344a5600c" dependencies = [ "gix-fs", "libc", @@ -2212,9 +2212,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -2224,9 +2224,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -2242,9 +2242,9 @@ dependencies = [ [[package]] name = "keyring" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9961b98f55dc0b2737000132505bdafa249abab147ee9de43c50ae04a054aa6c" +checksum = "c118b1bc529b034aad851808f41f49a69a337d10e112039e7f342e5fd514635b" dependencies = [ "byteorder", "dbus-secret-service", @@ -2256,9 +2256,9 @@ dependencies = [ [[package]] name = "kstring" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "static_assertions", ] @@ -2390,7 +2390,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2454,6 +2454,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "newline-converter" version = "0.3.0" @@ -2582,9 +2594,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -2705,7 +2717,7 @@ dependencies = [ "chrono", "clap", "colored", - "directories", + "dirs", "flate2", "full_moon", "git2", @@ -2728,8 +2740,9 @@ dependencies = [ "thiserror", "threadpool", "toml", - "toml_edit 0.22.16", + "toml_edit 0.22.17", "url", + "winreg", "zip", ] @@ -2750,7 +2763,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2885,14 +2898,13 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", "socket2", - "tracing", "windows-sys 0.52.0", ] @@ -3100,9 +3112,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "ring", @@ -3130,9 +3142,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -3228,7 +3240,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3250,14 +3262,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3301,7 +3313,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3355,7 +3367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -3445,9 +3457,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -3530,7 +3542,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3602,17 +3614,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.1", "pin-project-lite", "socket2", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3641,21 +3653,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.16", + "toml_edit 0.22.17", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] @@ -3673,15 +3685,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -3730,7 +3742,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3845,9 +3857,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -3895,7 +3907,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -3929,7 +3941,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4149,9 +4161,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.14" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" +checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" dependencies = [ "memchr", ] @@ -4189,9 +4201,9 @@ dependencies = [ [[package]] name = "zbus" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851238c133804e0aa888edf4a0229481c753544ca12a60fd1c3230c8a500fe40" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast", "async-process", @@ -4221,14 +4233,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5a3f12c20bd473be3194af6b49d50d7bb804ef3192dc70eddedb26b85d9da7" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "zvariant_utils", ] @@ -4260,7 +4272,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -4280,7 +4292,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -4356,9 +4368,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "4.1.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1724a2b330760dc7d2a8402d841119dc869ef120b139d29862d6980e9c75bfc9" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ "endi", "enumflags2", @@ -4369,24 +4381,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "4.1.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55025a7a518ad14518fb243559c058a2e5b848b015e31f1d90414f36e3317859" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] diff --git a/Cargo.toml b/Cargo.toml index 1b79c87..8264e58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,24 @@ repository = "https://github.com/daimond113/pesde" include = ["src/**/*", "Cargo.toml", "Cargo.lock", "README.md", "LICENSE", "CHANGELOG.md"] [features] -bin = ["clap", "directories", "pretty_env_logger", "reqwest/json", "reqwest/multipart", "indicatif", "indicatif-log-bridge", "inquire", "toml_edit", "colored", "anyhow", "keyring", "open", "gix/worktree-mutation", "serde_json"] +bin = [ + "clap", + "dirs", + "pretty_env_logger", + "reqwest/json", + "reqwest/multipart", + "indicatif", + "indicatif-log-bridge", + "inquire", + "toml_edit", + "colored", + "anyhow", + "keyring", + "open", + "gix/worktree-mutation", + "serde_json", + "winreg" +] wally-compat = ["zip", "serde_json", "roblox"] roblox = [] lune = [] @@ -27,7 +44,7 @@ uninlined_format_args = "warn" [dependencies] serde = { version = "1.0.204", features = ["derive"] } -toml = "0.8.15" +toml = "0.8.16" serde_with = "3.9.0" gix = { version = "0.64.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "revparse-regex", "credentials"] } semver = { version = "1.0.23", features = ["serde"] } @@ -53,16 +70,19 @@ serde_json = { version = "1.0.120", optional = true } anyhow = { version = "1.0.86", optional = true } open = { version = "5.3.0", optional = true } -keyring = { version = "3.0.3", features = ["crypto-rust", "windows-native", "apple-native", "linux-native"], optional = true } +keyring = { version = "3.0.4", features = ["crypto-rust", "windows-native", "apple-native", "linux-native"], optional = true } colored = { version = "2.1.0", optional = true } -toml_edit = { version = "0.22.16", optional = true } -clap = { version = "4.5.9", features = ["derive"], optional = true } -directories = { version = "5.0.1", optional = true } +toml_edit = { version = "0.22.17", optional = true } +clap = { version = "4.5.11", features = ["derive"], optional = true } +dirs = { version = "5.0.1", optional = true } pretty_env_logger = { version = "0.5.0", optional = true } indicatif = { version = "0.17.8", optional = true } indicatif-log-bridge = { version = "0.2.2", optional = true } inquire = { version = "0.7.5", optional = true } +[target.'cfg(target_os = "windows")'.dependencies] +winreg = { version = "0.52.0", optional = true } + [workspace] resolver = "2" members = [] diff --git a/src/cli/auth.rs b/src/cli/auth.rs new file mode 100644 index 0000000..41a61ea --- /dev/null +++ b/src/cli/auth.rs @@ -0,0 +1,75 @@ +use crate::cli::config::{read_config, write_config}; +use anyhow::Context; +use keyring::Entry; +use serde::Deserialize; +use std::path::Path; + +pub fn get_token>(data_dir: P) -> anyhow::Result> { + match std::env::var("PESDE_TOKEN") { + Ok(token) => return Ok(Some(token)), + Err(std::env::VarError::NotPresent) => {} + Err(e) => return Err(e.into()), + } + + let config = read_config(data_dir)?; + if let Some(token) = config.token { + return Ok(Some(token)); + } + + match Entry::new("token", env!("CARGO_PKG_NAME")) { + Ok(entry) => match entry.get_password() { + Ok(token) => return Ok(Some(token)), + Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} + Err(e) => return Err(e.into()), + }, + Err(keyring::Error::PlatformFailure(_)) => {} + Err(e) => return Err(e.into()), + } + + Ok(None) +} + +pub fn set_token>(data_dir: P, token: Option<&str>) -> anyhow::Result<()> { + let entry = match Entry::new("token", env!("CARGO_PKG_NAME")) { + Ok(entry) => entry, + Err(e) => return Err(e.into()), + }; + + let result = if let Some(token) = token { + entry.set_password(token) + } else { + entry.delete_credential() + }; + + match result { + Ok(()) => return Ok(()), + Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} + Err(e) => return Err(e.into()), + } + + let mut config = read_config(&data_dir)?; + config.token = token.map(|s| s.to_string()); + write_config(data_dir, &config)?; + + Ok(()) +} + +#[derive(Debug, Deserialize)] +struct UserResponse { + login: String, +} + +pub fn get_token_login( + reqwest: &reqwest::blocking::Client, + access_token: &str, +) -> anyhow::Result { + let response = reqwest + .get("https://api.github.com/user") + .header("Authorization", format!("Bearer {access_token}")) + .send() + .context("failed to send user request")? + .json::() + .context("failed to parse user response")?; + + Ok(response.login) +} diff --git a/src/cli/auth/mod.rs b/src/cli/auth/mod.rs deleted file mode 100644 index ad8f7f6..0000000 --- a/src/cli/auth/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -use anyhow::Context; -use clap::Subcommand; -use pesde::Project; -use serde::Deserialize; - -mod login; -mod logout; -mod whoami; - -#[derive(Debug, Deserialize)] -struct UserResponse { - login: String, -} - -pub fn get_token_login( - reqwest: &reqwest::blocking::Client, - access_token: &str, -) -> anyhow::Result { - let response = reqwest - .get("https://api.github.com/user") - .header("Authorization", format!("Bearer {access_token}")) - .send() - .context("failed to send user request")? - .json::() - .context("failed to parse user response")?; - - Ok(response.login) -} - -#[derive(Debug, Subcommand)] -pub enum AuthCommands { - /// Logs in into GitHub, and stores the token - Login(login::LoginCommand), - /// Removes the stored token - Logout(logout::LogoutCommand), - /// Prints the username of the currently logged-in user - #[clap(name = "whoami")] - WhoAmI(whoami::WhoAmICommand), -} - -impl AuthCommands { - pub fn run(self, project: Project) -> anyhow::Result<()> { - match self { - AuthCommands::Login(login) => login.run(project), - AuthCommands::Logout(logout) => logout.run(project), - AuthCommands::WhoAmI(whoami) => whoami.run(project), - } - } -} diff --git a/src/cli/auth/login.rs b/src/cli/commands/auth/login.rs similarity index 96% rename from src/cli/auth/login.rs rename to src/cli/commands/auth/login.rs index 3bddb52..d6b0d2b 100644 --- a/src/cli/auth/login.rs +++ b/src/cli/commands/auth/login.rs @@ -1,4 +1,7 @@ -use crate::cli::{auth::get_token_login, read_config, reqwest_client, set_token}; +use crate::cli::{ + auth::{get_token_login, set_token}, + config::read_config, +}; use anyhow::Context; use clap::Args; use colored::Colorize; @@ -171,9 +174,7 @@ impl LoginCommand { anyhow::bail!("code expired, please re-run the login command"); } - pub fn run(self, project: Project) -> anyhow::Result<()> { - let reqwest = reqwest_client(project.data_dir())?; - + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { let token = match self.token { Some(token) => token, None => self.authenticate_device_flow(&project, &reqwest)?, diff --git a/src/cli/auth/logout.rs b/src/cli/commands/auth/logout.rs similarity index 89% rename from src/cli/auth/logout.rs rename to src/cli/commands/auth/logout.rs index a2a1727..0f08210 100644 --- a/src/cli/auth/logout.rs +++ b/src/cli/commands/auth/logout.rs @@ -1,4 +1,4 @@ -use crate::cli::set_token; +use crate::cli::auth::set_token; use clap::Args; use pesde::Project; diff --git a/src/cli/commands/auth/mod.rs b/src/cli/commands/auth/mod.rs new file mode 100644 index 0000000..ea9bbbd --- /dev/null +++ b/src/cli/commands/auth/mod.rs @@ -0,0 +1,27 @@ +use clap::Subcommand; +use pesde::Project; + +mod login; +mod logout; +mod whoami; + +#[derive(Debug, Subcommand)] +pub enum AuthCommands { + /// Logs in into GitHub, and stores the token + Login(login::LoginCommand), + /// Removes the stored token + Logout(logout::LogoutCommand), + /// Prints the username of the currently logged-in user + #[clap(name = "whoami")] + WhoAmI(whoami::WhoAmICommand), +} + +impl AuthCommands { + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { + match self { + AuthCommands::Login(login) => login.run(project, reqwest), + AuthCommands::Logout(logout) => logout.run(project), + AuthCommands::WhoAmI(whoami) => whoami.run(project, reqwest), + } + } +} diff --git a/src/cli/auth/whoami.rs b/src/cli/commands/auth/whoami.rs similarity index 57% rename from src/cli/auth/whoami.rs rename to src/cli/commands/auth/whoami.rs index 6a314ee..c96aa1b 100644 --- a/src/cli/auth/whoami.rs +++ b/src/cli/commands/auth/whoami.rs @@ -1,4 +1,4 @@ -use crate::cli::{auth::get_token_login, get_token, reqwest_client}; +use crate::cli::{auth::get_token_login, get_token}; use clap::Args; use colored::Colorize; use pesde::Project; @@ -7,7 +7,7 @@ use pesde::Project; pub struct WhoAmICommand {} impl WhoAmICommand { - pub fn run(self, project: Project) -> anyhow::Result<()> { + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { let token = match get_token(project.data_dir())? { Some(token) => token, None => { @@ -16,10 +16,7 @@ impl WhoAmICommand { } }; - println!( - "logged in as {}", - get_token_login(&reqwest_client(project.data_dir())?, &token)?.bold() - ); + println!("logged in as {}", get_token_login(&reqwest, &token)?.bold()); Ok(()) } diff --git a/src/cli/config/default_index.rs b/src/cli/commands/config/default_index.rs similarity index 94% rename from src/cli/config/default_index.rs rename to src/cli/commands/config/default_index.rs index 65de195..49039f2 100644 --- a/src/cli/config/default_index.rs +++ b/src/cli/commands/config/default_index.rs @@ -1,4 +1,4 @@ -use crate::cli::{read_config, write_config, CliConfig}; +use crate::cli::config::{read_config, write_config, CliConfig}; use clap::Args; use pesde::Project; diff --git a/src/cli/config/mod.rs b/src/cli/commands/config/mod.rs similarity index 100% rename from src/cli/config/mod.rs rename to src/cli/commands/config/mod.rs diff --git a/src/cli/config/scripts_repo.rs b/src/cli/commands/config/scripts_repo.rs similarity index 94% rename from src/cli/config/scripts_repo.rs rename to src/cli/commands/config/scripts_repo.rs index 5a88f46..6f49fff 100644 --- a/src/cli/config/scripts_repo.rs +++ b/src/cli/commands/config/scripts_repo.rs @@ -1,4 +1,4 @@ -use crate::cli::{read_config, write_config, CliConfig}; +use crate::cli::config::{read_config, write_config, CliConfig}; use clap::Args; use pesde::Project; diff --git a/src/cli/init.rs b/src/cli/commands/init.rs similarity index 99% rename from src/cli/init.rs rename to src/cli/commands/init.rs index 836bd50..1cb1aef 100644 --- a/src/cli/init.rs +++ b/src/cli/commands/init.rs @@ -1,4 +1,4 @@ -use crate::cli::read_config; +use crate::cli::config::read_config; use anyhow::Context; use clap::Args; use colored::Colorize; diff --git a/src/cli/install.rs b/src/cli/commands/install.rs similarity index 89% rename from src/cli/install.rs rename to src/cli/commands/install.rs index 55b264b..52d07d5 100644 --- a/src/cli/install.rs +++ b/src/cli/commands/install.rs @@ -1,4 +1,4 @@ -use crate::cli::{home_dir, reqwest_client, IsUpToDate}; +use crate::cli::{files::make_executable, home_dir, IsUpToDate}; use anyhow::Context; use clap::Args; use indicatif::MultiProgress; @@ -55,7 +55,12 @@ end } impl InstallCommand { - pub fn run(self, project: Project, multi: MultiProgress) -> anyhow::Result<()> { + pub fn run( + self, + project: Project, + multi: MultiProgress, + reqwest: reqwest::blocking::Client, + ) -> anyhow::Result<()> { let mut refreshed_sources = HashSet::new(); let manifest = project @@ -133,7 +138,7 @@ impl InstallCommand { .download_graph( &graph, &mut refreshed_sources, - &reqwest_client(project.data_dir())?, + &reqwest, self.threads as usize, ) .context("failed to download dependencies")?; @@ -174,22 +179,16 @@ impl InstallCommand { continue; }; + if alias == env!("CARGO_BIN_NAME") { + log::warn!("package {alias} has the same name as the CLI, skipping bin link"); + continue; + } + let bin_file = bin_folder.join(format!("{alias}.luau")); std::fs::write(&bin_file, bin_link_file(alias)) .context("failed to write bin link file")?; - // TODO: test if this actually works - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - - let mut perms = std::fs::metadata(&bin_file) - .context("failed to get bin link file metadata")? - .permissions(); - perms.set_mode(perms.mode() | 0o111); - std::fs::set_permissions(&bin_file, perms) - .context("failed to set bin link file permissions")?; - } + make_executable(&bin_file).context("failed to make bin link executable")?; #[cfg(windows)] { diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs new file mode 100644 index 0000000..71168d3 --- /dev/null +++ b/src/cli/commands/mod.rs @@ -0,0 +1,76 @@ +use indicatif::MultiProgress; +use pesde::Project; + +mod auth; +mod config; +mod init; +mod install; +#[cfg(feature = "patches")] +mod patch; +#[cfg(feature = "patches")] +mod patch_commit; +mod publish; +mod run; +mod self_install; +mod self_upgrade; + +#[derive(Debug, clap::Subcommand)] +pub enum Subcommand { + /// Authentication-related commands + #[command(subcommand)] + Auth(auth::AuthCommands), + + /// Configuration-related commands + #[command(subcommand)] + Config(config::ConfigCommands), + + /// Initializes a manifest file in the current directory + Init(init::InitCommand), + + /// Runs a script, an executable package, or a file with Lune + Run(run::RunCommand), + + /// Installs all dependencies for the project + Install(install::InstallCommand), + + /// Publishes the project to the registry + Publish(publish::PublishCommand), + + /// Installs the pesde binary and scripts + SelfInstall(self_install::SelfInstallCommand), + + /// Sets up a patching environment for a package + #[cfg(feature = "patches")] + Patch(patch::PatchCommand), + + /// Finalizes a patching environment for a package + #[cfg(feature = "patches")] + PatchCommit(patch_commit::PatchCommitCommand), + + /// Installs the latest version of pesde + SelfUpgrade(self_upgrade::SelfUpgradeCommand), +} + +impl Subcommand { + pub fn run( + self, + project: Project, + multi: MultiProgress, + reqwest: reqwest::blocking::Client, + ) -> anyhow::Result<()> { + match self { + Subcommand::Auth(auth) => auth.run(project, reqwest), + Subcommand::Config(config) => config.run(project), + Subcommand::Init(init) => init.run(project), + Subcommand::Run(run) => run.run(project), + Subcommand::Install(install) => install.run(project, multi, reqwest), + Subcommand::Publish(publish) => publish.run(project), + Subcommand::SelfInstall(self_install) => self_install.run(project), + #[cfg(feature = "patches")] + Subcommand::Patch(patch) => patch.run(project, reqwest), + #[cfg(feature = "patches")] + Subcommand::PatchCommit(patch_commit) => patch_commit.run(project), + Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(project, reqwest), + } + } +} diff --git a/src/cli/patch.rs b/src/cli/commands/patch.rs similarity index 85% rename from src/cli/patch.rs rename to src/cli/commands/patch.rs index 9469c90..be0e18a 100644 --- a/src/cli/patch.rs +++ b/src/cli/commands/patch.rs @@ -1,4 +1,4 @@ -use crate::cli::{reqwest_client, IsUpToDate, VersionedPackageName}; +use crate::cli::{IsUpToDate, VersionedPackageName}; use anyhow::Context; use clap::Args; use colored::Colorize; @@ -16,7 +16,7 @@ pub struct PatchCommand { } impl PatchCommand { - pub fn run(self, project: Project) -> anyhow::Result<()> { + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { let graph = if project.is_up_to_date(true)? { project.deser_lockfile()?.graph } else { @@ -39,12 +39,7 @@ impl PatchCommand { .join(chrono::Utc::now().timestamp().to_string()); std::fs::create_dir_all(&directory)?; - source.download( - &node.node.pkg_ref, - &directory, - &project, - &reqwest_client(project.data_dir())?, - )?; + source.download(&node.node.pkg_ref, &directory, &project, &reqwest)?; // TODO: if MANIFEST_FILE_NAME does not exist, try to convert it diff --git a/src/cli/patch_commit.rs b/src/cli/commands/patch_commit.rs similarity index 100% rename from src/cli/patch_commit.rs rename to src/cli/commands/patch_commit.rs diff --git a/src/cli/publish.rs b/src/cli/commands/publish.rs similarity index 100% rename from src/cli/publish.rs rename to src/cli/commands/publish.rs diff --git a/src/cli/run.rs b/src/cli/commands/run.rs similarity index 100% rename from src/cli/run.rs rename to src/cli/commands/run.rs diff --git a/src/cli/commands/self_install.rs b/src/cli/commands/self_install.rs new file mode 100644 index 0000000..05f7af8 --- /dev/null +++ b/src/cli/commands/self_install.rs @@ -0,0 +1,88 @@ +use crate::cli::{files::make_executable, home_dir, scripts::update_scripts_folder, HOME_DIR}; +use anyhow::Context; +use clap::Args; +use colored::Colorize; +use pesde::Project; +use std::fs::create_dir_all; + +#[derive(Debug, Args)] +pub struct SelfInstallCommand { + #[cfg(windows)] + #[arg(short, long)] + skip_add_to_path: bool, +} + +impl SelfInstallCommand { + pub fn run(self, project: Project) -> anyhow::Result<()> { + update_scripts_folder(&project)?; + + let bin_dir = home_dir()?.join("bin"); + create_dir_all(&bin_dir).context("failed to create bin folder")?; + + #[cfg(windows)] + if !self.skip_add_to_path { + use winreg::{enums::HKEY_CURRENT_USER, RegKey}; + + let current_user = RegKey::predef(HKEY_CURRENT_USER); + let env = current_user + .create_subkey("Environment") + .context("failed to open Environment key")? + .0; + let path: String = env.get_value("Path").context("failed to get Path value")?; + + let exists = path + .split(';') + .any(|part| part == bin_dir.to_string_lossy().as_ref()); + + if !exists { + let new_path = format!("{path};{}", bin_dir.to_string_lossy()); + env.set_value("Path", &new_path) + .context("failed to set Path value")?; + } + + println!( + "installed {} {}!", + env!("CARGO_PKG_NAME").cyan(), + env!("CARGO_PKG_VERSION").yellow(), + ); + + if !exists { + println!( + "\nin order to allow binary exports as executables {}.\n\n{}", + format!("`~/{HOME_DIR}/bin` was added to PATH").green(), + "please restart your shell for this to take effect" + .yellow() + .bold() + ); + } + } + + #[cfg(unix)] + { + println!( + r#"installed {} {}! in order to be able to run binary exports as programs, add the following line to your shell profile: + +{} + +and then restart your shell. +"#, + env!("CARGO_PKG_NAME").cyan(), + env!("CARGO_PKG_VERSION").yellow(), + format!(r#"export PATH="$PATH:~/{}/bin""#, HOME_DIR) + .bold() + .green() + ); + } + + let copy_to = bin_dir + .join(env!("CARGO_BIN_NAME")) + .with_extension(std::env::consts::EXE_EXTENSION); + + std::fs::copy(std::env::current_exe()?, ©_to) + .context("failed to copy executable to bin folder")?; + + make_executable(©_to)?; + + Ok(()) + } +} diff --git a/src/cli/commands/self_upgrade.rs b/src/cli/commands/self_upgrade.rs new file mode 100644 index 0000000..ea71144 --- /dev/null +++ b/src/cli/commands/self_upgrade.rs @@ -0,0 +1,20 @@ +use crate::cli::{config::read_config, version::get_or_download_version}; +use clap::Args; +use pesde::Project; + +#[derive(Debug, Args)] +pub struct SelfUpgradeCommand { + #[cfg(windows)] + #[arg(short, long)] + skip_add_to_path: bool, +} + +impl SelfUpgradeCommand { + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { + let config = read_config(project.data_dir())?; + + get_or_download_version(&reqwest, &config.last_checked_updates.unwrap().1)?; + + Ok(()) + } +} diff --git a/src/cli/config.rs b/src/cli/config.rs new file mode 100644 index 0000000..1398c7b --- /dev/null +++ b/src/cli/config.rs @@ -0,0 +1,60 @@ +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CliConfig { + #[serde( + serialize_with = "crate::util::serialize_gix_url", + deserialize_with = "crate::util::deserialize_gix_url" + )] + pub default_index: gix::Url, + #[serde( + serialize_with = "crate::util::serialize_gix_url", + deserialize_with = "crate::util::deserialize_gix_url" + )] + pub scripts_repo: gix::Url, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub token: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_checked_updates: Option<(chrono::DateTime, semver::Version)>, +} + +impl Default for CliConfig { + fn default() -> Self { + Self { + default_index: "https://github.com/daimond113/pesde-index" + .try_into() + .unwrap(), + scripts_repo: "https://github.com/daimond113/pesde-scripts" + .try_into() + .unwrap(), + token: None, + + last_checked_updates: None, + } + } +} + +pub fn read_config>(data_dir: P) -> anyhow::Result { + let config_string = match std::fs::read_to_string(data_dir.as_ref().join("config.toml")) { + Ok(config_string) => config_string, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return Ok(CliConfig::default()); + } + Err(e) => return Err(e).context("failed to read config file"), + }; + + let config = toml::from_str(&config_string).context("failed to parse config file")?; + + Ok(config) +} + +pub fn write_config>(data_dir: P, config: &CliConfig) -> anyhow::Result<()> { + let config_string = toml::to_string(config).context("failed to serialize config")?; + std::fs::write(data_dir.as_ref().join("config.toml"), config_string) + .context("failed to write config file")?; + + Ok(()) +} diff --git a/src/cli/files.rs b/src/cli/files.rs new file mode 100644 index 0000000..4e6c8b6 --- /dev/null +++ b/src/cli/files.rs @@ -0,0 +1,19 @@ +use std::path::Path; + +pub fn make_executable>(_path: P) -> anyhow::Result<()> { + // TODO: test if this actually works + #[cfg(unix)] + { + use anyhow::Context; + use std::os::unix::fs::PermissionsExt; + + let mut perms = std::fs::metadata(&_path) + .context("failed to get bin link file metadata")? + .permissions(); + perms.set_mode(perms.mode() | 0o111); + std::fs::set_permissions(&_path, perms) + .context("failed to set bin link file permissions")?; + } + + Ok(()) +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 373a92c..6f1ee2f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,246 +1,21 @@ -use crate::util::authenticate_conn; +pub mod auth; +pub mod commands; +pub mod config; +pub mod files; +pub mod scripts; +pub mod version; + +use crate::cli::auth::get_token; use anyhow::Context; -use gix::remote::Direction; -use indicatif::MultiProgress; -use keyring::Entry; use pesde::{lockfile::DownloadedGraph, names::PackageNames, source::VersionId, Project}; -use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, path::Path, str::FromStr}; +use std::{collections::HashSet, str::FromStr}; -mod auth; -mod config; -mod init; -mod install; -#[cfg(feature = "patches")] -mod patch; -#[cfg(feature = "patches")] -mod patch_commit; -mod publish; -mod run; -mod self_install; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CliConfig { - #[serde( - serialize_with = "crate::util::serialize_gix_url", - deserialize_with = "crate::util::deserialize_gix_url" - )] - pub default_index: gix::Url, - #[serde( - serialize_with = "crate::util::serialize_gix_url", - deserialize_with = "crate::util::deserialize_gix_url" - )] - pub scripts_repo: gix::Url, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub token: Option, -} - -impl Default for CliConfig { - fn default() -> Self { - Self { - default_index: "https://github.com/daimond113/pesde-index" - .try_into() - .unwrap(), - scripts_repo: "https://github.com/daimond113/pesde-scripts" - .try_into() - .unwrap(), - token: None, - } - } -} - -pub fn read_config(data_dir: &Path) -> anyhow::Result { - let config_string = match std::fs::read_to_string(data_dir.join("config.toml")) { - Ok(config_string) => config_string, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - return Ok(CliConfig::default()); - } - Err(e) => return Err(e).context("failed to read config file"), - }; - - let config = toml::from_str(&config_string).context("failed to parse config file")?; - - Ok(config) -} - -pub fn write_config(data_dir: &Path, config: &CliConfig) -> anyhow::Result<()> { - let config_string = toml::to_string(config).context("failed to serialize config")?; - std::fs::write(data_dir.join("config.toml"), config_string) - .context("failed to write config file")?; - - Ok(()) -} - -pub fn get_token(data_dir: &Path) -> anyhow::Result> { - match std::env::var("PESDE_TOKEN") { - Ok(token) => return Ok(Some(token)), - Err(std::env::VarError::NotPresent) => {} - Err(e) => return Err(e.into()), - } - - let config = read_config(data_dir)?; - if let Some(token) = config.token { - return Ok(Some(token)); - } - - match Entry::new("token", env!("CARGO_PKG_NAME")) { - Ok(entry) => match entry.get_password() { - Ok(token) => return Ok(Some(token)), - Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} - Err(e) => return Err(e.into()), - }, - Err(keyring::Error::PlatformFailure(_)) => {} - Err(e) => return Err(e.into()), - } - - Ok(None) -} - -pub fn set_token(data_dir: &Path, token: Option<&str>) -> anyhow::Result<()> { - let entry = match Entry::new("token", env!("CARGO_PKG_NAME")) { - Ok(entry) => entry, - Err(e) => return Err(e.into()), - }; - - let result = if let Some(token) = token { - entry.set_password(token) - } else { - entry.delete_credential() - }; - - match result { - Ok(()) => return Ok(()), - Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} - Err(e) => return Err(e.into()), - } - - let mut config = read_config(data_dir)?; - config.token = token.map(|s| s.to_string()); - write_config(data_dir, &config)?; - - Ok(()) -} - -pub fn reqwest_client(data_dir: &Path) -> anyhow::Result { - let mut headers = reqwest::header::HeaderMap::new(); - if let Some(token) = get_token(data_dir)? { - headers.insert( - reqwest::header::AUTHORIZATION, - format!("Bearer {token}") - .parse() - .context("failed to create auth header")?, - ); - } - - headers.insert( - reqwest::header::ACCEPT, - "application/json" - .parse() - .context("failed to create accept header")?, - ); - - Ok(reqwest::blocking::Client::builder() - .user_agent(concat!( - env!("CARGO_PKG_NAME"), - "/", - env!("CARGO_PKG_VERSION") - )) - .default_headers(headers) - .build()?) -} +pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME")); pub fn home_dir() -> anyhow::Result { - Ok(directories::UserDirs::new() + Ok(dirs::home_dir() .context("failed to get home directory")? - .home_dir() - .join(concat!(".", env!("CARGO_PKG_NAME")))) -} - -pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> { - let scripts_dir = home_dir()?.join("scripts"); - - if scripts_dir.exists() { - let repo = gix::open(&scripts_dir).context("failed to open scripts repository")?; - - let remote = repo - .find_default_remote(Direction::Fetch) - .context("missing default remote of scripts repository")? - .context("failed to find default remote of scripts repository")?; - - let mut connection = remote - .connect(Direction::Fetch) - .context("failed to connect to default remote of scripts repository")?; - - authenticate_conn(&mut connection, project.auth_config()); - - let results = connection - .prepare_fetch(gix::progress::Discard, Default::default()) - .context("failed to prepare scripts repository fetch")? - .receive(gix::progress::Discard, &false.into()) - .context("failed to receive new scripts repository contents")?; - - let remote_ref = results - .ref_map - .remote_refs - .first() - .context("failed to get remote refs of scripts repository")?; - - let unpacked = remote_ref.unpack(); - let oid = unpacked - .1 - .or(unpacked.2) - .context("couldn't find oid in remote ref")?; - - let tree = repo - .find_object(oid) - .context("failed to find scripts repository tree")? - .peel_to_tree() - .context("failed to peel scripts repository object to tree")?; - - let mut index = gix::index::File::from_state( - gix::index::State::from_tree(&tree.id, &repo.objects, Default::default()) - .context("failed to create index state from scripts repository tree")?, - repo.index_path(), - ); - - let opts = gix::worktree::state::checkout::Options { - overwrite_existing: true, - destination_is_initially_empty: false, - ..Default::default() - }; - - gix::worktree::state::checkout( - &mut index, - repo.work_dir().context("scripts repo is bare")?, - repo.objects - .clone() - .into_arc() - .context("failed to clone objects")?, - &gix::progress::Discard, - &gix::progress::Discard, - &false.into(), - opts, - ) - .context("failed to checkout scripts repository")?; - - index - .write(gix::index::write::Options::default()) - .context("failed to write index")?; - } else { - std::fs::create_dir_all(&scripts_dir).context("failed to create scripts directory")?; - - let cli_config = read_config(project.data_dir())?; - - gix::prepare_clone(cli_config.scripts_repo, &scripts_dir) - .context("failed to prepare scripts repository clone")? - .fetch_then_checkout(gix::progress::Discard, &false.into()) - .context("failed to fetch and checkout scripts repository")? - .0 - .main_worktree(gix::progress::Discard, &false.into()) - .context("failed to set scripts repository as main worktree")?; - }; - - Ok(()) + .join(HOME_DIR)) } pub trait IsUpToDate { @@ -346,55 +121,3 @@ impl VersionedPackageName { pub fn parse_gix_url(s: &str) -> Result { s.try_into() } - -#[derive(Debug, clap::Subcommand)] -pub enum Subcommand { - /// Authentication-related commands - #[command(subcommand)] - Auth(auth::AuthCommands), - - /// Configuration-related commands - #[command(subcommand)] - Config(config::ConfigCommands), - - /// Initializes a manifest file in the current directory - Init(init::InitCommand), - - /// Runs a script, an executable package, or a file with Lune - Run(run::RunCommand), - - /// Installs all dependencies for the project - Install(install::InstallCommand), - - /// Publishes the project to the registry - Publish(publish::PublishCommand), - - /// Installs the pesde binary and scripts - SelfInstall(self_install::SelfInstallCommand), - - /// Sets up a patching environment for a package - #[cfg(feature = "patches")] - Patch(patch::PatchCommand), - - /// Finalizes a patching environment for a package - #[cfg(feature = "patches")] - PatchCommit(patch_commit::PatchCommitCommand), -} - -impl Subcommand { - pub fn run(self, project: Project, multi: MultiProgress) -> anyhow::Result<()> { - match self { - Subcommand::Auth(auth) => auth.run(project), - Subcommand::Config(config) => config.run(project), - Subcommand::Init(init) => init.run(project), - Subcommand::Run(run) => run.run(project), - Subcommand::Install(install) => install.run(project, multi), - Subcommand::Publish(publish) => publish.run(project), - Subcommand::SelfInstall(self_install) => self_install.run(project), - #[cfg(feature = "patches")] - Subcommand::Patch(patch) => patch.run(project), - #[cfg(feature = "patches")] - Subcommand::PatchCommit(patch_commit) => patch_commit.run(project), - } - } -} diff --git a/src/cli/scripts.rs b/src/cli/scripts.rs new file mode 100644 index 0000000..eb93d0d --- /dev/null +++ b/src/cli/scripts.rs @@ -0,0 +1,94 @@ +use crate::{ + cli::{config::read_config, home_dir}, + util::authenticate_conn, +}; +use anyhow::Context; +use gix::remote::Direction; +use pesde::Project; + +pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> { + let scripts_dir = home_dir()?.join("scripts"); + + if scripts_dir.exists() { + let repo = gix::open(&scripts_dir).context("failed to open scripts repository")?; + + let remote = repo + .find_default_remote(Direction::Fetch) + .context("missing default remote of scripts repository")? + .context("failed to find default remote of scripts repository")?; + + let mut connection = remote + .connect(Direction::Fetch) + .context("failed to connect to default remote of scripts repository")?; + + authenticate_conn(&mut connection, project.auth_config()); + + let results = connection + .prepare_fetch(gix::progress::Discard, Default::default()) + .context("failed to prepare scripts repository fetch")? + .receive(gix::progress::Discard, &false.into()) + .context("failed to receive new scripts repository contents")?; + + let remote_ref = results + .ref_map + .remote_refs + .first() + .context("failed to get remote refs of scripts repository")?; + + let unpacked = remote_ref.unpack(); + let oid = unpacked + .1 + .or(unpacked.2) + .context("couldn't find oid in remote ref")?; + + let tree = repo + .find_object(oid) + .context("failed to find scripts repository tree")? + .peel_to_tree() + .context("failed to peel scripts repository object to tree")?; + + let mut index = gix::index::File::from_state( + gix::index::State::from_tree(&tree.id, &repo.objects, Default::default()) + .context("failed to create index state from scripts repository tree")?, + repo.index_path(), + ); + + let opts = gix::worktree::state::checkout::Options { + overwrite_existing: true, + destination_is_initially_empty: false, + ..Default::default() + }; + + gix::worktree::state::checkout( + &mut index, + repo.work_dir().context("scripts repo is bare")?, + repo.objects + .clone() + .into_arc() + .context("failed to clone objects")?, + &gix::progress::Discard, + &gix::progress::Discard, + &false.into(), + opts, + ) + .context("failed to checkout scripts repository")?; + + index + .write(gix::index::write::Options::default()) + .context("failed to write index")?; + } else { + std::fs::create_dir_all(&scripts_dir).context("failed to create scripts directory")?; + + let cli_config = read_config(project.data_dir())?; + + gix::prepare_clone(cli_config.scripts_repo, &scripts_dir) + .context("failed to prepare scripts repository clone")? + .fetch_then_checkout(gix::progress::Discard, &false.into()) + .context("failed to fetch and checkout scripts repository")? + .0 + .main_worktree(gix::progress::Discard, &false.into()) + .context("failed to set scripts repository as main worktree")?; + }; + + Ok(()) +} diff --git a/src/cli/self_install.rs b/src/cli/self_install.rs deleted file mode 100644 index b50cd81..0000000 --- a/src/cli/self_install.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::cli::{home_dir, update_scripts_folder}; -use anyhow::Context; -use clap::Args; -use pesde::Project; -use std::fs::create_dir_all; - -#[derive(Debug, Args)] -pub struct SelfInstallCommand {} - -impl SelfInstallCommand { - pub fn run(self, project: Project) -> anyhow::Result<()> { - update_scripts_folder(&project)?; - - create_dir_all(home_dir()?.join("bin")).context("failed to create bin folder")?; - - // TODO: add the bin folder to the PATH - - Ok(()) - } -} diff --git a/src/cli/version.rs b/src/cli/version.rs new file mode 100644 index 0000000..e28803d --- /dev/null +++ b/src/cli/version.rs @@ -0,0 +1,218 @@ +use std::{ + fs::create_dir_all, + io::Read, + path::{Path, PathBuf}, +}; + +use anyhow::Context; +use colored::Colorize; +use reqwest::header::ACCEPT; +use semver::Version; +use serde::Deserialize; + +use crate::cli::{ + config::{read_config, write_config, CliConfig}, + files::make_executable, + home_dir, +}; + +pub fn current_version() -> Version { + Version::parse(env!("CARGO_PKG_VERSION")).unwrap() +} + +#[derive(Debug, Deserialize)] +struct Release { + tag_name: String, + assets: Vec, +} + +#[derive(Debug, Deserialize)] +struct Asset { + name: String, + url: url::Url, +} + +fn get_repo() -> (String, String) { + let mut parts = env!("CARGO_PKG_REPOSITORY").split('/').skip(3); + ( + parts.next().unwrap().to_string(), + parts.next().unwrap().to_string(), + ) +} + +const CHECK_INTERVAL: chrono::Duration = chrono::Duration::seconds(30); + +pub fn check_for_updates>( + reqwest: &reqwest::blocking::Client, + data_dir: P, +) -> anyhow::Result<()> { + let (owner, repo) = get_repo(); + + let config = read_config(&data_dir)?; + + let version = if let Some((_, version)) = config + .last_checked_updates + .filter(|(time, _)| chrono::Utc::now() - *time < CHECK_INTERVAL) + { + version + } else { + let releases = reqwest + .get(format!( + "https://api.github.com/repos/{owner}/{repo}/releases", + )) + .send() + .context("failed to send request to GitHub API")? + .json::>() + .context("failed to parse GitHub API response")?; + + let version = releases + .into_iter() + .map(|release| Version::parse(release.tag_name.trim_start_matches('v')).unwrap()) + .max() + .context("failed to find latest version")?; + + write_config( + &data_dir, + &CliConfig { + last_checked_updates: Some((chrono::Utc::now(), version.clone())), + ..config + }, + )?; + + version + }; + + if version > current_version() { + let name = env!("CARGO_PKG_NAME"); + + let unformatted_message = format!("a new version of {name} is available: {version}"); + + let message = format!( + "a new version of {} is available: {}", + name.cyan(), + version.to_string().yellow().bold() + ); + + let stars = "-" + .repeat(unformatted_message.len() + 4) + .bright_magenta() + .bold(); + let column = "|".bright_magenta().bold(); + + println!("\n{stars}\n{column} {message} {column}\n{stars}\n",); + } + + Ok(()) +} + +pub fn download_github_release( + reqwest: &reqwest::blocking::Client, + version: &Version, +) -> anyhow::Result> { + let (owner, repo) = get_repo(); + + let release = reqwest + .get(format!( + "https://api.github.com/repos/{owner}/{repo}/releases/tags/v{version}", + )) + .send() + .context("failed to send request to GitHub API")? + .json::() + .context("failed to parse GitHub API response")?; + + let asset = release + .assets + .into_iter() + .find(|asset| { + asset.name.ends_with(&format!( + "-{}-{}.tar.gz", + std::env::consts::OS, + std::env::consts::ARCH + )) + }) + .context("failed to find asset for current platform")?; + + let bytes = reqwest + .get(asset.url) + .header(ACCEPT, "application/octet-stream") + .send() + .context("failed to send request to download asset")? + .bytes() + .context("failed to download asset")?; + + let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref()); + let mut archive = tar::Archive::new(&mut decoder); + + let entry = archive + .entries() + .context("failed to read archive entries")? + .next() + .context("archive has no entry")? + .context("failed to get first archive entry")?; + + entry + .bytes() + .collect::, std::io::Error>>() + .context("failed to read archive entry bytes") +} + +pub fn get_or_download_version( + reqwest: &reqwest::blocking::Client, + version: &Version, +) -> anyhow::Result> { + #[cfg(debug_assertions)] + // possible hard to debug issues with the versioning system overtaking the debug build + return Ok(None); + + let path = home_dir()?.join("versions"); + create_dir_all(&path).context("failed to create versions directory")?; + + let path = path + .join(version.to_string()) + .with_extension(std::env::consts::EXE_EXTENSION); + + let is_requested_version = *version == current_version(); + + if path.exists() { + return Ok(if is_requested_version { + None + } else { + Some(path) + }); + } + + if is_requested_version { + std::fs::copy(std::env::current_exe()?, &path) + .context("failed to copy current executable to version directory")?; + } else { + let bytes = download_github_release(reqwest, version)?; + std::fs::write(&path, bytes).context("failed to write downloaded version file")?; + } + + make_executable(&path).context("failed to make downloaded version executable")?; + + Ok(if is_requested_version { + None + } else { + Some(path) + }) +} + +pub fn max_installed_version() -> anyhow::Result { + #[cfg(debug_assertions)] + return Ok(current_version()); + + let versions_dir = home_dir()?.join("versions"); + create_dir_all(&versions_dir).context("failed to create versions directory")?; + + let max_version = std::fs::read_dir(versions_dir) + .context("failed to read versions directory")? + .collect::, _>>()? + .into_iter() + .map(|entry| Version::parse(entry.path().file_stem().unwrap().to_str().unwrap()).unwrap()) + .max() + .filter(|v| v >= ¤t_version()) + .unwrap_or_else(current_version); + + Ok(max_version) +} diff --git a/src/main.rs b/src/main.rs index b53e9f8..638135c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,9 @@ -use crate::cli::get_token; +use crate::cli::{ + auth::get_token, + home_dir, + version::{check_for_updates, current_version, get_or_download_version, max_installed_version}, +}; +use anyhow::Context; use clap::Parser; use colored::Colorize; use indicatif::MultiProgress; @@ -18,29 +23,38 @@ struct Cli { version: (), #[command(subcommand)] - subcommand: cli::Subcommand, + subcommand: cli::commands::Subcommand, } -fn main() { +fn run() -> anyhow::Result<()> { #[cfg(windows)] - { + 'scripts: { let exe = std::env::current_exe().expect("failed to get current executable path"); + if exe.parent().is_some_and(|parent| { + parent.as_os_str() != "bin" + || parent + .parent() + .is_some_and(|parent| parent.as_os_str() != cli::HOME_DIR) + }) { + break 'scripts; + } + let exe_name = exe.with_extension(""); let exe_name = exe_name.file_name().unwrap(); - if exe_name != env!("CARGO_BIN_NAME") { - let args = std::env::args_os(); - - let status = std::process::Command::new("lune") - .arg("run") - .arg(exe.with_extension("luau")) - .args(args.skip(1)) - .current_dir(std::env::current_dir().unwrap()) - .status() - .expect("failed to run lune"); - - std::process::exit(status.code().unwrap()); + if exe_name == env!("CARGO_BIN_NAME") { + break 'scripts; } + + let status = std::process::Command::new("lune") + .arg("run") + .arg(exe.with_extension("luau")) + .args(std::env::args_os().skip(1)) + .current_dir(std::env::current_dir().unwrap()) + .status() + .expect("failed to run lune"); + + std::process::exit(status.code().unwrap()); } let multi = { @@ -54,21 +68,82 @@ fn main() { multi }; - let project_dirs = - directories::ProjectDirs::from("com", env!("CARGO_PKG_NAME"), env!("CARGO_BIN_NAME")) - .expect("couldn't get home directory"); let cwd = std::env::current_dir().expect("failed to get current working directory"); - let cli = Cli::parse(); - let data_dir = project_dirs.data_dir(); - create_dir_all(data_dir).expect("failed to create data directory"); + let data_dir = home_dir()?.join("data"); + create_dir_all(&data_dir).expect("failed to create data directory"); - if let Err(err) = get_token(data_dir).and_then(|token| { - cli.subcommand.run( - Project::new(cwd, data_dir, AuthConfig::new().with_pesde_token(token)), - multi, - ) - }) { + let token = get_token(&data_dir)?; + + let project = Project::new( + cwd, + &data_dir, + AuthConfig::new().with_pesde_token(token.as_ref()), + ); + + let reqwest = { + let mut headers = reqwest::header::HeaderMap::new(); + if let Some(token) = token { + headers.insert( + reqwest::header::AUTHORIZATION, + format!("Bearer {token}") + .parse() + .context("failed to create auth header")?, + ); + } + + headers.insert( + reqwest::header::ACCEPT, + "application/json" + .parse() + .context("failed to create accept header")?, + ); + + reqwest::blocking::Client::builder() + .user_agent(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION") + )) + .default_headers(headers) + .build()? + }; + + check_for_updates(&reqwest, &data_dir)?; + + let target_version = project + .deser_manifest() + .ok() + .and_then(|manifest| manifest.pesde_version); + + // store the current version in case it needs to be used later + get_or_download_version(&reqwest, ¤t_version())?; + + let exe_path = if let Some(version) = target_version { + Some(get_or_download_version(&reqwest, &version)?) + } else { + None + }; + let exe_path = if let Some(exe_path) = exe_path { + exe_path + } else { + get_or_download_version(&reqwest, &max_installed_version()?)? + }; + + if let Some(exe_path) = exe_path { + let status = std::process::Command::new(exe_path) + .args(std::env::args_os().skip(1)) + .status() + .expect("failed to run new version"); + + std::process::exit(status.code().unwrap()); + } + + Cli::parse().subcommand.run(project, multi, reqwest) +} + +fn main() { + if let Err(err) = run() { eprintln!("{}: {err}\n", "error".red().bold()); let cause = err.chain().skip(1).collect::>(); diff --git a/src/manifest/mod.rs b/src/manifest/mod.rs index 371a549..3ae2743 100644 --- a/src/manifest/mod.rs +++ b/src/manifest/mod.rs @@ -51,6 +51,8 @@ pub struct Manifest { #[cfg(feature = "patches")] #[serde(default, skip_serializing)] pub patches: BTreeMap>, + #[serde(default, skip_serializing)] + pub pesde_version: Option, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub dependencies: BTreeMap,