diff --git a/.gitbook.yaml b/.gitbook.yaml index 4f32c861..f2540ca5 100644 --- a/.gitbook.yaml +++ b/.gitbook.yaml @@ -1,20 +1,26 @@ root: ./docs/ redirects: - pantry: pantry.md - shell-integration: shell-integration.md - shellcode: shell-integration.md - getting-started: run/anywhere/terminals.md - quickstart: run/anywhere/terminals.md - installing-w/out-brew: run/anywhere/terminals.md - docker: run/anywhere/docker.md - ci-cd: run/anywhere/ci-cd.md - scripts: run/anywhere/scripts.md - editors: run/anywhere/editors.md - pkgx-install: pkgx-install.md - install: pkgx-install.md - support: support.md - dev: dev.md + installing-w/out-brew: getting-started.md + pantry: pkging/pantry.md + getting-started: getting-started.md + quickstart: getting-started.md + help/pkg-not-cached: https://github.com/orgs/pkgxdev/discussions/new?category=help&title=pkg-not-cached help/http-failure: https://github.com/orgs/pkgxdev/discussions/new?category=help&title=http-failure help/ambiguous-pkgspec: https://github.com/orgs/pkgxdev/discussions/new?category=help&title=ambiguous-pkgspec + + # links should never die + docker: getting-started.md + ci-cd: getting-started.md + scripts: scripting.md + run/anywhere/terminals.md: getting-started.md + run/anywhere/docker.md: getting-started.md + run/anywhere/ci-cd.md: getting-started.md + run/anywhere/scripts.md: scripting.md + pantry.md: pkging/pantry.md + pantry-api.md: pkging/pantry.md + pkgx-install: https://github.com/pkgxdev/pkgm + install: https://github.com/pkgxdev/pkgm + support: https://github.com/pkgxdev/discussions + dev: https://github.com/pkgxdev/dev diff --git a/.github/Dockerfile b/.github/Dockerfile index 5040172d..23ee4b7d 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -1,12 +1,13 @@ FROM debian:buster-slim as stage0 COPY ./products/* /pkgx/ -RUN cp /pkgx/$(uname -m) /usr/local/bin/pkgx +RUN install -m 755 /pkgx/$(uname -m) /usr/local/bin/pkgx +RUN install -m 755 /pkgx/pkgm /usr/local/bin/pkgm RUN echo 'export PS1="\\[\\033[38;5;63m\\]pkgx\\[\\033[0m\\]\\w $ "' >> /root/.bashrc -RUN pkgx integrate FROM debian:buster-slim as stage1 -RUN apt-get update && apt --yes install libc-dev libstdc++-8-dev libgcc-8-dev netbase libudev-dev +RUN apt-get update && apt --yes install libc-dev libstdc++-8-dev libgcc-8-dev netbase libudev-dev ca-certificates COPY --from=stage0 /usr/local/bin/pkgx /usr/local/bin/pkgx +COPY --from=stage0 /usr/local/bin/pkgm /usr/local/bin/pkgm COPY --from=stage0 /root/.bashrc /root/.bashrc ENV BASH_ENV /root/.bashrc SHELL ["/bin/bash", "-c"] diff --git a/.github/markdownlint.yml b/.github/markdownlint.yml index b80859cc..c881ee57 100644 --- a/.github/markdownlint.yml +++ b/.github/markdownlint.yml @@ -1,32 +1,17 @@ -# use banner rather than an h1 at the top of the file -MD041: false - -# extra lines to offset content -MD012: false - -# skipping heading levels as needed for display purposes -MD001: false +default: + true -# "prompts" in code examples -MD014: false - -# consecutive distinct blockquotes -MD028: false - -# sometimes, you need HTML:
-MD033: false +MD013: + code_blocks: false + tables: false -# duplicate headers are sometimes useful -MD024: false +# Honestly most of the lint rules are absurd, we disable these *at least* -# can't split up a table row -MD013: false - -# some titles end in 'etc.' -MD026: false - -# gitbook provides its own top-level heading so we use `#` as `##` +# First line in a file should be a top-level heading +MD041: false +# Multiple top-level headings in the same document MD025: false - -# this is working around gitbook styling issues -MD029: false +# Inline HTML +MD033: false +# Multiple consecutive blank lines +MD012: false diff --git a/.github/workflows/cd.brew.yml b/.github/workflows/cd.brew.yml index ef4ef399..bad4b2e9 100644 --- a/.github/workflows/cd.brew.yml +++ b/.github/workflows/cd.brew.yml @@ -13,7 +13,7 @@ jobs: bump-tap: runs-on: ubuntu-latest steps: - - uses: aurelien-baudet/workflow-dispatch@v2 + - uses: aurelien-baudet/workflow-dispatch@v4 with: workflow: bump.yml repo: pkgxdev/homebrew-made diff --git a/.github/workflows/cd.docker.yml b/.github/workflows/cd.docker.yml index 20891fe2..d01f56f3 100644 --- a/.github/workflows/cd.docker.yml +++ b/.github/workflows/cd.docker.yml @@ -17,7 +17,7 @@ jobs: build-and-push-image: runs-on: ubuntu-latest steps: - - uses: robinraju/release-downloader@v1.9 + - uses: robinraju/release-downloader@v1.11 with: releaseId: ${{ github.event.release.id }} @@ -35,6 +35,8 @@ jobs: mv linux+x86-64 products/x86_64 mv linux+aarch64 products/aarch64 + curl -o products/pkgm https://pkgxdev.github.io/pkgm/pkgm.ts + - uses: actions/checkout@v4 with: path: src diff --git a/.github/workflows/cd.vx.yml b/.github/workflows/cd.vx.yml index 9883ad1f..65b3eea0 100644 --- a/.github/workflows/cd.vx.yml +++ b/.github/workflows/cd.vx.yml @@ -20,3 +20,6 @@ jobs: - uses: fischerscode/tagger@v0 with: prefix: v + - run: | + git tag -f latest + git push origin latest --force diff --git a/.github/workflows/cd.www.yml b/.github/workflows/cd.www.yml index 4d1b68a6..27cc4f2e 100644 --- a/.github/workflows/cd.www.yml +++ b/.github/workflows/cd.www.yml @@ -25,7 +25,7 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - - uses: robinraju/release-downloader@v1.9 + - uses: robinraju/release-downloader@v1.11 with: releaseId: ${{ github.event.release.id || github.event.inputs.release-id }} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 28e92b21..841dbb7b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,14 +14,20 @@ concurrency: jobs: qa: - uses: ./.github/workflows/ci.yml - - integration-tests: - uses: ./.github/workflows/ci.shellcode.yml + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --all-features + env: + RUSTFLAGS: "-D warnings" attach-srcs: runs-on: ubuntu-latest - needs: [qa, integration-tests] + needs: qa env: FILENAME: pkgx-${{ github.event.inputs.version }} steps: @@ -30,8 +36,6 @@ jobs: path: ${{ env.FILENAME }} - name: clean run: rm -rf ${{ env.FILENAME }}/.github .gitbook.yml - - name: stamp version.ts - run: echo "export default function() { return '${{github.event.inputs.version}}' }" > $FILENAME/src/modes/version.ts - name: include GPG pubkey run: echo "${{ secrets.GPG_PUBLIC_KEY }}" | base64 -d > $FILENAME/pkgx.dev.pub.asc - run: tar cJf $FILENAME.tar.xz $FILENAME @@ -77,32 +81,37 @@ jobs: name: srcs - uses: pkgxdev/setup@v2 - with: - +: unzip xz - - run: pkgx +xz tar xJf pkgx-${{ github.event.inputs.version }}.tar.xz --strip-components=1 + - run: pkgx +xz -- tar xJf pkgx-${{ github.event.inputs.version }}.tar.xz --strip-components=1 - # we would prefer this, but our pkging is not stable enough :/ - # - uses: pkgxdev/dev@v0 - - uses: denoland/setup-deno@v2 - with: - deno-version: ^2.1.4 + - uses: dtolnay/rust-toolchain@stable - - run: deno task compile + # we don’t get why it’s only linux+aarch64 that lacks a linker either + # we use our own perl as the system perl does not have FindBin.pm + - run: | + if [ "${{ matrix.platform.build-id }}" = "linux+aarch64" ]; then + set -a + eval "$(pkgx +llvm.org +perl)" + AR=llvm-ar + set +a + fi + cargo build --release + mv target/release/pkgx . + strip ./pkgx - - uses: pkgxdev/brewkit/actions/setup-codesign@v0 + - uses: pkgxdev/pantry/.github/actions/setup@main if: startsWith(matrix.platform.build-id, 'darwin+') with: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }} p12-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }} + APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }} - # codesign always fails for deno binaries, even though it - # signs fine. See https://github.com/denoland/deno/issues/575 - run: codesign --sign "$APPLE_IDENTITY" --force - --preserve-metadata=entitlements,requirements,flags,runtime ./pkgx || true + --preserve-metadata=entitlements,requirements,flags,runtime ./pkgx env: APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }} + if: startsWith(matrix.platform.build-id, 'darwin+') - name: sanity check run: test "$(./pkgx --version)" = "pkgx ${{ github.event.inputs.version }}" diff --git a/.github/workflows/ci.docker.yaml b/.github/workflows/ci.docker.yaml deleted file mode 100644 index d1c1f538..00000000 --- a/.github/workflows/ci.docker.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: ci·docker - -on: - pull_request: - paths: - - .github/Dockerfile - -concurrency: - group: ci/docker/${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - packages: write - -jobs: - build-and-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - run: | - mkdir products - curl https://pkgx.sh/$(uname)/$(uname -m).gz | gunzip > products/$(uname -m) - chmod +x products/* - - - run: - docker build - --tag pkgxdev/pkgx - --file .github/Dockerfile - . - - - run: | - cat < Dockerfile - FROM pkgxdev/pkgx - RUN env +duf && duf - RUN if which duf; then exit 1; fi - EoD - - docker build --file Dockerfile . - - - run: | - cat < Dockerfile - FROM pkgxdev/pkgx - RUN echo '{}' > package.json - RUN dev && npm --version - RUN if which npm; then exit 1; fi - EoD - - docker build --file Dockerfile . diff --git a/.github/workflows/ci.docker.yml b/.github/workflows/ci.docker.yml new file mode 100644 index 00000000..709be33e --- /dev/null +++ b/.github/workflows/ci.docker.yml @@ -0,0 +1,65 @@ +name: ci·docker + +on: + pull_request: + paths: + - .github/Dockerfile + - .github/workflows/ci.docker.yml + +concurrency: + group: ci/docker/${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write + +jobs: + docker-build: + runs-on: ubuntu-latest + container: debian:buster-slim + steps: + - uses: actions/checkout@v4 + + - run: apt-get update && apt-get install -y curl gcc perl make + + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --release + + - uses: actions/upload-artifact@v4 + with: + name: products + path: ./target/release/pkgx + + docker-test: + runs-on: ubuntu-latest + needs: docker-build + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: products + + - run: | + mkdir products + mv ./pkgx products/$(uname -m) + curl https://pkgxdev.github.io/pkgm/pkgm.ts -o products/pkgm + + - run: + docker build + --tag pkgxdev/pkgx + --file .github/Dockerfile + . + + - run: | + cat < Dockerfile + FROM pkgxdev/pkgx + RUN pkgx --version + RUN if git --version; then exit 1; fi + RUN pkgx git --version + RUN pkgm install git + RUN if ! git --version; then exit 2; fi + EoD + + docker build --file Dockerfile . diff --git a/.github/workflows/ci.md.yml b/.github/workflows/ci.md.yml index fa27b59f..27fda289 100644 --- a/.github/workflows/ci.md.yml +++ b/.github/workflows/ci.md.yml @@ -1,9 +1,9 @@ -name: ci·markdown-lint +name: ci·md on: pull_request: paths: - - '**.md' + - 'docs/**.md' - .github/workflows/ci.md.yml concurrency: diff --git a/.github/workflows/ci.shellcode.yml b/.github/workflows/ci.shellcode.yml deleted file mode 100644 index 734ec5f0..00000000 --- a/.github/workflows/ci.shellcode.yml +++ /dev/null @@ -1,455 +0,0 @@ -name: ci·shellcode - -on: - pull_request: - paths: - - src/modes/shellcode.* - - src/prefab/construct-env.* - - .github/workflows/ci.shellcode.yml - - workflow_call: - -concurrency: - group: ${{ github.ref }}/shellcode - cancel-in-progress: true - -jobs: - compile: - runs-on: ubuntu-latest - steps: - - uses: denoland/setup-deno@v2 - with: - deno-version: ^2.1.4 - - uses: actions/checkout@v4 - - run: deno task compile - - uses: actions/upload-artifact@v4 - with: - name: pkgx - path: pkgx - - #NOTE bash/zsh/POSIX jobs are duplicated because `shell` cannot be set by a matrix :-/ - bash: - needs: compile - runs-on: ubuntu-latest - container: debian:buster-slim - defaults: - run: - shell: bash -eo pipefail {0} - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: | - apt-get update && apt-get --yes install bash libatomic1 - chmod u+x /usr/local/bin/pkgx - - - run: | - test -n "$BASH_VERSION" - echo $PATH - - - name: ensure test specimens are not installed on the runners - run: | - if which duf node; then - exit 1 - fi - - - name: env +duf doesn’t add duf until the shellcode is loaded - run: | - if env +duf; then - exit 1 - fi - if which duf node; then - exit 1 - fi - - - name: test `env +foo` shellcode - run: | - eval "$(pkgx --shellcode)" - - env +duf - duf --version - env -duf - - # check deactivate has worked - if which duf; then - exit 3 - fi - - - name: test `dev` shellcode - run: | - eval "$(pkgx --shellcode)" - - echo '{}' > package.json - dev - node --version - - cd .. - if which node; then - exit 4 - fi - - cd - - node --version - - #TODO need to be in the pantry first - # - name: pkgx@latest - # run: | - # eval "$(pkgx --shellcode)" - # pkgx@latest --version - - - name: ~/.local/bin in PATH - run: | - mkdir -p ~/.local/bin - echo '#!/bin/sh' > ~/.local/bin/foo - echo 'echo hi' >> ~/.local/bin/foo - chmod u+x ~/.local/bin/foo - - if foo; then - exit 1 - fi - - eval "$(pkgx --shellcode)" - test $(foo) = hi - - - name: POSIXLY_CORRECT - run: | - eval "$(pkgx --shellcode)" - env +duf - duf --version - shell: bash -eo posix {0} - - zsh: - needs: compile - runs-on: ubuntu-latest - container: debian:buster-slim - defaults: - run: - shell: zsh -eo pipefail {0} - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: | - apt-get update && apt-get --yes install zsh libatomic1 - chmod u+x /usr/local/bin/pkgx - shell: sh # ∵ cannot be zsh until zsh is installed - - # NOTE matrix does not work for the shell key :-/ - - run: | - test -n "$ZSH_VERSION" - echo $PATH - - - run: | - if which duf node optipng; then - exit 1 - fi - name: ensure test specimens are not installed on the runners - - - name: env +duf doesn’t add duf until the shellcode is loaded - run: | - if env +duf; then - exit 1 - fi - if which duf node; then - exit 1 - fi - - - name: test `env +foo` shellcode - run: | - eval "$(pkgx --shellcode)" - - env +duf - duf --version - env +optipng - optipng --version - env -duf -optipng - - # check deactivate has worked - if which duf optipng >/dev/null; then - exit 3 - fi - - - name: test `pkgx activate` shellcode - run: | - eval "$(pkgx --shellcode)" - - echo '{}' > package.json - dev - node --version - - cd .. - if which node >/dev/null; then - exit 4 - fi - - cd - - node --version - - # subfolders are still considered “in-env” - mkdir foo - cd foo - node --version - - dev off - - if which node >/dev/null; then - exit 5 - fi - - #TODO need to be in the pantry first - # - name: pkgx@latest - # run: | - # eval "$(pkgx --shellcode)" - # pkgx@latest --version - - - name: ~/.local/bin in PATH - run: | - mkdir -p ~/.local/bin - echo '#!/bin/sh' > ~/.local/bin/foo - echo 'echo hi' >> ~/.local/bin/foo - chmod u+x ~/.local/bin/foo - - if foo; then - exit 1 - fi - - eval "$(pkgx --shellcode)" - test $(foo) = hi - - - name: emulate -L sh - run: | - emulate -L sh - eval "$(pkgx --shellcode)" - env +duf - duf --version - - POSIX: - needs: compile - runs-on: ubuntu-latest - container: debian:buster-slim - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: | - apt-get update && apt-get --yes install libatomic1 - chmod u+x /usr/local/bin/pkgx - - # NOTE matrix does not work for the shell key :-/ - - name: test `use` shellcode - run: | - # ensure test specimens are not installed on the runners - if which duf node; then - exit 1 - fi - - # env +foo should not work until the shellcode is evaluated - if env +duf; then - exit 1 - fi - if which duf node; then - exit 1 - fi - - eval "$(pkgx --shellcode)" - - env +duf - duf --version - env +optipng - optipng --version - env -duf -optipng - - # check env is now empty again - if which duf >/dev/null; then - exit 3 - fi - - # verifies our assumption that we are running in “POSIX mode” - - name: pkgx@latest fails - run: | - eval "$(pkgx --shellcode)" - - if pkgx@latest --version; then - exit 1 - fi - - - name: test `dev` shellcode - run: | - eval "$(pkgx --shellcode)" - - echo '{}' > package.json - dev - node --version - # ^^ POSIX mode cannot persist or deactivate activations - - # verifies pkgx install installs the version being used - test-install: - needs: compile - runs-on: ubuntu-latest - container: debian:buster-slim - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: | - apt-get update && apt-get --yes install bash libatomic1 - chmod u+x /usr/local/bin/pkgx - - - run: | - eval "$(pkgx --shellcode)" - - env +node^16 - pkgx install - - #TODO - #if pkgx -node; then - # exit 3 - #fi - - node --version | grep v16 - shell: bash - - test-plus-pkg-syntax: - runs-on: ubuntu-latest - needs: compile - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - name: prep - run: | - chmod u+x /usr/local/bin/pkgx - - - run: pkgx +ack node -e 'console.log()' - - test-shebangs: - runs-on: ubuntu-latest - needs: compile - container: debian:buster-slim - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: | - apt-get update && apt-get --yes install bash libatomic1 - chmod u+x /usr/local/bin/pkgx - - - run: | - echo '#!/usr/bin/env -S pkgx +ack node' > foo - echo 'console.log("sh.pkgx")' >> foo - chmod u+x foo - - - run: test $(./foo) = 'sh.pkgx' - - run: if pkgx ./foo; then exit 1; fi - - run: if pkgx foo; then exit 1; fi - - - run: | - echo '#!/usr/bin/env node' > foo - echo 'console.log("sh.pkgx")' >> foo - - - run: test $(pkgx ./foo) = 'sh.pkgx' - - run: test $(pkgx foo) = 'sh.pkgx' - - run: if ./foo; then exit 1; fi - - fork-bomb-protector-check: - runs-on: ubuntu-latest - needs: compile - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - # create a fork bomb, but since it’s via pkgx we prevent it - - run: | - echo '#!/bin/sh' > foo - echo 'pkgx -- /bin/sh "$0"' >> foo - chmod u+x foo - - run: | - if ./foo; then - exit 1 - fi - - no-shell-output-if-no-tty: - needs: compile - runs-on: ubuntu-latest - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - - name: prep - run: chmod u+x /usr/local/bin/pkgx - - #TODO ideally we'd test something that uses another tool in pkgx and - # is poorly written so it reads both stdout and stderr eg `lazygit` - # but CI doesn't have ttys and I couldn't figure out another way - # ref https://github.com/actions/runner/issues/241 - #NOTE CI has no ttys so the opposite test is v. hard to achieve - - run: | - pkgx echo 2> >(tee temp_file) >/dev/null - if [[ -s temp_file ]]; then - echo - echo 'pkgx output logging info when no tty present' >&2 - exit 1 - fi - - cannot-multi-dev: - needs: compile - runs-on: ubuntu-latest - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - name: prep - run: chmod u+x /usr/local/bin/pkgx - - - run: echo '{}' > package.json - - - run: | - eval "$(pkgx --shellcode)" - - dev - - if dev; then - echo 'dev did not fail when already activated' >&2 - exit 1 - fi - - x-already-defined-works: - needs: compile - runs-on: ubuntu-latest - defaults: - run: - shell: zsh -eo pipefail {0} - steps: - - uses: actions/download-artifact@v4 - with: - path: /usr/local/bin - name: pkgx - - name: prep - run: | - sudo apt-get update && sudo apt-get --yes install zsh libatomic1 - chmod u+x /usr/local/bin/pkgx - shell: sh - - run: | - alias x="echo hi" - eval "$(pkgx --shellcode)" - test "$(x)" = hi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fa272ef..24949aa6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,72 +1,102 @@ -name: ci - on: - pull_request: - paths: - - deno.jsonc - - '**/*.ts' - - .github/workflows/ci.yml - workflow_call: push: - branches: main + branches: + - main paths: - - deno.jsonc - - '**/*.ts' - - .github/workflows/ci.yml + - "**/*.rs" + - "**/Cargo.toml" + - Cargo.lock + pull_request: + +name: ci·rs concurrency: - group: ${{ github.ref }} + group: ci/rs/${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: - tests: - runs-on: ${{ matrix.os }} + check: strategy: - fail-fast: false matrix: - os: - - ubuntu-latest - - macos-latest + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - - run: echo 'TMPDIR=/tmp' >> $GITHUB_ENV - if: ${{ matrix.os == 'ubuntu-latest' }} + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check + env: + RUSTFLAGS: "-D warnings" + fmt: + runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 # using ourself to install deno could compromise the tests + - uses: dtolnay/rust-toolchain@stable with: - deno-version: ^2.1.4 - - run: deno cache **/*.test.ts - - run: deno task test --coverage=cov_profile --no-check - - run: deno coverage cov_profile --lcov --exclude=tests/ --output=cov_profile.lcov + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - run: cargo clippy --all-features + env: + RUSTFLAGS: "-D warnings" + + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --all-features + env: + RUSTFLAGS: "-D warnings" + + smoke: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + needs: [check] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo run --all-features -- git --version + + coverage: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-tarpaulin + - run: cargo tarpaulin -o lcov --output-dir coverage - uses: coverallsapp/github-action@v2 with: - path-to-lcov: cov_profile.lcov + path-to-lcov: coverage/lcov.info parallel: true flag-name: ${{ matrix.os }} upload-coverage: - needs: tests + needs: coverage if: ${{ always() }} runs-on: ubuntu-latest steps: - uses: coverallsapp/github-action@v2 with: parallel-finished: true - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 # using ourself to install deno could compromise the tests - with: - deno-version: ^2.1.4 - - run: deno lint - - typecheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 - with: - deno-version: ^2.1.4 - - run: deno task typecheck diff --git a/.github/workflows/release-packaging.yaml.todo b/.github/workflows/release-packaging.yaml.todo new file mode 100644 index 00000000..2e33d10f --- /dev/null +++ b/.github/workflows/release-packaging.yaml.todo @@ -0,0 +1,36 @@ +# https://github.com/BamPeers/rust-ci-github-actions-workflow + +# TODO: +# on: +# release: +# types: [published] + +# name: Release Packaging + +# jobs: +# release: +# name: Release Packaging +# env: +# PROJECT_NAME_UNDERSCORE: pkgx +# RUSTFLAGS: "-D warnings" +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: dtolnay/rust-toolchain@stable +# - name: Release Build +# run: cargo build --release +# - name: "Upload Artifact" +# uses: actions/upload-artifact@v4 +# with: +# name: ${{ env.PROJECT_NAME_UNDERSCORE }} +# path: target/release/${{ env.PROJECT_NAME_UNDERSCORE }} + +# publish: +# name: Publish to crates.io +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: dtolnay/rust-toolchain@stable +# - uses: katyo/publish-crates@v2 +# with: +# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.gitignore b/.gitignore index 971e6ef3..05923927 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ +/target .DS_Store -/pkgx - -.idea/ -cov_profile** diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 7e81106f..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "request": "launch", - "name": "Debug Test", - "type": "node", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": [ - "test", - "--unstable", - "--inspect-brk", - "--allow-all", - "${file}" - ], - "attachSimplePort": 9229 - }, - { - "request": "launch", - "name": "Debug pkgx with args", - "type": "node", - "cwd": "${workspaceFolder}", - "runtimeExecutable": "deno", - "runtimeArgs": [ - "run", - "--unstable", - "--inspect-brk", - "--allow-all", - "src/app.ts" - ], - "args": "${input:args}", - "attachSimplePort": 9229 - } - ], - "inputs": [ - { - "id": "args", - "type": "promptString", - "default": "", - "description": "The arguments for pkgx" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 88f01e6a..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "deno.enable": true, - "deno.lint": true, - "deno.unstable": true, - "deno.config": "deno.jsonc", - "markdownlint.config": { - "extends": "./.github/markdownlint.yml" - }, - "cSpell.words": [ - "shellcode" - ] -} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..dcf8e35a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2050 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "xz2", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "indicatif" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libpkgx" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-compression", + "dirs-next", + "fs2", + "futures", + "lazy_static", + "libsemverator", + "nix", + "regex", + "reqwest", + "rusqlite", + "serde", + "serde_yaml", + "strum", + "strum_macros", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.7.0", + "libc", + "redox_syscall 0.5.8", +] + +[[package]] +name = "libsemverator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba788049ee5796a7b329a6e2a8a3ddb9912379b3837b11e2ccd49bef555f7851" +dependencies = [ + "anyhow", + "lazy_static", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.7.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.7.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.4.1+3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.8", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "pkgx" +version = "2.0.0" +dependencies = [ + "console", + "indicatif", + "libpkgx", + "native-tls", + "nix", + "regex", + "rusqlite", + "serde_json", + "tokio", +] + +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.7.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg", +] + +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.7.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +dependencies = [ + "bitflags 2.7.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.7.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xattr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..cb61fc7d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["crates/cli", "crates/lib"] +resolver = "2" + +[profile.release] +lto = "fat" diff --git a/README.md b/README.md index 8529135c..5daea04c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![pkgx.dev](https://pkgx.dev/banner.png) -`pkgx` is a single, *standalone binary* that can *run anything*. +`pkgx` is a 4MB, *standalone* binary that can *run anything*.   [![coverage][]][coveralls] [![teaRank][]](https://tea.xyz)   @@ -9,11 +9,10 @@ ### Quickstart ```sh -brew install pkgxdev/made/pkgx +brew install pkgxdev/made/pkgx || sh <(curl https://pkgx.sh) ``` -> * [docs.pkgx.sh/installing-w/out-brew] -> * [Migrating from v0](https://blog.pkgx.dev/pkgx-1-0-0-alpha-1/) +> [docs.pkgx.sh/installing-w/out-brew]   @@ -25,12 +24,12 @@ $ deno command not found: deno $ pkgx deno -Deno 1.36.3 +Deno 2.1.4 > ^D $ deno command not found: deno -# ^^ nothing was installed; your system remains untouched +# ^^ nothing was installed; your wider system is untouched ``` @@ -96,15 +95,15 @@ Python 2.7.18 *
CI/CD
```yaml - - uses: pkgxdev/setup@v1 + - uses: pkgxdev/setup@v2 - run: pkgx shellcheck ``` Or in other CI/CD providers: ```sh - $ curl https://pkgx.sh | sh - $ pkgx shellcheck + curl https://pkgx.sh | sh + pkgx shellcheck ``` > [docs.pkgx.sh/ci-cd] @@ -149,106 +148,217 @@ Python 2.7.18   +# The `pkgx` Ecosystem -# Shell Integration +`pkgx` is not just a package runner, it’s a composable primitive that can be +used to build a whole ecosystem of tools. -`pkgx` puts the whole open source ecosystem at your fingertips and its -***optional*** shell integration makes workflows with that open source -even more seamless. +## `dev` + +`dev` uses `pkgx` and shellcode to create “virtual environments” consisting +of the specific versions of tools and their dependencies you need for your +projects. ```sh -$ env +go@1.16 # do `pkgx integrate --dry-run` first -added ~/.pkgx/go.dev/v1.16 to environment +$ cd my-rust-proj && ls +Cargo.toml src/ + +my-rust-proj $ cargo build +command not found: cargo + +my-rust-proj $ dev ++rust +cargo -(+go) $ go -Go is a tool for managing Go source code. +my-rust-proj $ cargo build +Compiling my-rust-proj v0.1.0 #… +``` + +> [github.com/pkgxdev/dev][dev] + + +## `pkgm` + +`pkgm` installs `pkgx` packages to `/usr/local`. It installs alongside `pkgx`. + +> [github.com/pkgxdev/pkgm][pkgm] + + +## Scripting -(+go) $ env | grep go -PATH=~/.pkgx/go.dev/v1.16.15/bin:$PATH -LIBRARY_PATH=~/.pkgx/go.dev/v1.16.15/lib +A powerful use of `pkgx` is scripting, eg. here’s a script to release new +versions to GitHub: -(+go) $ env -go -removed ~/.pkgx/go.dev/v1.16 from environment +```sh +#!/usr/bin/env -S pkgx +gum +gh +npx +git bash>=4 -eo pipefail + +gum format "# determining new version" + +versions="$(git tag | grep '^v[0-9]\+\.[0-9]\+\.[0-9]\+')" +v_latest="$(npx -- semver --include-prerelease $versions | tail -n1)" +v_new=$(npx -- semver bump $v_latest --increment $1) + +gum format "# releasing v$v_new" -$ go -command not found: go +gh release create \ + $v_new \ + --title "$v_new Released 🎉" \ + --generate-notes \ + --notes-start-tag=v$v_latest ``` -Tools are available for the duration of your terminal session. -If you need them for longer, eg. `pkgx install go`. +Above you can see how we “loaded” the shebang with `+pkg` syntax to bring in +all the tools we needed. -> [docs.pkgx.sh/shell-integration] \ -> [docs.pkgx.sh/pkgx-install] +> We have pretty advanced versions of the above script, eg +> [teaBASE][teaBASE-release-script] -## `dev` +There’s tools for just about every language ecosystem so you can import +dependencies. For example, here we use `uv` to run a python script with +pypi dependencies, and pkgx to load both `uv` and a specific python version: + +```sh +#!/usr/bin/env -S pkgx +python@3.11 uv run --script + +# /// script +# dependencies = [ +# "requests<3", +# "rich", +# ] +# /// + +import requests +from rich.pretty import pprint + +resp = requests.get("https://peps.python.org/api/peps.json") +data = resp.json() +pprint([(k, v["title"]) for k, v in data.items()][:10]) +``` + +> [!TIP] +> +> ### Mash +> +> We love scripting with `pkgx` so much that we made a whole package manager +> for scripts to show the world what is possible when the whole open source +> ecosystem is available to your scripts Check it out [`mash`]. + +## Recursive Run -`dev` is a separate tool that leverages pkgx's core -features to auto-detect and install project dependencies, seamlessly -integrating them into your shell and editor. +Easily run tools from other language ecosystems: ```sh -my-rust-proj $ dev # do `pkgx integrate --dry-run` first -dev: found Cargo.toml; env +cargo +rust +pkgx uvx cowsay "Run Python (PyPi) programs with `uvx`" # or pipx +pkgx bunx cowsay "Run JavaScript (NPM) programs tools with `bunx`" # or `npx` +``` -(+cargo+rust) my-rust-proj $ cargo build -Compiling my-rust-proj v0.1.0 -#… +## Magic + +It can be fun to add magic to your shell: + +```sh +# add to ~/.zshrc +command_not_found_handler() { + pkgx -- "$@" +} ``` -The `dev` tool requires our shell integration to work. +Thus if you type `gh` and it’s not installed pkgx will magically run it as +though it was installed all along. -> [docs.pkgx.sh/dev][dev] +> [!NOTE] +> Bash is the same function but drop the `r` from the end of the name.   +# Further Reading -# Getting Started +[docs.pkgx.sh][docs] is a comprehensive manual and user guide for the `pkgx` +suite. + +  + + +# Migrating from `pkgx`^1 + +## Shellcode + +The `pkgx` suite has had its scopes tightened. There is no shellcode in `pkgx` +anymore. Instead [`dev`] is its own separate tool that has its own shellcode. +Migrate your shell configuration with: ```sh -brew install pkgxdev/made/pkgx +pkgx pkgx^1 deintegrate +pkgx dev integrate ``` -> no `brew`? [docs.pkgx.sh/installing-w/out-brew] +## `env +foo` -### Integrating with your Shell +If you used this, let us know, we can make a mash script to provide this +functionality again. You can achieve the same result as eg. `env +git` with: ```sh -pkgx integrate --dry-run # docs.pkgx.sh/shell-integration +eval "$(pkgx +git)" ``` -## Further Reading +Surround the `eval` with `set -a` and `set +a` if you need the environment +exported. -[docs.pkgx.sh][docs] is a comprehensive manual and user guide for `pkgx`. +## `pkgx install` -  +We now provide [`pkgm`] but if you miss the leanness of “stubs” we provide a +[`mash`] script to create stubs in `/usr/local/bin`: +```sh +$ pkgx mash pkgx/stub git +created stub: /usr/local/bin/git + +$ cat /usr/local/bin/git +#!/bin/sh +exec pkgx git "$@" +``` + +  # Contributing +We recommend using [`dev`] to make rust available. + * To add packages see the [pantry README] -* To hack on `pkgx` itself; clone it and then `pkgx deno task` to list - entrypoints for hackers +* To hack on `pkgx` itself; clone it and `cargo build` + * [`hydrate.rs`] is where optimization efforts will bear most fruit -If you have questions or feedback: +## Pre-PR Linting -* [github.com/orgs/pkgxdev/discussions][discussions] -* [x.com/pkgxdev](https://x.com/pkgxdev) (DMs are open) +```sh +cargo fmt --all --check +cargo clippy --all-features +pkgx npx markdownlint --config .github/markdownlint.yml --fix . +``` +# Chat / Support / Questions + +We love a good chinwag. + +* [Discord](https://discord.gg/rNwNUY83XS) +* [github.com/orgs/pkgxdev/discussions][discussions] [docs]: https://docs.pkgx.sh [pantry README]: ../../../pantry#contributing [discussions]: ../../discussions -[docs.pkgx.sh/pkgx-install]: https://docs.pkgx.sh/pkgx-install [docs.pkgx.sh/ci-cd]: https://docs.pkgx.sh/ci-cd [docs.pkgx.sh/scripts]: https://docs.pkgx.sh/scripts [docs.pkgx.sh/editors]: https://docs.pkgx.sh/editors [docs.pkgx.sh/docker]: https://docs.pkgx.sh/docker [docs.pkgx.sh/installing-w/out-brew]: https://docs.pkgx.sh/installing-w/out-brew -[docs.pkgx.sh/shell-integration]: https://docs.pkgx.sh/shell-integration -[dev]: https://docs.pkgx.sh/dev +[dev]: https://github.com/pkgxdev/dev +[pkgm]: https://github.com/pkgxdev/pkgm +[teaBASE-release-script]: https://github.com/teaxyz/teaBASE/blob/main/Scripts/publish-release.sh +[`hydrate.rs`]: src/hydrate.rs +[`mash`]: https://github.com/pkgxdev/mash +[`dev`]: https://github.com/pkgxdev/dev [coverage]: https://coveralls.io/repos/github/pkgxdev/pkgx/badge.svg?branch=main [coveralls]: https://coveralls.io/github/pkgxdev/pkgx?branch=main diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 00000000..ff3b6db7 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pkgx" +description = "Run anything" +authors = ["Max Howell ", "Jacob Heider "] +license = "Apache-2.0" +version = "2.0.0" +edition = "2021" +repository = "https://github.com/pkgxdev/pkgx" + +[dependencies] +tokio = { version = "1.43", features = ["full", "rt-multi-thread"] } +rusqlite = "0.32.1" +regex = "1.11.1" +indicatif = "0.17.9" +nix = { version = "0.29.0", features = ["process"] } +serde_json = "1.0.135" +libpkgx = { path = "../lib" } +console = { version = "0.15", default-features = false, features = ["ansi-parsing"] } + +[target.'cfg(not(target_os = "macos"))'.dependencies] +rusqlite = { version = "0.32.1", features = ["bundled"] } +native-tls = { version = "0.2", features = ["vendored"] } +# ^^ this is a transitive dependency +# ^^ we vendor OpenSSL ∵ we want to be standalone and just work inside minimal docker images + +[profile.release] +lto = "fat" diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs new file mode 100644 index 00000000..e3eb0b29 --- /dev/null +++ b/crates/cli/src/args.rs @@ -0,0 +1,98 @@ +use console::style; + +pub enum Mode { + X, + Help, + Version, +} + +pub struct Flags { + pub silent: bool, + pub json: bool, +} + +pub struct Args { + pub plus: Vec, + pub args: Vec, + pub find_program: bool, + pub mode: Mode, + pub flags: Flags, +} + +pub fn parse() -> Args { + let mut mode = Mode::X; + let mut plus = Vec::new(); + let mut args = Vec::new(); + let mut silent: bool = false; + let mut json: bool = false; + let mut find_program = false; + let mut collecting_args = false; + + for arg in std::env::args().skip(1) { + if collecting_args { + args.push(arg); + } else if arg.starts_with('+') { + plus.push(arg.trim_start_matches('+').to_string()); + } else if arg == "--" { + find_program = false; + collecting_args = true; + } else if arg.starts_with("--") { + match arg.as_str() { + "--json" => { + if !silent { + eprintln!( + "{} use --json=v1", + style("warning: --json is not stable").yellow() + ); + } + json = true + } + "--json=v1" => json = true, + "--silent" => silent = true, + "--help" => mode = Mode::Help, + "--version" => mode = Mode::Version, + "--shellcode" => { + if !silent { + eprintln!("{}", style("⨯ migration required").red()); + eprintln!( + "{} pkgx^2 is now exclusively focused on executing packages", + style("│").red() + ); + eprintln!( + "{} you need to migrate to the new, isolated `dev` command", + style("│").red() + ); + eprintln!("{} run the following:", style("│").red()); + eprintln!( + "{} pkgx pkgx^1 deintegrate && pkgx dev integrate", + style("╰─➤").red() + ); + } + std::process::exit(1); + } + _ => panic!("unknown argument {}", arg), + } + } else if arg.starts_with('-') { + // spit arg into characters + for c in arg.chars().skip(1) { + match c { + 's' => silent = true, + 'j' => json = true, + _ => panic!("unknown argument: -{}", c), + } + } + } else { + find_program = !arg.contains('/'); + collecting_args = true; + args.push(arg); + } + } + + Args { + plus, + args, + find_program, + mode, + flags: Flags { silent, json }, + } +} diff --git a/crates/cli/src/execve.rs b/crates/cli/src/execve.rs new file mode 100644 index 00000000..14644c62 --- /dev/null +++ b/crates/cli/src/execve.rs @@ -0,0 +1,49 @@ +use nix::unistd::execve as nix_execve; +use std::ffi::CString; +use std::{collections::HashMap, error::Error}; + +pub fn execve( + cmd: String, + mut args: Vec, + env: HashMap, +) -> Result<(), Box> { + // Convert the command to a CString + let c_command = CString::new(cmd.clone()) + .map_err(|e| format!("Failed to convert command to CString: {}", e))?; + + // execve expects the command to be the first argument (yes, as well) + args.insert(0, cmd); + + // Convert the arguments to CStrings and collect them into a Vec + let c_args: Vec = args + .iter() + .map(|arg| { + CString::new(arg.clone()) + .map_err(|e| format!("Failed to convert argument to CString: {}", e)) + }) + .collect::>()?; + + // Convert the environment to a Vec of `KEY=VALUE` strings + let env_vars: Vec = env + .iter() + .map(|(key, value)| format!("{}={}", key, value)) + .collect(); + + // Convert the environment variables to CStrings and collect them into a Vec + let c_env: Vec = env_vars + .iter() + .map(|env| { + CString::new(env.clone()) + .map_err(|e| format!("Failed to convert environment variable to CString: {}", e)) + }) + .collect::>()?; + + // Replace the process with the new command, arguments, and environment + let execve_result = nix_execve(&c_command, &c_args, &c_env); + if execve_result.is_err() { + let errno = execve_result.unwrap_err(); + return Err(format!("execve failed with errno: {}", errno).into()); + } + + Ok(()) +} diff --git a/crates/cli/src/help.rs b/crates/cli/src/help.rs new file mode 100644 index 00000000..d2995d06 --- /dev/null +++ b/crates/cli/src/help.rs @@ -0,0 +1,40 @@ +use regex::Regex; + +fn dim(input: &str) -> String { + // Placeholder function for "dim" styling + format!("\x1b[2m{}\x1b[0m", input) +} + +pub fn usage() -> String { + let usage = r##" +usage: + pkgx [+pkg@x.y…] [--] [arg…] + +examples: + $ pkgx gum format "# hello world" "sup?" + $ pkgx node@18 --eval 'console.log("hello world")' + $ pkgx +openssl cargo build + +flags: + -s, --silent # no chat. no errors. just execute. + --version + +more: + $ open https://docs.pkgx.sh +"##; + + let usage = usage + .replace('[', &dim("[")) + .replace(']', &dim("]")) + .replace('<', &dim("<")) + .replace('>', &dim(">")) + .replace('$', &dim("$")) + .replace('|', &dim("|")); + + let re = Regex::new("(?m) #.*$").unwrap(); + + re.replace_all(&usage, |caps: ®ex::Captures| { + dim(caps.get(0).unwrap().as_str()) + }) + .to_string() +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs new file mode 100644 index 00000000..bb5f1b12 --- /dev/null +++ b/crates/cli/src/main.rs @@ -0,0 +1,344 @@ +mod args; +mod execve; +mod help; +#[cfg(test)] +mod tests; + +use std::{collections::HashMap, error::Error, fmt::Write, sync::Arc, time::Duration}; + +use execve::execve; +use indicatif::{ProgressBar, ProgressState, ProgressStyle}; +use libpkgx::{ + config::Config, env, hydrate::hydrate, install_multi, pantry_db, resolve::resolve, sync, + types::PackageReq, utils, +}; +use rusqlite::Connection; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args::Args { + plus, + mut args, + mode, + flags, + find_program, + } = args::parse(); + + match mode { + args::Mode::Help => { + println!("{}", help::usage()); + return Ok(()); + } + args::Mode::Version => { + println!("pkgx {}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + args::Mode::X => (), + } + + let config = Config::new()?; + + let cache_dir = config.pantry_dir.parent().unwrap(); + std::fs::create_dir_all(cache_dir)?; + let mut conn = Connection::open(cache_dir.join("pantry.db"))?; + + let spinner = if flags.silent { + None + } else { + let spinner = indicatif::ProgressBar::new_spinner(); + spinner.enable_steady_tick(Duration::from_millis(100)); + Some(spinner) + }; + + let did_sync = if sync::should(&config) { + if let Some(spinner) = &spinner { + spinner.set_message("syncing pkg-db…"); + } + sync::replace(&config, &mut conn).await?; + true + } else { + false + }; + + if let Some(spinner) = &spinner { + spinner.set_message("resolving pkg graph…"); + } + + let mut pkgs = vec![]; + + if find_program { + let PackageReq { + constraint, + project: cmd, + } = PackageReq::parse(&args[0])?; + + args[0] = cmd.clone(); // invoke eg. `node` rather than eg. `node@20` + + let project = match which(&cmd, &conn).await { + Err(WhichError::CmdNotFound(cmd)) => { + if !did_sync { + if let Some(spinner) = &spinner { + let msg = format!("{} not found, syncing…", cmd); + spinner.set_message(msg); + } + // cmd not found ∴ sync in case it is new + sync::replace(&config, &mut conn).await?; + if let Some(spinner) = &spinner { + spinner.set_message("resolving pkg graph…"); + } + which(&cmd, &conn).await + } else { + Err(WhichError::CmdNotFound(cmd)) + } + } + Err(err) => Err(err), + Ok(project) => Ok(project), + }?; + + pkgs.push(PackageReq { + project, + constraint, + }); + } + + for pkgspec in plus { + let PackageReq { + project: project_or_cmd, + constraint, + } = PackageReq::parse(&pkgspec)?; + if config + .pantry_dir + .join("projects") + .join(project_or_cmd.clone()) + .is_dir() + { + pkgs.push(PackageReq { + project: project_or_cmd, + constraint, + }); + } else { + let project = which(&project_or_cmd, &conn).await?; + pkgs.push(PackageReq { + project, + constraint, + }); + } + } + + let companions = pantry_db::companions_for_projects( + &pkgs + .iter() + .map(|project| project.project.clone()) + .collect::>(), + &conn, + )?; + + pkgs.extend(companions); + + let graph = hydrate(&pkgs, |project| { + pantry_db::deps_for_project(&project, &conn) + }) + .await?; + + let resolution = resolve(graph, &config).await?; + + let spinner_clone = spinner.clone(); + let clear_progress_bar = move || { + if let Some(spinner) = spinner_clone { + spinner.finish_and_clear(); + } + }; + + let mut installations = resolution.installed; + if !resolution.pending.is_empty() { + let pb = spinner.map(|spinner| { + configure_bar(&spinner); + Arc::new(MultiProgressBar { pb: spinner }) + }); + let installed = install_multi::install_multi(&resolution.pending, &config, pb).await?; + installations.extend(installed); + } + + let env = env::map(&installations); + + if !args.is_empty() { + let pkgx_lvl = std::env::var("PKGX_LVL") + .unwrap_or("0".to_string()) + .parse() + .unwrap_or(0) + + 1; + if pkgx_lvl >= 10 { + return Err("PKGX_LVL exceeded: https://github.com/orgs/pkgxdev/discussions/11".into()); + } + + let cmd = if find_program { + utils::find_program(&args.remove(0), &env["PATH"]).await? + } else if args[0].contains('/') { + // user specified a path to program which we should use + args.remove(0) + } else { + // user wants a system tool, eg. pkgx +wget -- git clone + // NOTE we still check the injected PATH since they may have added the tool anyway + // it’s just this route allows the user to get a non-error for delegating through to the system + let mut paths = vec![]; + if let Some(pkgpaths) = env.get("PATH") { + paths.append(&mut pkgpaths.clone()); + } + if let Ok(syspaths) = std::env::var("PATH") { + paths.extend( + syspaths + .split(':') + .map(|x| x.to_string()) + .collect::>(), + ); + } + utils::find_program(&args.remove(0), &paths).await? + }; + let env = env::mix(env); + let mut env = env::mix_runtime(&env, &installations, &conn)?; + + // fork bomb protection + env.insert("PKGX_LVL".to_string(), pkgx_lvl.to_string()); + + clear_progress_bar(); + + execve(cmd, args, env) + } else if !env.is_empty() { + clear_progress_bar(); + + if !flags.json { + let env = env.iter().map(|(k, v)| (k.clone(), v.join(":"))).collect(); + let env = env::mix_runtime(&env, &installations, &conn)?; + for (key, value) in env { + println!("{}=\"{}${{{}:+:${}}}\"", key, value, key, key); + } + } else { + let mut runtime_env = HashMap::new(); + for pkg in installations.clone() { + let pkg_runtime_env = pantry_db::runtime_env_for_project(&pkg.pkg.project, &conn)?; + if !pkg_runtime_env.is_empty() { + runtime_env.insert(pkg.pkg.project, pkg_runtime_env); + } + } + let json = json!({ + "pkgs": installations, + "env": env, + "runtime_env": runtime_env + }); + println!("{}", json); + } + Ok(()) + } else { + clear_progress_bar(); + eprintln!("{}", help::usage()); + std::process::exit(2); + } +} + +#[derive(Debug)] +pub enum WhichError { + CmdNotFound(String), + MultipleProjects(String, Vec), + DbError(rusqlite::Error), +} + +impl std::fmt::Display for WhichError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WhichError::CmdNotFound(cmd) => write!(f, "cmd not found: {}", cmd), + WhichError::MultipleProjects(cmd, projects) => { + write!(f, "multiple projects found for {}: {:?}", cmd, projects) + } + WhichError::DbError(err) => write!(f, "db error: {}", err), + } + } +} + +impl std::error::Error for WhichError {} + +async fn which(cmd: &String, conn: &Connection) -> Result { + let candidates = pantry_db::which(cmd, conn).map_err(WhichError::DbError)?; + if candidates.len() == 1 { + Ok(candidates[0].clone()) + } else if candidates.is_empty() { + return Err(WhichError::CmdNotFound(cmd.clone())); + } else { + return Err(WhichError::MultipleProjects(cmd.clone(), candidates)); + } +} + +struct MultiProgressBar { + pb: ProgressBar, +} + +impl libpkgx::install_multi::ProgressBarExt for MultiProgressBar { + fn inc(&self, n: u64) { + self.pb.inc(n); + } + + fn inc_length(&self, n: u64) { + self.pb.inc_length(n); + } +} + +// ProgressBar is Send + Sync +unsafe impl Send for MultiProgressBar {} +unsafe impl Sync for MultiProgressBar {} + +fn configure_bar(pb: &ProgressBar) { + pb.set_length(1); + pb.set_style( + ProgressStyle::with_template( + "{elapsed:.dim} ❲{wide_bar:.red}❳ {percent}% {bytes_per_sec:.dim} {bytes:.dim}", + ) + .unwrap() + .with_key("elapsed", |state: &ProgressState, w: &mut dyn Write| { + let s = state.elapsed().as_secs_f64(); + let precision = precision(s); + write!(w, "{:.precision$}s", s, precision = precision).unwrap() + }) + .with_key("bytes", |state: &ProgressState, w: &mut dyn Write| { + let (right, divisor) = pretty_size(state.len().unwrap()); + let left = state.pos() as f64 / divisor as f64; + let leftprecision = precision(left); + write!( + w, + "{:.precision$}/{}", + left, + right, + precision = leftprecision + ) + .unwrap() + }) + .progress_chars("⚯ "), + ); + pb.enable_steady_tick(Duration::from_millis(50)); +} + +fn pretty_size(n: u64) -> (String, u64) { + let units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; + + // number of 1024s + let thousands = n.max(1).ilog(1024).clamp(0, units.len() as u32 - 1) as usize; + // size in the appropriate unit + let size = n as f64 / 1024.0f64.powi(thousands as i32); + // the divisor to get back to bytes + let divisor = 1024u64.pow(thousands as u32); + // number of decimal places to show (0 if we're bytes. no fractional bytes. come on.) + let precision = if thousands == 0 { 0 } else { precision(size) }; + + let formatted = format!( + "{:.precision$} {}", + size, + units[thousands], + precision = precision + ); + + (formatted, divisor) +} + +fn precision(n: f64) -> usize { + // 1 > 1.00, 10 > 10.0, 100 > 100 + 2 - (n.log10().clamp(0.0, 2.0) as usize) +} diff --git a/crates/cli/src/tests/main.rs b/crates/cli/src/tests/main.rs new file mode 100644 index 00000000..c7d248a8 --- /dev/null +++ b/crates/cli/src/tests/main.rs @@ -0,0 +1,74 @@ +use crate::{precision, pretty_size}; + +#[test] +fn test_pretty_size() { + assert_eq!(pretty_size(0), ("0 B".to_string(), 1)); + assert_eq!(pretty_size(1), ("1 B".to_string(), 1)); + assert_eq!(pretty_size(1024), ("1.00 KiB".to_string(), 1024)); + assert_eq!( + pretty_size(1024 * 1024), + ("1.00 MiB".to_string(), 1024 * 1024) + ); + assert_eq!( + pretty_size(1024 * 1024 * 1024), + ("1.00 GiB".to_string(), 1024 * 1024 * 1024) + ); + assert_eq!( + pretty_size(1024 * 1024 * 1024 * 1024), + ("1.00 TiB".to_string(), 1024 * 1024 * 1024 * 1024) + ); + assert_eq!( + pretty_size(1024 * 1024 * 1024 * 1024 * 1024), + ("1.00 PiB".to_string(), 1024 * 1024 * 1024 * 1024 * 1024) + ); + assert_eq!( + pretty_size(1024 * 1024 * 1024 * 1024 * 1024 * 1024), + ( + "1.00 EiB".to_string(), + 1024 * 1024 * 1024 * 1024 * 1024 * 1024 + ) + ); + // these are bigger than u64 + // assert_eq!( + // pretty_size(1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + // ( + // "1 ZiB".to_string(), + // 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 + // ) + // ); + // assert_eq!( + // pretty_size(1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + // ( + // "1 YiB".to_string(), + // 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 + // ) + // ); + assert_eq!(pretty_size(5000), ("4.88 KiB".to_string(), 1024)); + assert_eq!(pretty_size(5120), ("5.00 KiB".to_string(), 1024)); + + assert_eq!( + pretty_size(1024 * 1024 + 1), + ("1.00 MiB".to_string(), 1024 * 1024) + ); + assert_eq!( + pretty_size(35_245 * 1024), + ("34.4 MiB".to_string(), 1024 * 1024) + ); + assert_eq!( + pretty_size(356_245 * 1024 + 1), + ("348 MiB".to_string(), 1024 * 1024) + ); +} + +#[test] +fn test_precision() { + assert_eq!(precision(1.0), 2); + assert_eq!(precision(1.1), 2); + assert_eq!(precision(9.99), 2); + assert_eq!(precision(10.0), 1); + assert_eq!(precision(10.1), 1); + assert_eq!(precision(99.9), 1); + assert_eq!(precision(100.0), 0); + assert_eq!(precision(100.1), 0); + assert_eq!(precision(999.9), 0); +} diff --git a/crates/cli/src/tests/mod.rs b/crates/cli/src/tests/mod.rs new file mode 100644 index 00000000..d18669ad --- /dev/null +++ b/crates/cli/src/tests/mod.rs @@ -0,0 +1 @@ +mod main; diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml new file mode 100644 index 00000000..a73ab6d5 --- /dev/null +++ b/crates/lib/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "libpkgx" +description = "Install and run `pkgx` packages" +authors = ["Max Howell ", "Jacob Heider "] +license = "Apache-2.0" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/pkgxdev/pkgx" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +anyhow = "1.0.95" +dirs-next = "2.0" +libsemverator = { version = "0.9.0", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" +tokio = { version = "1.43", features = ["full", "rt-multi-thread"] } +tokio-stream = "0.1" +strum = "0.24" +strum_macros = "0.24" +rusqlite = "0.32.1" +regex = "1.11.1" +reqwest = { version = "0.11", features = ["stream", "blocking"] } +async-compression = { version = "0.4", features = ["tokio", "gzip", "xz"] } +tokio-tar = "0.3.1" +tokio-util = { version = "0.7.13", features = ["compat"] } +futures = "0.3.31" +lazy_static = "1.5.0" +nix = { version = "0.29.0", features = ["process"] } +fs2 = "0.4.3" + +[target.'cfg(not(target_os = "macos"))'.dependencies] +rusqlite = { version = "0.32.1", features = ["bundled"] } diff --git a/crates/lib/build.rs b/crates/lib/build.rs new file mode 100644 index 00000000..3304bc87 --- /dev/null +++ b/crates/lib/build.rs @@ -0,0 +1,8 @@ +fn main() { + let dist_url = option_env!("PKGX_DIST_URL").unwrap_or("https://dist.pkgx.dev"); + let default_pantry_url = format!("{dist_url}/pantry.tgz"); + let pantry_url = option_env!("PKGX_PANTRY_TARBALL_URL").unwrap_or(&default_pantry_url); + + println!("cargo:rustc-env=PKGX_DIST_URL={dist_url}"); + println!("cargo:rustc-env=PKGX_PANTRY_TARBALL_URL={pantry_url}"); +} diff --git a/crates/lib/src/cellar.rs b/crates/lib/src/cellar.rs new file mode 100644 index 00000000..658df3d6 --- /dev/null +++ b/crates/lib/src/cellar.rs @@ -0,0 +1,66 @@ +use crate::config::Config; +use crate::types::{Installation, Package, PackageReq}; +use libsemverator::semver::Semver as Version; +use std::error::Error; +use std::path::PathBuf; +use tokio::fs; + +pub async fn ls(project: &str, config: &Config) -> Result, Box> { + let d = config.pkgx_dir.join(project); + + if !fs::metadata(&d).await?.is_dir() { + return Ok(vec![]); + } + + let mut rv = vec![]; + let mut entries = fs::read_dir(&d).await?; + while let Some(entry) = entries.next_entry().await? { + let path = entry.path(); + let name = entry.file_name().to_string_lossy().to_string(); + + if !name.starts_with('v') || name == "var" { + continue; + } + if !fs::symlink_metadata(&path).await?.is_dir() { + continue; + } + + if let Ok(version) = Version::parse(&name[1..]) { + rv.push(Installation { + path, + pkg: Package { + project: project.to_string(), + version, + }, + }); + } + } + + Ok(rv) +} + +pub async fn resolve(pkgreq: &PackageReq, config: &Config) -> Result> { + let installations = ls(&pkgreq.project, config).await?; + + if let Some(i) = installations + .iter() + .filter(|i| pkgreq.constraint.satisfies(&i.pkg.version)) + .max_by_key(|i| i.pkg.version.clone()) + { + Ok(i.clone()) + } else { + // If no matching version is found, return an error + Err(format!("couldn’t resolve {:?}", pkgreq).into()) + } +} + +pub async fn has(pkg: &PackageReq, config: &Config) -> Option { + resolve(pkg, config).await.ok() +} + +pub fn dst(pkg: &Package, config: &Config) -> PathBuf { + config + .pkgx_dir + .join(pkg.project.clone()) + .join(format!("v{}", pkg.version.raw)) +} diff --git a/crates/lib/src/config.rs b/crates/lib/src/config.rs new file mode 100644 index 00000000..58381318 --- /dev/null +++ b/crates/lib/src/config.rs @@ -0,0 +1,59 @@ +use std::env; +use std::io; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct Config { + pub pantry_dir: PathBuf, + pub dist_url: String, + pub pkgx_dir: PathBuf, +} + +impl Config { + pub fn new() -> io::Result { + let pantry_dir = get_pantry_dir()?; + let dist_url = get_dist_url(); + let pkgx_dir = get_pkgx_dir()?; + Ok(Self { + pantry_dir, + dist_url, + pkgx_dir, + }) + } +} + +fn get_dist_url() -> String { + if let Ok(env_url) = env::var("PKGX_DIST_URL") { + return env_url; + } + env!("PKGX_DIST_URL").to_string() +} + +fn get_pantry_dir() -> io::Result { + if let Ok(env_dir) = env::var("PKGX_PANTRY_DIR") { + let path = PathBuf::from(env_dir); + if !path.is_absolute() { + return Ok(env::current_dir()?.join(path)); + } else { + return Ok(path); + } + } + Ok(dirs_next::cache_dir().unwrap().join("pkgx/pantry")) +} + +fn get_pkgx_dir() -> io::Result { + if let Ok(env_dir) = env::var("PKGX_DIR") { + let path = PathBuf::from(env_dir); + if !path.is_absolute() { + return Ok(env::current_dir()?.join(path)); + } else { + return Ok(path); + } + } + #[cfg(target_os = "macos")] + return Ok(dirs_next::home_dir().unwrap().join(".pkgx")); + #[cfg(target_os = "linux")] + return Ok(dirs_next::home_dir().unwrap().join(".pkgx")); + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + panic!("Unsupported platform") +} diff --git a/crates/lib/src/env.rs b/crates/lib/src/env.rs new file mode 100644 index 00000000..dfef976f --- /dev/null +++ b/crates/lib/src/env.rs @@ -0,0 +1,210 @@ +use std::{ + collections::{HashMap, HashSet}, + error::Error, + path::PathBuf, + str::FromStr, +}; + +use crate::types::Installation; + +pub fn map(installations: &Vec) -> HashMap> { + let mut vars: HashMap> = HashMap::new(); + + let projects: HashSet<&str> = installations + .iter() + .map(|i| i.pkg.project.as_str()) + .collect(); + + for installation in installations { + for key in EnvKey::iter() { + if let Some(suffixes) = suffixes(&key) { + for suffix in suffixes { + let path = installation.path.join(suffix); + if path.is_dir() { + vars.entry(key.clone()) + .or_insert_with(OrderedSet::new) + .add(path); + } + } + } + } + + if projects.contains("cmake.org") { + vars.entry(EnvKey::CmakePrefixPath) + .or_insert_with(OrderedSet::new) + .add(installation.path.clone()); + } + } + + // don’t break `man` + if vars.contains_key(&EnvKey::Manpath) { + vars.get_mut(&EnvKey::Manpath) + .unwrap() + .add(PathBuf::from_str("/usr/share/man").unwrap()); + } + // https://github.com/pkgxdev/libpkgx/issues/70 + if vars.contains_key(&EnvKey::XdgDataDirs) { + let set = vars.get_mut(&EnvKey::XdgDataDirs).unwrap(); + set.add(PathBuf::from_str("/usr/local/share").unwrap()); + set.add(PathBuf::from_str("/usr/share").unwrap()); + } + + let mut rv: HashMap> = HashMap::new(); + for (key, set) in vars { + let set = set + .items + .iter() + .map(|p| p.to_string_lossy().to_string()) + .collect(); + rv.insert(key.as_ref().to_string(), set); + } + rv +} + +use rusqlite::Connection; +use strum::IntoEnumIterator; +use strum_macros::{AsRefStr, EnumIter, EnumString}; + +#[derive(Debug, EnumString, AsRefStr, PartialEq, Eq, Hash, Clone, EnumIter)] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +enum EnvKey { + Path, + Manpath, + PkgConfigPath, + LibraryPath, + LdLibraryPath, + Cpath, + XdgDataDirs, + CmakePrefixPath, + #[cfg(target_os = "macos")] + DyldFallbackLibraryPath, + SslCertFile, + Ldflags, + PkgxDir, + AclocalPath, +} + +struct OrderedSet { + items: Vec, + set: HashSet, +} + +impl OrderedSet { + fn new() -> Self { + OrderedSet { + items: Vec::new(), + set: HashSet::new(), + } + } + + fn add(&mut self, item: T) { + if self.set.insert(item.clone()) { + self.items.push(item); + } + } +} + +fn suffixes(key: &EnvKey) -> Option> { + match key { + EnvKey::Path => Some(vec!["bin", "sbin"]), + EnvKey::Manpath => Some(vec!["man", "share/man"]), + EnvKey::PkgConfigPath => Some(vec!["share/pkgconfig", "lib/pkgconfig"]), + EnvKey::XdgDataDirs => Some(vec!["share"]), + EnvKey::AclocalPath => Some(vec!["share/aclocal"]), + EnvKey::LibraryPath | EnvKey::LdLibraryPath => Some(vec!["lib", "lib64"]), + #[cfg(target_os = "macos")] + EnvKey::DyldFallbackLibraryPath => Some(vec!["lib", "lib64"]), + EnvKey::Cpath => Some(vec!["include"]), + EnvKey::CmakePrefixPath | EnvKey::SslCertFile | EnvKey::Ldflags | EnvKey::PkgxDir => None, + } +} + +pub fn mix(input: HashMap>) -> HashMap { + let mut rv = HashMap::new(); + + for (key, mut value) in std::env::vars() { + if let Some(injected_values) = input.get(&key) { + value = format!("{}:{}", injected_values.join(":"), value); + } + rv.insert(key, value); + } + + rv +} + +pub fn mix_runtime( + input: &HashMap, + installations: &Vec, + conn: &Connection, +) -> Result, Box> { + let mut output = input.clone(); + + for installation in installations.clone() { + let runtime_env = + crate::pantry_db::runtime_env_for_project(&installation.pkg.project, conn)?; + for (key, runtime_value) in runtime_env { + let runtime_value = expand_moustaches(&runtime_value, &installation, installations); + let new_value = match output.get(&key) { + Some(curr_value) => runtime_value.replace(&format!("${}", key), curr_value), + None => { + //TODO need to remove any $FOO, aware of `:` delimiters + runtime_value + .replace(&format!(":${}", key), "") + .replace(&format!("${}:", key), "") + .replace(&format!("${}", key), "") + } + }; + output.insert(key, new_value); + } + } + + Ok(output) +} + +pub fn expand_moustaches(input: &str, pkg: &Installation, deps: &Vec) -> String { + let mut output = input.to_string(); + + if output.starts_with("${{") { + output.replace_range(..1, ""); + } + + output = output.replace("{{prefix}}", &pkg.path.to_string_lossy()); + output = output.replace("{{version}}", &format!("{}", &pkg.pkg.version)); + output = output.replace("{{version.major}}", &format!("{}", pkg.pkg.version.major)); + output = output.replace("{{version.minor}}", &format!("{}", pkg.pkg.version.minor)); + output = output.replace("{{version.patch}}", &format!("{}", pkg.pkg.version.patch)); + output = output.replace( + "{{version.marketing}}", + &format!("{}.{}", pkg.pkg.version.major, pkg.pkg.version.minor), + ); + + for dep in deps { + let prefix = format!("deps.{}", dep.pkg.project); + output = output.replace( + &format!("{{{{{}.prefix}}}}", prefix), + &dep.path.to_string_lossy(), + ); + output = output.replace( + &format!("{{{{{}.version}}}}", prefix), + &format!("{}", &dep.pkg.version), + ); + output = output.replace( + &format!("{{{{{}.version.major}}}}", prefix), + &format!("{}", dep.pkg.version.major), + ); + output = output.replace( + &format!("{{{{{}.version.minor}}}}", prefix), + &format!("{}", dep.pkg.version.minor), + ); + output = output.replace( + &format!("{{{{{}.version.patch}}}}", prefix), + &format!("{}", dep.pkg.version.patch), + ); + output = output.replace( + &format!("{{{{{}.version.marketing}}}}", prefix), + &format!("{}.{}", dep.pkg.version.major, dep.pkg.version.minor), + ); + } + + output +} diff --git a/crates/lib/src/hydrate.rs b/crates/lib/src/hydrate.rs new file mode 100644 index 00000000..9a69c7f4 --- /dev/null +++ b/crates/lib/src/hydrate.rs @@ -0,0 +1,109 @@ +use crate::types::PackageReq; +use libsemverator::range::Range as VersionReq; +use std::collections::{HashMap, HashSet}; +use std::error::Error; + +#[derive(Clone)] +struct Node { + parent: Option>, + pkg: PackageReq, + children: HashSet, +} + +impl Node { + fn new(pkg: PackageReq, parent: Option>) -> Self { + Self { + parent, + pkg, + children: HashSet::new(), + } + } + + fn count(&self) -> usize { + let mut count = 0; + let mut node = self.parent.as_ref(); + while let Some(parent_node) = node { + count += 1; + node = parent_node.parent.as_ref(); + } + count + } +} + +/// Hydrates dependencies and returns a topologically sorted list of packages. +pub async fn hydrate( + input: &Vec, + get_deps: F, +) -> Result, Box> +where + F: Fn(String) -> Result, Box>, +{ + let dry = condense(input); + let mut graph: HashMap> = HashMap::new(); + let mut stack: Vec> = vec![]; + let mut additional_unicodes: Vec = vec![]; + + for pkg in dry.iter() { + let node = graph + .entry(pkg.project.clone()) + .or_insert_with(|| Box::new(Node::new(pkg.clone(), None))); + node.pkg.constraint = intersect_constraints(&node.pkg.constraint, &pkg.constraint)?; + stack.push(node.clone()); + } + + while let Some(mut current) = stack.pop() { + for child_pkg in get_deps(current.pkg.project.clone())? { + let child_node = graph + .entry(child_pkg.project.clone()) + .or_insert_with(|| Box::new(Node::new(child_pkg.clone(), Some(current.clone())))); + let intersection = + intersect_constraints(&child_node.pkg.constraint, &child_pkg.constraint); + if let Ok(constraint) = intersection { + child_node.pkg.constraint = constraint; + current.children.insert(child_node.pkg.project.clone()); + stack.push(child_node.clone()); + } else if child_pkg.project == "unicode.org" { + // we handle unicode.org for now to allow situations like: + // https://github.com/pkgxdev/pantry/issues/4104 + // https://github.com/pkgxdev/pkgx/issues/899 + additional_unicodes.push(child_pkg.constraint); + } else { + return Err(intersection.unwrap_err()); + } + } + } + + let mut pkgs: Vec<&Box> = graph.values().collect(); + pkgs.sort_by_key(|node| node.count()); + let mut pkgs: Vec = pkgs.into_iter().map(|node| node.pkg.clone()).collect(); + + // see above explanation + for constraint in additional_unicodes { + let pkg = PackageReq { + project: "unicode.org".to_string(), + constraint, + }; + pkgs.push(pkg); + } + + Ok(pkgs) +} + +/// Condenses a list of `PackageRequirement` by intersecting constraints for duplicates. +fn condense(pkgs: &Vec) -> Vec { + let mut out: Vec = vec![]; + for pkg in pkgs { + if let Some(existing) = out.iter_mut().find(|p| p.project == pkg.project) { + existing.constraint = intersect_constraints(&existing.constraint, &pkg.constraint) + .expect("Failed to intersect constraints"); + } else { + out.push(pkg.clone()); + } + } + out +} + +/// Intersects two version constraints. +fn intersect_constraints(a: &VersionReq, b: &VersionReq) -> Result> { + a.intersect(b).map_err(|e| e.into()) +} diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs new file mode 100644 index 00000000..3403ce0c --- /dev/null +++ b/crates/lib/src/install.rs @@ -0,0 +1,191 @@ +use async_compression::tokio::bufread::XzDecoder; +use fs2::FileExt; +use reqwest::Client; +use std::{error::Error, fs::OpenOptions, path::PathBuf}; +use tokio::task; +use tokio_tar::Archive; + +// Compatibility trait lets us call `compat()` on a futures::io::AsyncRead +// to convert it into a tokio::io::AsyncRead. +use tokio_util::compat::FuturesAsyncReadCompatExt; + +// Lets us call into_async_read() to convert a futures::stream::Stream into a +// futures::io::AsyncRead. +use futures::stream::TryStreamExt; + +use crate::{ + cellar, + config::Config, + inventory, + types::{Installation, Package}, +}; + +pub enum InstallEvent { + DownloadSize(u64), // Total size of the download in bytes + Progress(u64), // we downloaded n bytes +} + +//TODO set UserAgent + +pub async fn install( + pkg: &Package, + config: &Config, + mut event_callback: Option, +) -> Result> +where + F: FnMut(InstallEvent) + Send + 'static, +{ + let shelf = config.pkgx_dir.join(&pkg.project); + fs::create_dir_all(&shelf)?; + let shelf = OpenOptions::new() + .read(true) // Open the directory in read-only mode + .open(shelf)?; + + task::spawn_blocking({ + let shelf = shelf.try_clone()?; + move || { + shelf + .lock_exclusive() + .expect("couldn’t obtain lock, is another pkgx instance running?"); + } + }) + .await?; + + let url = inventory::get_url(pkg, config); + let client = Client::new(); + let rsp = client.get(url).send().await?.error_for_status()?; + + let total_size = rsp + .content_length() + .ok_or("Failed to get content length from response")?; + + if let Some(cb) = event_callback.as_mut() { + cb(InstallEvent::DownloadSize(total_size)); + } + + let stream = rsp.bytes_stream(); + + //TODO we don’t want to add inspect_ok to the stream at all in --silent mode + // ^^ but the borrow checker despises us with a venom I can barely articulate if we try + let stream = stream.inspect_ok(move |chunk| { + if let Some(cb) = event_callback.as_mut() { + cb(InstallEvent::Progress(chunk.len() as u64)); + } + }); + + let stream = stream + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) + .into_async_read(); + let stream = stream.compat(); + + // Step 2: Create a XZ decoder + let decoder = XzDecoder::new(stream); + + // Step 3: Extract the tar archive + let mut archive = Archive::new(decoder); + archive.unpack(&config.pkgx_dir).await?; + + let installation = Installation { + path: cellar::dst(pkg, config), + pkg: pkg.clone(), + }; + + symlink(&installation, config).await?; + + FileExt::unlock(&shelf)?; + + Ok(installation) +} + +use libsemverator::range::Range as VersionReq; +use libsemverator::semver::Semver as Version; +use std::collections::VecDeque; +use std::fs; +use std::path::Path; + +async fn symlink(installation: &Installation, config: &Config) -> Result<(), Box> { + let mut versions: VecDeque<(Version, PathBuf)> = cellar::ls(&installation.pkg.project, config) + .await? + .into_iter() + .map(|entry| (entry.pkg.version, entry.path)) + .collect(); + + versions.make_contiguous().sort_by(|a, b| a.0.cmp(&b.0)); + + if versions.is_empty() { + return Err(format!("no versions for package {}", installation.pkg.project).into()); + } + + let shelf = installation.path.parent().unwrap(); + let newest = versions.back().unwrap(); // Safe as we've checked it's not empty + + let v_mm = format!( + "{}.{}", + installation.pkg.version.major, installation.pkg.version.minor + ); + let minor_range = VersionReq::parse(&format!("^{}", v_mm))?; + let most_minor = versions + .iter() + .filter(|(version, _)| minor_range.satisfies(version)) + .last() + .ok_or_else(|| anyhow::anyhow!("Could not find most minor version"))?; + + if most_minor.0 != installation.pkg.version { + return Ok(()); + } + + make_symlink(shelf, &format!("v{}", v_mm), installation).await?; + + // bug in semverator + let major_range = VersionReq::parse(&format!("^{}", installation.pkg.version.major))?; + + let most_major = versions + .iter() + .filter(|(version, _)| major_range.satisfies(version)) + .last() + .ok_or_else(|| anyhow::anyhow!("Could not find most major version"))?; + + if most_major.0 != installation.pkg.version { + return Ok(()); + } + + make_symlink( + shelf, + &format!("v{}", installation.pkg.version.major), + installation, + ) + .await?; + + if installation.pkg.version == newest.0 { + make_symlink(shelf, "v*", installation).await?; + } + + Ok(()) +} + +async fn make_symlink( + shelf: &Path, + symname: &str, + installation: &Installation, +) -> Result<(), Box> { + let symlink_path = shelf.join(symname); + + if symlink_path.is_symlink() { + if let Err(err) = fs::remove_file(&symlink_path) { + if err.kind() != std::io::ErrorKind::NotFound { + return Err(err.into()); + } + } + } + + let target = installation + .path + .file_name() + .ok_or_else(|| anyhow::anyhow!("Could not get the base name of the installation path"))?; + + match std::os::unix::fs::symlink(target, &symlink_path) { + Ok(_) => Ok(()), + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), + Err(err) => Err(err.into()), + } +} diff --git a/crates/lib/src/install_multi.rs b/crates/lib/src/install_multi.rs new file mode 100644 index 00000000..e34cd6aa --- /dev/null +++ b/crates/lib/src/install_multi.rs @@ -0,0 +1,44 @@ +use std::error::Error; +use std::sync::Arc; + +use crate::install::{install, InstallEvent}; +use crate::types::{Installation, Package}; +use futures::stream::FuturesUnordered; +use futures::StreamExt; + +use crate::config::Config; + +pub trait ProgressBarExt { + fn inc(&self, n: u64); + fn inc_length(&self, n: u64); +} + +pub async fn install_multi( + pending: &[Package], + config: &Config, + pb: Option>, +) -> Result, Box> { + pending + .iter() + .map(|pkg| { + install( + pkg, + config, + pb.clone().map(|pb| { + move |event| match event { + InstallEvent::DownloadSize(size) => { + pb.inc_length(size); + } + InstallEvent::Progress(chunk) => { + pb.inc(chunk); + } + } + }), + ) + }) + .collect::>() + .collect::>() + .await + .into_iter() + .collect() +} diff --git a/crates/lib/src/inventory.rs b/crates/lib/src/inventory.rs new file mode 100644 index 00000000..5ec536e3 --- /dev/null +++ b/crates/lib/src/inventory.rs @@ -0,0 +1,86 @@ +use crate::config::Config; +use crate::types::{host, Package, PackageReq}; +use libsemverator::semver::Semver as Version; +use reqwest::Url; +use std::error::Error; + +// Custom error for download issues +#[derive(Debug)] +pub struct DownloadError { + pub status: u16, + pub src: String, +} + +impl std::fmt::Display for DownloadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Download error: status code {} from {}", + self.status, self.src + ) + } +} + +impl Error for DownloadError {} + +// Select function to pick a version +pub async fn select(rq: &PackageReq, config: &Config) -> Result, Box> { + let versions = ls(rq, config).await?; + + Ok(versions + .iter() + .filter(|v| rq.constraint.satisfies(v)) + .max() + .cloned()) +} + +// Get function to fetch available versions +pub async fn ls(rq: &PackageReq, config: &Config) -> Result, Box> { + let base_url = config.dist_url.clone(); + + let (platform, arch) = host(); + let url = Url::parse(&format!( + "{}/{}/{}/{}/versions.txt", + base_url, rq.project, platform, arch + ))?; + + let rsp = reqwest::get(url.clone()).await?; + + if !rsp.status().is_success() { + return Err(Box::new(DownloadError { + status: rsp.status().as_u16(), + src: url.to_string(), + })); + } + + let releases = rsp.text().await?; + let mut versions: Vec = releases + .lines() + .map(Version::parse) + .filter_map(Result::ok) + .collect(); + + if versions.is_empty() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + format!("No versions for {}", rq.project), + ))); + } + + if rq.project == "openssl.org" { + // Workaround: Remove specific version + let excluded_version = Version::parse("1.1.118")?; + versions.retain(|x| x != &excluded_version); + } + + Ok(versions) +} + +//TODO xz bottles are preferred +pub fn get_url(pkg: &Package, config: &Config) -> String { + let (platform, arch) = host(); + format!( + "{}/{}/{}/{}/v{}.tar.xz", + config.dist_url, pkg.project, platform, arch, pkg.version.raw + ) +} diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs new file mode 100644 index 00000000..62e45131 --- /dev/null +++ b/crates/lib/src/lib.rs @@ -0,0 +1,13 @@ +mod cellar; +pub mod config; +pub mod env; +pub mod hydrate; +mod install; +pub mod install_multi; +mod inventory; +mod pantry; +pub mod pantry_db; +pub mod resolve; +pub mod sync; +pub mod types; +pub mod utils; diff --git a/crates/lib/src/pantry.rs b/crates/lib/src/pantry.rs new file mode 100644 index 00000000..55a1bd99 --- /dev/null +++ b/crates/lib/src/pantry.rs @@ -0,0 +1,301 @@ +use crate::{config::Config, types::PackageReq}; +use libsemverator::range::Range as VersionReq; +use serde::Deserialize; +use serde::Deserializer; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +pub struct PantryEntry { + pub project: String, + pub deps: Vec, + pub programs: Vec, + pub companions: Vec, + pub env: HashMap, +} + +impl PantryEntry { + fn from_path(path: &PathBuf, pantry_dir: &PathBuf) -> Result> { + let project = path + .parent() + .unwrap() + .strip_prefix(pantry_dir) + .unwrap() + .to_str() + .unwrap() + .to_string(); + + Self::from_raw_entry(RawPantryEntry::from_path(path)?, project) + } + + fn from_raw_entry( + entry: RawPantryEntry, + project: String, + ) -> Result> { + let deps = if let Some(deps) = entry.dependencies { + deps.0 + .iter() + .map(|(project, constraint)| { + VersionReq::parse(constraint).map(|constraint| PackageReq { + project: project.clone(), + constraint, + }) + }) + .collect::, _>>()? + } else { + vec![] + }; + + let programs = if let Some(provides) = entry.provides { + provides.0 + } else { + vec![] + }; + + let companions = if let Some(companions) = entry.companions { + companions + .0 + .iter() + .map(|(k, v)| { + VersionReq::parse(v).map(|constraint| PackageReq { + project: k.clone(), + constraint, + }) + }) + .collect::, _>>()? + } else { + vec![] + }; + + let env = if let Some(runtime) = entry.runtime { + runtime.env + } else { + HashMap::new() + }; + + Ok(Self { + deps, + project, + env, + companions, + programs, + }) + } +} + +pub struct PackageEntryIterator { + stack: Vec, // stack for directories to visit + pantry_dir: PathBuf, +} + +impl PackageEntryIterator { + pub fn new(pantry_dir: PathBuf) -> Self { + Self { + stack: vec![pantry_dir.clone()], + pantry_dir, + } + } +} + +impl Iterator for PackageEntryIterator { + type Item = PantryEntry; + + fn next(&mut self) -> Option { + while let Some(path) = self.stack.pop() { + if path.is_dir() { + // push subdirectories and files into the stack + if let Ok(entries) = fs::read_dir(&path) { + for entry in entries.flatten() { + self.stack.push(entry.path()); + } + } + } else if path.file_name() == Some("package.yml".as_ref()) { + if let Ok(entry) = PantryEntry::from_path(&path, &self.pantry_dir) { + return Some(entry); + } else if cfg!(debug_assertions) { + eprintln!("parse failure: {:?}", path); + } + } + } + None + } +} + +pub fn ls(config: &Config) -> PackageEntryIterator { + PackageEntryIterator::new(config.pantry_dir.join("projects")) +} + +#[derive(Debug, Deserialize)] +struct RawPantryEntry { + dependencies: Option, + provides: Option, + companions: Option, + runtime: Option, +} + +#[derive(Debug)] +struct Runtime { + env: HashMap, +} + +impl<'de> Deserialize<'de> for Runtime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[cfg(target_os = "macos")] + let platform_key = "darwin"; + #[cfg(target_os = "linux")] + let platform_key = "linux"; + #[cfg(target_os = "windows")] + let platform_key = "windows"; + #[cfg(target_arch = "aarch64")] + let arch_key = "aarch64"; + #[cfg(target_arch = "x86_64")] + let arch_key = "x86-64"; + + fn stringify(value: serde_yaml::Value) -> Option { + match value { + serde_yaml::Value::String(s) => Some(s.clone()), + serde_yaml::Value::Number(n) => Some(n.to_string()), + serde_yaml::Value::Bool(b) => Some(b.to_string()), + _ => None, + } + } + + let mut result = HashMap::new(); + + let root: HashMap = Deserialize::deserialize(deserializer)?; + + if let Some(env) = root.get("env").and_then(|x| x.as_mapping()).cloned() { + for (key, value) in env { + if key == "linux" || key == "darwin" || key == "windows" { + // If the key is platform-specific, only include values for the current platform + if key == platform_key { + if let serde_yaml::Value::Mapping(value) = value { + for (key, value) in value { + if let (Some(key), Some(value)) = (stringify(key), stringify(value)) + { + result.insert(key, value); + } + } + } + } + } else if key == "aarch64" || key == "x86-64" { + if key == arch_key { + if let serde_yaml::Value::Mapping(value) = value { + for (key, value) in value { + if let (Some(key), Some(value)) = (stringify(key), stringify(value)) + { + result.insert(key, value); + } + } + } + } + } else if let (Some(key), Some(value)) = (stringify(key), stringify(value)) { + result.insert(key, value); + } + } + } + Ok(Runtime { env: result }) + } +} + +#[derive(Debug)] +struct Deps(HashMap); + +impl<'de> Deserialize<'de> for Deps { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Deserialize the map as a generic HashMap + let full_map: HashMap = Deserialize::deserialize(deserializer)?; + + // Determine the current platform + #[cfg(target_os = "macos")] + let platform_key = "darwin"; + + #[cfg(target_os = "linux")] + let platform_key = "linux"; + + #[cfg(target_os = "windows")] + let platform_key = "windows"; + + // Create the result map + let mut result = HashMap::new(); + + fn handle_value(input: &serde_yaml::Value) -> Option { + match input { + serde_yaml::Value::String(s) => Some(if s.chars().next().unwrap().is_numeric() { + format!("^{}", s) + } else { + s.clone() + }), + serde_yaml::Value::Number(n) => Some(format!("^{}", n)), + _ => None, + } + } + + for (key, value) in full_map { + if key == "linux" || key == "darwin" || key == "windows" { + // If the key is platform-specific, only include values for the current platform + if key == platform_key { + if let serde_yaml::Value::Mapping(platform_values) = value { + for (k, v) in platform_values { + if let (serde_yaml::Value::String(k), Some(v)) = (k, handle_value(&v)) { + result.insert(k, v); + } + } + } + } + } else if let Some(value) = handle_value(&value) { + result.insert(key, value); + } + } + + Ok(Deps(result)) + } +} + +#[derive(Debug)] +struct Provides(Vec); + +impl<'de> Deserialize<'de> for Provides { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Define an enum to capture the possible YAML structures + #[derive(Deserialize)] + #[serde(untagged)] + enum ProvidesHelper { + List(Vec), + Map(HashMap>), + } + + match ProvidesHelper::deserialize(deserializer)? { + ProvidesHelper::List(list) => Ok(Provides(list)), + ProvidesHelper::Map(map) => { + #[cfg(target_os = "macos")] + let key = "darwin"; + + #[cfg(target_os = "linux")] + let key = "linux"; + + if let Some(values) = map.get(key) { + Ok(Provides(values.clone())) + } else { + Ok(Provides(Vec::new())) // Return an empty Vec if the key isn't found + } + } + } + } +} + +impl RawPantryEntry { + fn from_path(path: &PathBuf) -> Result> { + let content = fs::read_to_string(path)?; + Ok(serde_yaml::from_str(&content)?) + } +} diff --git a/crates/lib/src/pantry_db.rs b/crates/lib/src/pantry_db.rs new file mode 100644 index 00000000..e2e872c2 --- /dev/null +++ b/crates/lib/src/pantry_db.rs @@ -0,0 +1,150 @@ +use std::{collections::HashMap, error::Error}; + +use rusqlite::{params, Connection}; + +use crate::{config::Config, pantry, types::PackageReq}; + +pub fn cache(config: &Config, conn: &mut Connection) -> Result<(), Box> { + conn.execute_batch( + " + PRAGMA synchronous = OFF; + PRAGMA journal_mode = MEMORY; + PRAGMA temp_store = MEMORY; + DROP TABLE IF EXISTS provides; + DROP TABLE IF EXISTS dependencies; + DROP TABLE IF EXISTS companions; + DROP TABLE IF EXISTS runtime_env; + CREATE TABLE provides ( + project TEXT, + program TEXT + ); + CREATE TABLE dependencies ( + project TEXT, + pkgspec TEXT + ); + CREATE TABLE companions ( + project TEXT, + pkgspec TEXT + ); + CREATE TABLE runtime_env ( + project TEXT, + envline TEXT + ); + CREATE INDEX idx_project ON provides(project); + CREATE INDEX idx_program ON provides(program); + CREATE INDEX idx_project_dependencies ON dependencies(project); + CREATE INDEX idx_project_companions ON companions(project); + ", + )?; + + let tx = conn.transaction()?; + + for pkg in pantry::ls(config) { + for mut program in pkg.programs { + program = std::path::Path::new(&program) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + tx.execute( + "INSERT INTO provides (project, program) VALUES (?1, ?2);", + params![pkg.project, program], + )?; + } + + for dep in pkg.deps { + tx.execute( + "INSERT INTO dependencies (project, pkgspec) VALUES (?1, ?2);", + params![pkg.project, dep.to_string()], + )?; + } + + for companion in pkg.companions { + tx.execute( + "INSERT INTO companions (project, pkgspec) VALUES (?1, ?2);", + params![pkg.project, companion.to_string()], + )?; + } + + for (key, value) in pkg.env { + tx.execute( + "INSERT INTO runtime_env (project, envline) VALUES (?1, ?2);", + params![pkg.project, format!("{}={}", key, value)], + )?; + } + } + + tx.commit()?; + + Ok(()) +} + +pub fn deps_for_project( + project: &String, + conn: &Connection, +) -> Result, Box> { + let mut stmt = conn.prepare("SELECT pkgspec FROM dependencies WHERE project = ?1")?; + let rv = stmt.query_map(params![project], |row| { + let pkgspec: String = row.get(0)?; + let pkgrq = PackageReq::parse(&pkgspec).unwrap(); //FIXME unwrap() + Ok(pkgrq) + })?; + Ok(rv.collect::, _>>()?) +} + +pub fn which(cmd: &String, conn: &Connection) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare("SELECT project FROM provides WHERE program = ?1")?; + let mut rv = Vec::new(); + let mut rows = stmt.query(params![cmd])?; + while let Some(row) = rows.next()? { + rv.push(row.get(0)?); + } + Ok(rv) +} + +pub fn runtime_env_for_project( + project: &String, + conn: &Connection, +) -> Result, Box> { + let sql = "SELECT envline FROM runtime_env WHERE project = ?1"; + let mut stmt = conn.prepare(sql)?; + let mut rows = stmt.query(params![project])?; + let mut env = HashMap::new(); + while let Some(row) = rows.next()? { + let envline: String = row.get(0)?; + let (key, value) = envline.split_once('=').unwrap(); + env.insert(key.to_string(), value.to_string()); + } + Ok(env) +} + +pub fn companions_for_projects( + projects: &[String], + conn: &Connection, +) -> Result, Box> { + if projects.is_empty() { + return Ok(Vec::new()); + } + + // Generate placeholders for the IN clause (?, ?, ?, ...) + let placeholders = projects.iter().map(|_| "?").collect::>().join(", "); + let query = format!( + "SELECT pkgspec FROM companions WHERE project IN ({})", + placeholders + ); + + let mut stmt = conn.prepare(&query)?; + + let companions = stmt.query_map( + rusqlite::params_from_iter(projects.iter()), // Efficiently bind the projects + |row| { + let pkgspec: String = row.get(0)?; + let pkgrq = PackageReq::parse(&pkgspec).unwrap(); //TODO handle error! + Ok(pkgrq) + }, + )?; + + // Collect results into a Vec, propagating errors + Ok(companions.collect::, _>>()?) +} diff --git a/crates/lib/src/resolve.rs b/crates/lib/src/resolve.rs new file mode 100644 index 00000000..fc49e684 --- /dev/null +++ b/crates/lib/src/resolve.rs @@ -0,0 +1,76 @@ +use crate::config::Config; +use crate::types::{Installation, Package, PackageReq}; +use crate::{cellar, inventory}; +use std::error::Error; + +#[derive(Debug, Default)] +pub struct Resolution { + /// fully resolved list (includes both installed and pending) + pub pkgs: Vec, + + /// already installed packages + pub installed: Vec, + + /// these are the pkgs that aren’t yet installed + pub pending: Vec, +} + +//TODO no need to take array since it doesn’t consider anything +use futures::stream::{FuturesUnordered, StreamExt}; + +pub async fn resolve(reqs: Vec, config: &Config) -> Result> { + let mut rv = Resolution::default(); + + // Create a FuturesUnordered to run the tasks concurrently + let mut futures = FuturesUnordered::new(); + + for req in reqs { + futures.push(async move { + if let Some(installation) = cellar::has(&req, config).await { + Ok::<_, Box>(( + Some((installation.clone(), installation.pkg.clone())), + None, + )) + } else if let Ok(Some(version)) = inventory::select(&req, config).await { + let pkg = Package { + project: req.project.clone(), + version, + }; + Ok::<_, Box>((None, Some(pkg))) + } else { + Err(Box::new(ResolveError { pkg: req }) as Box) + } + }); + } + + // Process the results as they are completed + while let Some(result) = futures.next().await { + match result? { + (Some((installation, pkg)), None) => { + rv.installed.push(installation); + rv.pkgs.push(pkg); + } + (None, Some(pkg)) => { + rv.pkgs.push(pkg.clone()); + rv.pending.push(pkg); + } + _ => unreachable!(), // This should not happen + } + } + + Ok(rv) +} + +use std::fmt; + +#[derive(Debug)] +pub struct ResolveError { + pub pkg: PackageReq, // Holds the package or requirement +} +impl Error for ResolveError {} + +impl fmt::Display for ResolveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "not-found: pkg: {:?}", self.pkg) + } +} diff --git a/crates/lib/src/sync.rs b/crates/lib/src/sync.rs new file mode 100644 index 00000000..c52dcf79 --- /dev/null +++ b/crates/lib/src/sync.rs @@ -0,0 +1,63 @@ +use crate::{config::Config, pantry_db}; +use async_compression::tokio::bufread::GzipDecoder; +use fs2::FileExt; +use futures::TryStreamExt; +use rusqlite::Connection; +use std::{error::Error, fs::OpenOptions, path::PathBuf}; +use tokio_tar::Archive; +use tokio_util::compat::FuturesAsyncReadCompatExt; + +#[allow(clippy::all)] +pub fn should(config: &Config) -> bool { + if !config.pantry_dir.join("projects").is_dir() { + true + } else if !config + .pantry_dir + .parent() + .unwrap() + .join("pantry.db") + .is_file() + { + true + } else { + false + } +} + +pub async fn replace(config: &Config, conn: &mut Connection) -> Result<(), Box> { + let url = env!("PKGX_PANTRY_TARBALL_URL"); + let dest = &config.pantry_dir; + + std::fs::create_dir_all(dest.clone())?; + let dir = OpenOptions::new() + .read(true) // Open in read-only mode; no need to write. + .open(dest)?; + dir.lock_exclusive()?; + + download_and_extract_pantry(url, dest).await?; + + pantry_db::cache(config, conn)?; + + FileExt::unlock(&dir)?; + + Ok(()) +} + +async fn download_and_extract_pantry(url: &str, dest: &PathBuf) -> Result<(), Box> { + let rsp = reqwest::get(url).await?.error_for_status()?; + + let stream = rsp.bytes_stream(); + + let stream = stream + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) + .into_async_read(); + let stream = stream.compat(); + + let decoder = GzipDecoder::new(stream); + + // Step 3: Extract the tar archive + let mut archive = Archive::new(decoder); + archive.unpack(dest).await?; + + Ok(()) +} diff --git a/crates/lib/src/types.rs b/crates/lib/src/types.rs new file mode 100644 index 00000000..92fa0afe --- /dev/null +++ b/crates/lib/src/types.rs @@ -0,0 +1,135 @@ +use lazy_static::lazy_static; +use libsemverator::range::Range as VersionReq; +use libsemverator::semver::Semver as Version; +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; +use std::error::Error; +use std::fmt; + +//TODO regex is probs not most efficient (but do perf tests if you change it) +lazy_static! { + static ref PACKAGE_REGEX: Regex = Regex::new(r"^(.+?)([\^=~<>@].+)?$").unwrap(); +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct Package { + pub project: String, + pub version: Version, +} + +impl fmt::Display for Package { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}={}", self.project, &self.version) + } +} + +#[derive(Debug, Clone)] +pub struct PackageReq { + pub project: String, + pub constraint: VersionReq, +} + +use regex::Regex; + +impl PackageReq { + pub fn parse(pkgspec: &str) -> Result> { + let input = pkgspec.trim(); + let captures = PACKAGE_REGEX + .captures(input) + .ok_or_else(|| format!("invalid pkgspec: {}", input))?; + + let project = captures.get(1).unwrap().as_str().to_string(); + let str = if let Some(cap) = captures.get(2) { + cap.as_str() + } else { + "*" + }; + let constraint = VersionReq::parse(str)?; + + Ok(Self { + project, + constraint, + }) + } +} + +impl fmt::Display for PackageReq { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.constraint.raw == "*" { + write!(f, "{}", self.project) + } else { + write!(f, "{}{}", self.project, &self.constraint) + } + } +} + +#[derive(Debug, Clone)] +pub struct Installation { + pub path: std::path::PathBuf, + pub pkg: Package, +} + +impl Serialize for Installation { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("MyType", 3)?; + state.serialize_field("path", &self.path)?; + state.serialize_field("project", &self.pkg.project)?; + state.serialize_field("version", &self.pkg.version)?; + state.end() + } +} + +// These are only used per build at present +#[allow(dead_code)] +pub enum Host { + Darwin, + Linux, +} + +// These are only used per build at present +#[allow(dead_code)] +pub enum Arch { + Arm64, + X86_64, +} + +pub fn host() -> (Host, Arch) { + #[cfg(target_os = "macos")] + let host = Host::Darwin; + #[cfg(target_os = "linux")] + let host = Host::Linux; + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + panic!("Unsupported platform"); + + #[cfg(target_arch = "aarch64")] + let arch = Arch::Arm64; + #[cfg(target_arch = "x86_64")] + let arch = Arch::X86_64; + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + panic!("Unsupported architecture"); + + (host, arch) +} + +impl fmt::Display for Host { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let os_str = match self { + Host::Linux => "linux", + Host::Darwin => "darwin", + }; + write!(f, "{}", os_str) + } +} + +impl fmt::Display for Arch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let os_str = match self { + Arch::Arm64 => "aarch64", + Arch::X86_64 => "x86-64", + }; + write!(f, "{}", os_str) + } +} diff --git a/crates/lib/src/utils.rs b/crates/lib/src/utils.rs new file mode 100644 index 00000000..cccbe028 --- /dev/null +++ b/crates/lib/src/utils.rs @@ -0,0 +1,23 @@ +use std::{error::Error, os::unix::fs::PermissionsExt, path::Path}; + +pub async fn find_program(arg: &str, paths: &Vec) -> Result> { + if arg.starts_with("/") { + return Ok(arg.to_string()); + } else if arg.contains("/") { + return Ok(std::env::current_dir() + .unwrap() + .join(arg) + .to_str() + .unwrap() + .to_string()); + } + for path in paths { + let full_path = Path::new(&path).join(arg); + if let Ok(metadata) = full_path.metadata() { + if full_path.is_file() && (metadata.permissions().mode() & 0o111 != 0) { + return Ok(full_path.to_str().unwrap().to_string()); + } + } + } + Err(format!("cmd not found: {}", arg).into()) +} diff --git a/deno.jsonc b/deno.jsonc deleted file mode 100644 index e096fb17..00000000 --- a/deno.jsonc +++ /dev/null @@ -1,41 +0,0 @@ -{ - "compilerOptions": { - "strict": true - }, - "tasks": { - // runs this source checkout, args will be passed - "run": "deno run --allow-all ./entrypoint.ts", - - // you can specify paths to specific tests if you need - "test": "deno test --allow-all", - - // installs to /usr/local/bin/pkgx - "install": "deno task compile && ./pkgx +gnu.org/coreutils /usr/bin/sudo install -D ./pkgx /usr/local/bin/pkgx", - - //--------------------------------------- ci/cd/admin - "coverage": "scripts/run-coverage.sh", - "typecheck": "deno check ./entrypoint.ts", - "compile": "deno compile --lock=deno.lock --allow-all --output \"$INIT_CWD/pkgx\" ./entrypoint.ts" - }, - "pkgx": "deno~2.1.4", - "lint": { - "exclude": ["src/**/*.test.ts"] - }, - "fmt": { - "semiColons": false - }, - "imports": { - "@cliffy/ansi": "jsr:@cliffy/ansi@^1.0.0-rc.7", - "@std/assert": "jsr:@std/assert@^1.0.6", - "@std/fmt": "jsr:@std/fmt@^1.0.2", - "@std/io": "jsr:@std/io@^0.225.0", - "@std/jsonc": "jsr:@std/jsonc@^1.0.1", - "@std/path": "jsr:@std/path@^1.0.6", - "@std/testing": "jsr:@std/testing@^1.0.3", - "@std/yaml": "jsr:@std/yaml@^1.0.5", - "is-what": "https://deno.land/x/is_what@v4.1.15/src/index.ts", - "outdent": "jsr:@cspotcode/outdent@^0.8", - "pkgx": "https://deno.land/x/libpkgx@v0.20.1/mod.ts", - "pkgx/": "https://deno.land/x/libpkgx@v0.20.1/src/" - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index ad6a6f73..00000000 --- a/deno.lock +++ /dev/null @@ -1,1036 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@cliffy/ansi@^1.0.0-rc.7": "1.0.0-rc.7", - "jsr:@cliffy/internal@1.0.0-rc.7": "1.0.0-rc.7", - "jsr:@cspotcode/outdent@0.8": "0.8.0", - "jsr:@std/assert@*": "1.0.6", - "jsr:@std/assert@^1.0.6": "1.0.6", - "jsr:@std/bytes@*": "1.0.2", - "jsr:@std/bytes@^1.0.2": "1.0.2", - "jsr:@std/crypto@1": "1.0.3", - "jsr:@std/data-structures@^1.0.4": "1.0.4", - "jsr:@std/encoding@*": "1.0.5", - "jsr:@std/encoding@1": "1.0.5", - "jsr:@std/encoding@~1.0.5": "1.0.5", - "jsr:@std/fmt@*": "1.0.2", - "jsr:@std/fmt@^1.0.2": "1.0.2", - "jsr:@std/fs@1": "1.0.4", - "jsr:@std/fs@^1.0.4": "1.0.4", - "jsr:@std/internal@^1.0.4": "1.0.4", - "jsr:@std/io@*": "0.225.0", - "jsr:@std/io@0.225": "0.225.0", - "jsr:@std/io@0.225.0": "0.225.0", - "jsr:@std/json@1": "1.0.0", - "jsr:@std/jsonc@*": "1.0.1", - "jsr:@std/jsonc@^1.0.1": "1.0.1", - "jsr:@std/path@*": "1.0.6", - "jsr:@std/path@1": "1.0.6", - "jsr:@std/path@^1.0.6": "1.0.6", - "jsr:@std/testing@*": "1.0.3", - "jsr:@std/testing@^1.0.3": "1.0.3", - "jsr:@std/yaml@*": "1.0.5", - "jsr:@std/yaml@1": "1.0.5", - "jsr:@std/yaml@^1.0.5": "1.0.5", - "npm:@faker-js/faker@*": "8.1.0", - "npm:@types/node@*": "18.16.19" - }, - "jsr": { - "@cliffy/ansi@1.0.0-rc.7": { - "integrity": "f71c921cce224c13d322e5cedba4f38e8f7354c7d855c9cb22729362a53f25aa", - "dependencies": [ - "jsr:@cliffy/internal", - "jsr:@std/encoding@~1.0.5" - ] - }, - "@cliffy/internal@1.0.0-rc.7": { - "integrity": "10412636ab3e67517d448be9eaab1b70c88eba9be22617b5d146257a11cc9b17" - }, - "@cspotcode/outdent@0.8.0": { - "integrity": "bbb3dea1443b4191a091644dfeaf3f182cca83e9003d211c50786c21792695f6" - }, - "@std/assert@1.0.6": { - "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/bytes@1.0.2": { - "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" - }, - "@std/crypto@1.0.3": { - "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" - }, - "@std/data-structures@1.0.4": { - "integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0" - }, - "@std/encoding@1.0.5": { - "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" - }, - "@std/fmt@1.0.2": { - "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" - }, - "@std/fs@1.0.4": { - "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", - "dependencies": [ - "jsr:@std/path@^1.0.6" - ] - }, - "@std/internal@1.0.4": { - "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" - }, - "@std/io@0.225.0": { - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", - "dependencies": [ - "jsr:@std/bytes@^1.0.2" - ] - }, - "@std/json@1.0.0": { - "integrity": "985c1e544918d42e4e84072fc739ac4a19c3a5093292c99742ffcdd03fb6a268" - }, - "@std/jsonc@1.0.1": { - "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda", - "dependencies": [ - "jsr:@std/json" - ] - }, - "@std/path@1.0.6": { - "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" - }, - "@std/testing@1.0.3": { - "integrity": "f98c2bee53860a5916727d7e7d3abe920dd6f9edace022e2d059f00d05c2cf42", - "dependencies": [ - "jsr:@std/assert@^1.0.6", - "jsr:@std/data-structures", - "jsr:@std/fs@^1.0.4", - "jsr:@std/internal", - "jsr:@std/path@^1.0.6" - ] - }, - "@std/yaml@1.0.5": { - "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" - } - }, - "npm": { - "@faker-js/faker@8.1.0": { - "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==" - }, - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" - } - }, - "redirects": { - "https://deno.land/std/fmt/colors.ts": "https://deno.land/std@0.212.0/fmt/colors.ts", - "https://deno.land/x/semver/mod.ts": "https://deno.land/x/semver@v1.4.1/mod.ts" - }, - "remote": { - "https://deno.land/std@0.176.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.176.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.176.0/encoding/hex.ts": "50f8c95b52eae24395d3dfcb5ec1ced37c5fe7610ef6fffdcc8b0fdc38e3b32f", - "https://deno.land/std@0.176.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", - "https://deno.land/std@0.176.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", - "https://deno.land/std@0.176.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8", - "https://deno.land/std@0.176.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", - "https://deno.land/std@0.176.0/fs/ensure_dir.ts": "724209875497a6b4628dfb256116e5651c4f7816741368d6c44aab2531a1e603", - "https://deno.land/std@0.176.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9", - "https://deno.land/std@0.176.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4", - "https://deno.land/std@0.176.0/fs/ensure_symlink.ts": "2955cc8332aeca9bdfefd05d8d3976b94e282b0f353392a71684808ed2ffdd41", - "https://deno.land/std@0.176.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", - "https://deno.land/std@0.176.0/fs/exists.ts": "b8c8a457b71e9d7f29b9d2f87aad8dba2739cbe637e8926d6ba6e92567875f8e", - "https://deno.land/std@0.176.0/fs/expand_glob.ts": "45d17e89796a24bd6002e4354eda67b4301bb8ba67d2cac8453cdabccf1d9ab0", - "https://deno.land/std@0.176.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", - "https://deno.land/std@0.176.0/fs/move.ts": "4cb47f880e3f0582c55e71c9f8b1e5e8cfaacb5e84f7390781dd563b7298ec19", - "https://deno.land/std@0.176.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", - "https://deno.land/std@0.176.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.176.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.176.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.176.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.176.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.176.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", - "https://deno.land/std@0.176.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.176.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.176.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/std@0.179.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.179.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.179.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.179.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.179.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.179.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.179.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.179.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", - "https://deno.land/std@0.179.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.179.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.179.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/std@0.196.0/_util/diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", - "https://deno.land/std@0.196.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.196.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", - "https://deno.land/std@0.196.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", - "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.196.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", - "https://deno.land/std@0.196.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", - "https://deno.land/std@0.196.0/assert/assert_equals.ts": "a0ee60574e437bcab2dcb79af9d48dc88845f8fd559468d9c21b15fd638ef943", - "https://deno.land/std@0.196.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", - "https://deno.land/std@0.196.0/assert/assert_false.ts": "a9962749f4bf5844e3fa494257f1de73d69e4fe0e82c34d0099287552163a2dc", - "https://deno.land/std@0.196.0/assert/assert_instance_of.ts": "09fd297352a5b5bbb16da2b5e1a0d8c6c44da5447772648622dcc7df7af1ddb8", - "https://deno.land/std@0.196.0/assert/assert_is_error.ts": "b4eae4e5d182272efc172bf28e2e30b86bb1650cd88aea059e5d2586d4160fb9", - "https://deno.land/std@0.196.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", - "https://deno.land/std@0.196.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", - "https://deno.land/std@0.196.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", - "https://deno.land/std@0.196.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", - "https://deno.land/std@0.196.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", - "https://deno.land/std@0.196.0/assert/assert_object_match.ts": "27439c4f41dce099317566144299468ca822f556f1cc697f4dc8ed61fe9fee4c", - "https://deno.land/std@0.196.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", - "https://deno.land/std@0.196.0/assert/assert_strict_equals.ts": "5cf29b38b3f8dece95287325723272aa04e04dbf158d886d662fa594fddc9ed3", - "https://deno.land/std@0.196.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", - "https://deno.land/std@0.196.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", - "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.196.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", - "https://deno.land/std@0.196.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", - "https://deno.land/std@0.196.0/assert/mod.ts": "08d55a652c22c5da0215054b21085cec25a5da47ce4a6f9de7d9ad36df35bdee", - "https://deno.land/std@0.196.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", - "https://deno.land/std@0.196.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", - "https://deno.land/std@0.196.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", - "https://deno.land/std@0.196.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", - "https://deno.land/std@0.196.0/crypto/_fnv/fnv32.ts": "e4649dfdefc5c987ed53c3c25db62db771a06d9d1b9c36d2b5cf0853b8e82153", - "https://deno.land/std@0.196.0/crypto/_fnv/fnv64.ts": "bfa0e4702061fdb490a14e6bf5f9168a22fb022b307c5723499469bfefca555e", - "https://deno.land/std@0.196.0/crypto/_fnv/mod.ts": "f956a95f58910f223e420340b7404702ecd429603acd4491fa77af84f746040c", - "https://deno.land/std@0.196.0/crypto/_fnv/util.ts": "accba12bfd80a352e32a872f87df2a195e75561f1b1304a4cb4f5a4648d288f9", - "https://deno.land/std@0.196.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "85b50eee2e511584698c04f1d84155e57452ea963106fee64987c326e9e5d25d", - "https://deno.land/std@0.196.0/crypto/_wasm/mod.ts": "973058e70052c98292b567d1c8396dffc28d6dfc6a44f0763032f6fbdf5222f5", - "https://deno.land/std@0.196.0/crypto/crypto.ts": "731dffeb3054ddca38e6279a6ad718abef0fb6e218639c496d1c208db70e55e4", - "https://deno.land/std@0.196.0/crypto/keystack.ts": "877ab0f19eb7d37ad6495190d3c3e39f58e9c52e0b6a966f82fd6df67ca55f90", - "https://deno.land/std@0.196.0/crypto/mod.ts": "ae384519e85eca9aeff4e7111ed153df8f3dbda7b35b70850ed4b3e9c8cec4d5", - "https://deno.land/std@0.196.0/crypto/timing_safe_equal.ts": "7b0a4d2ef1c17590e0ad6c0cb1776369d2ba80cd99e945005e117851690507fe", - "https://deno.land/std@0.196.0/crypto/to_hash_string.ts": "6927c768f3e373a1be4a31555a45ccecf7bd413105455cc334ad3f908cfa986f", - "https://deno.land/std@0.196.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d", - "https://deno.land/std@0.196.0/encoding/base64url.ts": "2ed4ba122b20fedf226c5d337cf22ee2024fa73a8f85d915d442af7e9ce1fae1", - "https://deno.land/std@0.196.0/encoding/hex.ts": "b4b1a7cb678745b0bf181ed8cf2498c7be00d121a7de244b752fbf9c7d9c48cd", - "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", - "https://deno.land/std@0.196.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", - "https://deno.land/std@0.196.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", - "https://deno.land/std@0.196.0/io/buf_reader.ts": "2bccff0878537ef201c5051fc0db0ce8196388c5ea69d2be6be1900fe48c5f4b", - "https://deno.land/std@0.196.0/io/buffer.ts": "4d6883daeb2e698579c4064170515683d69f40f3de019bfe46c5cf31e74ae793", - "https://deno.land/std@0.196.0/io/read_lines.ts": "c526c12a20a9386dc910d500f9cdea43cba974e853397790bd146817a7eef8cc", - "https://deno.land/std@0.196.0/json/common.ts": "ecd5e87d45b5f0df33238ed8b1746e1444da7f5c86ae53d0f0b04280f41a25bb", - "https://deno.land/std@0.196.0/jsonc/mod.ts": "b88dce28eb3645667caa856538ae2fe87af51410822544a0b45a4177ef3bd7dd", - "https://deno.land/std@0.196.0/jsonc/parse.ts": "c1096e2b7ffb4996d7ed841dfdb29a4fccc78edcc55299beaa20d6fe5facf7b6", - "https://deno.land/std@0.196.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.196.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.196.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.196.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.196.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.196.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef", - "https://deno.land/std@0.196.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.196.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.196.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12", - "https://deno.land/std@0.196.0/streams/read_all.ts": "ee319772fb0fd28302f97343cc48dfcf948f154fd0d755d8efe65814b70533be", - "https://deno.land/std@0.196.0/streams/write_all.ts": "aec90152978581ea62d56bb53a5cbf487e6a89c902f87c5969681ffbdf32b998", - "https://deno.land/std@0.196.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", - "https://deno.land/std@0.196.0/testing/asserts.ts": "b4e4b1359393aeff09e853e27901a982c685cb630df30426ed75496961931946", - "https://deno.land/std@0.196.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", - "https://deno.land/std@0.196.0/testing/mock.ts": "4c52b8312d159179fdd9d9a1b35e342ee4e1a1248f29e5c7f57fb4011c3f55ed", - "https://deno.land/std@0.196.0/yaml/_error.ts": "b59e2c76ce5a47b1b9fa0ff9f96c1dd92ea1e1b17ce4347ece5944a95c3c1a84", - "https://deno.land/std@0.196.0/yaml/_loader/loader.ts": "47b9592efcb390b58b1903cc471bfdf1fc71a0d2d2b31e37b5cae7d8804c7aed", - "https://deno.land/std@0.196.0/yaml/_loader/loader_state.ts": "0841870b467169269d7c2dfa75cd288c319bc06f65edd9e42c29e5fced91c7a4", - "https://deno.land/std@0.196.0/yaml/_mark.ts": "dcd8585dee585e024475e9f3fe27d29740670fb64ebb970388094cad0fc11d5d", - "https://deno.land/std@0.196.0/yaml/_state.ts": "ef03d55ec235d48dcfbecc0ab3ade90bfae69a61094846e08003421c2cf5cfc6", - "https://deno.land/std@0.196.0/yaml/_type/binary.ts": "d34d8c8d8ed521e270cfede3401c425b971af4f6c69da1e2cb32b172d42c7da7", - "https://deno.land/std@0.196.0/yaml/_type/bool.ts": "5bfa75da84343d45347b521ba4e5aeace9fe6f53447405290d53315a3fc20e66", - "https://deno.land/std@0.196.0/yaml/_type/float.ts": "056bd3cb9c5586238b20517511014fb24b0e36f98f9f6073e12da308b6b9808a", - "https://deno.land/std@0.196.0/yaml/_type/function.ts": "ff574fe84a750695302864e1c31b93f12d14ada4bde79a5f93197fc33ad17471", - "https://deno.land/std@0.196.0/yaml/_type/int.ts": "563ad074f0fa7aecf6b6c3d84135bcc95a8269dcc15de878de20ce868fd773fa", - "https://deno.land/std@0.196.0/yaml/_type/map.ts": "7b105e4ab03a361c61e7e335a0baf4d40f06460b13920e5af3fb2783a1464000", - "https://deno.land/std@0.196.0/yaml/_type/merge.ts": "8192bf3e4d637f32567917f48bb276043da9cf729cf594e5ec191f7cd229337e", - "https://deno.land/std@0.196.0/yaml/_type/mod.ts": "060e2b3d38725094b77ea3a3f05fc7e671fced8e67ca18e525be98c4aa8f4bbb", - "https://deno.land/std@0.196.0/yaml/_type/nil.ts": "606e8f0c44d73117c81abec822f89ef81e40f712258c74f186baa1af659b8887", - "https://deno.land/std@0.196.0/yaml/_type/omap.ts": "cfe59a294726f5cea705c39a61fd2b08199cf48f4ccd6b040cb550ec0f38d0a1", - "https://deno.land/std@0.196.0/yaml/_type/pairs.ts": "0032fdfe57558d21696a4f8cf5b5cfd1f698743177080affc18629685c905666", - "https://deno.land/std@0.196.0/yaml/_type/regexp.ts": "1ce118de15b2da43b4bd8e4395f42d448b731acf3bdaf7c888f40789f9a95f8b", - "https://deno.land/std@0.196.0/yaml/_type/seq.ts": "95333abeec8a7e4d967b8c8328b269e342a4bbdd2585395549b9c4f58c8533a2", - "https://deno.land/std@0.196.0/yaml/_type/set.ts": "f28ba44e632ef2a6eb580486fd47a460445eeddbdf1dbc739c3e62486f566092", - "https://deno.land/std@0.196.0/yaml/_type/str.ts": "a67a3c6e429d95041399e964015511779b1130ea5889fa257c48457bd3446e31", - "https://deno.land/std@0.196.0/yaml/_type/timestamp.ts": "706ea80a76a73e48efaeb400ace087da1f927647b53ad6f754f4e06d51af087f", - "https://deno.land/std@0.196.0/yaml/_type/undefined.ts": "94a316ca450597ccbc6750cbd79097ad0d5f3a019797eed3c841a040c29540ba", - "https://deno.land/std@0.196.0/yaml/_utils.ts": "26b311f0d42a7ce025060bd6320a68b50e52fd24a839581eb31734cd48e20393", - "https://deno.land/std@0.196.0/yaml/parse.ts": "1fbbda572bf3fff578b6482c0d8b85097a38de3176bf3ab2ca70c25fb0c960ef", - "https://deno.land/std@0.196.0/yaml/schema.ts": "96908b78dc50c340074b93fc1598d5e7e2fe59103f89ff81e5a49b2dedf77a67", - "https://deno.land/std@0.196.0/yaml/schema/core.ts": "fa406f18ceedc87a50e28bb90ec7a4c09eebb337f94ef17468349794fa828639", - "https://deno.land/std@0.196.0/yaml/schema/default.ts": "0047e80ae8a4a93293bc4c557ae8a546aabd46bb7165b9d9b940d57b4d88bde9", - "https://deno.land/std@0.196.0/yaml/schema/extended.ts": "0784416bf062d20a1626b53c03380e265b3e39b9409afb9f4cb7d659fd71e60d", - "https://deno.land/std@0.196.0/yaml/schema/failsafe.ts": "d219ab5febc43f770917d8ec37735a4b1ad671149846cbdcade767832b42b92b", - "https://deno.land/std@0.196.0/yaml/schema/json.ts": "5f41dd7c2f1ad545ef6238633ce9ee3d444dfc5a18101e1768bd5504bf90e5e5", - "https://deno.land/std@0.196.0/yaml/schema/mod.ts": "4472e827bab5025e92bc2eb2eeefa70ecbefc64b2799b765c69af84822efef32", - "https://deno.land/std@0.196.0/yaml/type.ts": "1aabb8e0a3f4229ce0a3526256f68826d9bdf65a36c8a3890ead8011fcba7670", - "https://deno.land/std@0.202.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", - "https://deno.land/std@0.202.0/assert/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", - "https://deno.land/std@0.202.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", - "https://deno.land/std@0.202.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.202.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", - "https://deno.land/std@0.202.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", - "https://deno.land/std@0.202.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", - "https://deno.land/std@0.202.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", - "https://deno.land/std@0.202.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", - "https://deno.land/std@0.202.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", - "https://deno.land/std@0.202.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", - "https://deno.land/std@0.202.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", - "https://deno.land/std@0.202.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", - "https://deno.land/std@0.202.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", - "https://deno.land/std@0.202.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", - "https://deno.land/std@0.202.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", - "https://deno.land/std@0.202.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", - "https://deno.land/std@0.202.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", - "https://deno.land/std@0.202.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", - "https://deno.land/std@0.202.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", - "https://deno.land/std@0.202.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", - "https://deno.land/std@0.202.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", - "https://deno.land/std@0.202.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", - "https://deno.land/std@0.202.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", - "https://deno.land/std@0.202.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", - "https://deno.land/std@0.202.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.202.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", - "https://deno.land/std@0.202.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", - "https://deno.land/std@0.202.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", - "https://deno.land/std@0.202.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", - "https://deno.land/std@0.202.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", - "https://deno.land/std@0.202.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", - "https://deno.land/std@0.202.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", - "https://deno.land/std@0.202.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", - "https://deno.land/std@0.202.0/io/buf_reader.ts": "0bd8ad26255945b5f418940db23db03bee0c160dbb5ae4627e2c0be3b361df6a", - "https://deno.land/std@0.202.0/io/buffer.ts": "4d6883daeb2e698579c4064170515683d69f40f3de019bfe46c5cf31e74ae793", - "https://deno.land/std@0.202.0/io/read_lines.ts": "c526c12a20a9386dc910d500f9cdea43cba974e853397790bd146817a7eef8cc", - "https://deno.land/std@0.202.0/json/common.ts": "ecd5e87d45b5f0df33238ed8b1746e1444da7f5c86ae53d0f0b04280f41a25bb", - "https://deno.land/std@0.202.0/jsonc/mod.ts": "b88dce28eb3645667caa856538ae2fe87af51410822544a0b45a4177ef3bd7dd", - "https://deno.land/std@0.202.0/jsonc/parse.ts": "c1096e2b7ffb4996d7ed841dfdb29a4fccc78edcc55299beaa20d6fe5facf7b6", - "https://deno.land/std@0.202.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", - "https://deno.land/std@0.202.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.202.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", - "https://deno.land/std@0.202.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", - "https://deno.land/std@0.202.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", - "https://deno.land/std@0.202.0/path/_from_file_url.ts": "6eadfae2e6f63ad9ee46b26db4a1b16583055c0392acedfb50ed2fc694b6f581", - "https://deno.land/std@0.202.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.202.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", - "https://deno.land/std@0.202.0/path/_join.ts": "815f5e85b042285175b1492dd5781240ce126c23bd97bad6b8211fe7129c538e", - "https://deno.land/std@0.202.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", - "https://deno.land/std@0.202.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", - "https://deno.land/std@0.202.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", - "https://deno.land/std@0.202.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", - "https://deno.land/std@0.202.0/path/_resolve.ts": "7a3616f1093735ed327e758313b79c3c04ea921808ca5f19ddf240cb68d0adf6", - "https://deno.land/std@0.202.0/path/_to_file_url.ts": "a141e4a525303e1a3a0c0571fd024552b5f3553a2af7d75d1ff3a503dcbb66d8", - "https://deno.land/std@0.202.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", - "https://deno.land/std@0.202.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", - "https://deno.land/std@0.202.0/path/basename.ts": "bdfa5a624c6a45564dc6758ef2077f2822978a6dbe77b0a3514f7d1f81362930", - "https://deno.land/std@0.202.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.202.0/path/dirname.ts": "b6533f4ee4174a526dec50c279534df5345836dfdc15318400b08c62a62a39dd", - "https://deno.land/std@0.202.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", - "https://deno.land/std@0.202.0/path/format.ts": "110270b238514dd68455a4c54956215a1aff7e37e22e4427b7771cefe1920aa5", - "https://deno.land/std@0.202.0/path/from_file_url.ts": "9f5cb58d58be14c775ec2e57fc70029ac8b17ed3bd7fe93e475b07280adde0ac", - "https://deno.land/std@0.202.0/path/glob.ts": "593e2c3573883225c25c5a21aaa8e9382a696b8e175ea20a3b6a1471ad17aaed", - "https://deno.land/std@0.202.0/path/is_absolute.ts": "0b92eb35a0a8780e9f16f16bb23655b67dace6a8e0d92d42039e518ee38103c1", - "https://deno.land/std@0.202.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", - "https://deno.land/std@0.202.0/path/mod.ts": "6e1efb0b13121463aedb53ea51dabf5639a3172ab58c89900bbb72b486872532", - "https://deno.land/std@0.202.0/path/normalize.ts": "6ea523e0040979dd7ae2f1be5bf2083941881a252554c0f32566a18b03021955", - "https://deno.land/std@0.202.0/path/parse.ts": "be8de342bb9e1924d78dc4d93c45215c152db7bf738ec32475560424b119b394", - "https://deno.land/std@0.202.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", - "https://deno.land/std@0.202.0/path/relative.ts": "8bedac226afd360afc45d451a6c29fabceaf32978526bcb38e0c852661f66c61", - "https://deno.land/std@0.202.0/path/resolve.ts": "133161e4949fc97f9ca67988d51376b0f5eef8968a6372325ab84d39d30b80dc", - "https://deno.land/std@0.202.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", - "https://deno.land/std@0.202.0/path/to_file_url.ts": "00e6322373dd51ad109956b775e4e72e5f9fa68ce2c6b04e4af2a6eed3825d31", - "https://deno.land/std@0.202.0/path/to_namespaced_path.ts": "1b1db3055c343ab389901adfbda34e82b7386bcd1c744d54f9c1496ee0fd0c3d", - "https://deno.land/std@0.202.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", - "https://deno.land/std@0.202.0/streams/read_all.ts": "3b20a50af87d1bfebefa9c2dbda49e2b214d8ab0382ffdcc8ce858af80a912be", - "https://deno.land/std@0.202.0/streams/write_all.ts": "4cdd36256f892fe7aead46338054f6ea813a63765e87bda4c60e8c5a57d1c5c1", - "https://deno.land/std@0.202.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", - "https://deno.land/std@0.202.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", - "https://deno.land/std@0.202.0/testing/mock.ts": "6576b4aa55ee20b1990d656a78fff83599e190948c00e9f25a7f3ac5e9d6492d", - "https://deno.land/std@0.202.0/yaml/_error.ts": "b59e2c76ce5a47b1b9fa0ff9f96c1dd92ea1e1b17ce4347ece5944a95c3c1a84", - "https://deno.land/std@0.202.0/yaml/_loader/loader.ts": "63ec7f0a265dbbabc54b25a4beefff7650e205160a2d75c7d8f8363b5f84851a", - "https://deno.land/std@0.202.0/yaml/_loader/loader_state.ts": "0841870b467169269d7c2dfa75cd288c319bc06f65edd9e42c29e5fced91c7a4", - "https://deno.land/std@0.202.0/yaml/_mark.ts": "dcd8585dee585e024475e9f3fe27d29740670fb64ebb970388094cad0fc11d5d", - "https://deno.land/std@0.202.0/yaml/_state.ts": "ef03d55ec235d48dcfbecc0ab3ade90bfae69a61094846e08003421c2cf5cfc6", - "https://deno.land/std@0.202.0/yaml/_type/binary.ts": "24d49614463a7339a8a16d894919c2ec18a10588ae360ec352093b60e2cc8b0d", - "https://deno.land/std@0.202.0/yaml/_type/bool.ts": "5bfa75da84343d45347b521ba4e5aeace9fe6f53447405290d53315a3fc20e66", - "https://deno.land/std@0.202.0/yaml/_type/float.ts": "056bd3cb9c5586238b20517511014fb24b0e36f98f9f6073e12da308b6b9808a", - "https://deno.land/std@0.202.0/yaml/_type/function.ts": "ff574fe84a750695302864e1c31b93f12d14ada4bde79a5f93197fc33ad17471", - "https://deno.land/std@0.202.0/yaml/_type/int.ts": "563ad074f0fa7aecf6b6c3d84135bcc95a8269dcc15de878de20ce868fd773fa", - "https://deno.land/std@0.202.0/yaml/_type/map.ts": "7b105e4ab03a361c61e7e335a0baf4d40f06460b13920e5af3fb2783a1464000", - "https://deno.land/std@0.202.0/yaml/_type/merge.ts": "8192bf3e4d637f32567917f48bb276043da9cf729cf594e5ec191f7cd229337e", - "https://deno.land/std@0.202.0/yaml/_type/mod.ts": "060e2b3d38725094b77ea3a3f05fc7e671fced8e67ca18e525be98c4aa8f4bbb", - "https://deno.land/std@0.202.0/yaml/_type/nil.ts": "606e8f0c44d73117c81abec822f89ef81e40f712258c74f186baa1af659b8887", - "https://deno.land/std@0.202.0/yaml/_type/omap.ts": "cfe59a294726f5cea705c39a61fd2b08199cf48f4ccd6b040cb550ec0f38d0a1", - "https://deno.land/std@0.202.0/yaml/_type/pairs.ts": "0032fdfe57558d21696a4f8cf5b5cfd1f698743177080affc18629685c905666", - "https://deno.land/std@0.202.0/yaml/_type/regexp.ts": "1ce118de15b2da43b4bd8e4395f42d448b731acf3bdaf7c888f40789f9a95f8b", - "https://deno.land/std@0.202.0/yaml/_type/seq.ts": "95333abeec8a7e4d967b8c8328b269e342a4bbdd2585395549b9c4f58c8533a2", - "https://deno.land/std@0.202.0/yaml/_type/set.ts": "f28ba44e632ef2a6eb580486fd47a460445eeddbdf1dbc739c3e62486f566092", - "https://deno.land/std@0.202.0/yaml/_type/str.ts": "a67a3c6e429d95041399e964015511779b1130ea5889fa257c48457bd3446e31", - "https://deno.land/std@0.202.0/yaml/_type/timestamp.ts": "706ea80a76a73e48efaeb400ace087da1f927647b53ad6f754f4e06d51af087f", - "https://deno.land/std@0.202.0/yaml/_type/undefined.ts": "94a316ca450597ccbc6750cbd79097ad0d5f3a019797eed3c841a040c29540ba", - "https://deno.land/std@0.202.0/yaml/_utils.ts": "26b311f0d42a7ce025060bd6320a68b50e52fd24a839581eb31734cd48e20393", - "https://deno.land/std@0.202.0/yaml/parse.ts": "1fbbda572bf3fff578b6482c0d8b85097a38de3176bf3ab2ca70c25fb0c960ef", - "https://deno.land/std@0.202.0/yaml/schema.ts": "96908b78dc50c340074b93fc1598d5e7e2fe59103f89ff81e5a49b2dedf77a67", - "https://deno.land/std@0.202.0/yaml/schema/core.ts": "fa406f18ceedc87a50e28bb90ec7a4c09eebb337f94ef17468349794fa828639", - "https://deno.land/std@0.202.0/yaml/schema/default.ts": "0047e80ae8a4a93293bc4c557ae8a546aabd46bb7165b9d9b940d57b4d88bde9", - "https://deno.land/std@0.202.0/yaml/schema/extended.ts": "0784416bf062d20a1626b53c03380e265b3e39b9409afb9f4cb7d659fd71e60d", - "https://deno.land/std@0.202.0/yaml/schema/failsafe.ts": "d219ab5febc43f770917d8ec37735a4b1ad671149846cbdcade767832b42b92b", - "https://deno.land/std@0.202.0/yaml/schema/json.ts": "5f41dd7c2f1ad545ef6238633ce9ee3d444dfc5a18101e1768bd5504bf90e5e5", - "https://deno.land/std@0.202.0/yaml/schema/mod.ts": "4472e827bab5025e92bc2eb2eeefa70ecbefc64b2799b765c69af84822efef32", - "https://deno.land/std@0.202.0/yaml/type.ts": "1aabb8e0a3f4229ce0a3526256f68826d9bdf65a36c8a3890ead8011fcba7670", - "https://deno.land/std@0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", - "https://deno.land/std@0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", - "https://deno.land/std@0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", - "https://deno.land/std@0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.204.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", - "https://deno.land/std@0.204.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", - "https://deno.land/std@0.204.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", - "https://deno.land/std@0.204.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", - "https://deno.land/std@0.204.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", - "https://deno.land/std@0.204.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", - "https://deno.land/std@0.204.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", - "https://deno.land/std@0.204.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", - "https://deno.land/std@0.204.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", - "https://deno.land/std@0.204.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", - "https://deno.land/std@0.204.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", - "https://deno.land/std@0.204.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", - "https://deno.land/std@0.204.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", - "https://deno.land/std@0.204.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", - "https://deno.land/std@0.204.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", - "https://deno.land/std@0.204.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", - "https://deno.land/std@0.204.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", - "https://deno.land/std@0.204.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", - "https://deno.land/std@0.204.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", - "https://deno.land/std@0.204.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", - "https://deno.land/std@0.204.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", - "https://deno.land/std@0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.204.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", - "https://deno.land/std@0.204.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", - "https://deno.land/std@0.204.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", - "https://deno.land/std@0.204.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", - "https://deno.land/std@0.204.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", - "https://deno.land/std@0.204.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", - "https://deno.land/std@0.204.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", - "https://deno.land/std@0.204.0/crypto/_fnv/fnv32.ts": "e4649dfdefc5c987ed53c3c25db62db771a06d9d1b9c36d2b5cf0853b8e82153", - "https://deno.land/std@0.204.0/crypto/_fnv/fnv64.ts": "bfa0e4702061fdb490a14e6bf5f9168a22fb022b307c5723499469bfefca555e", - "https://deno.land/std@0.204.0/crypto/_fnv/mod.ts": "f956a95f58910f223e420340b7404702ecd429603acd4491fa77af84f746040c", - "https://deno.land/std@0.204.0/crypto/_fnv/util.ts": "accba12bfd80a352e32a872f87df2a195e75561f1b1304a4cb4f5a4648d288f9", - "https://deno.land/std@0.204.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "e0e4f9952f7ad3cbcc63a04e49865e7f6355cb830c593c3f5a3fc731bebbfa7d", - "https://deno.land/std@0.204.0/crypto/_wasm/mod.ts": "c4386f0ee80968faa82079cc21066aba2147bd6ca74ae45f1478fca7dbca804a", - "https://deno.land/std@0.204.0/crypto/crypto.ts": "e040c130e22b6c8b416bf4073be1cc94c53c57a2a29f75ce8c29c37745d13cc3", - "https://deno.land/std@0.204.0/crypto/keystack.ts": "cec9ddf60e0c0c9970360e66c9402afb3c0c68bcd50ef19c68daf63f94e40863", - "https://deno.land/std@0.204.0/crypto/mod.ts": "ae384519e85eca9aeff4e7111ed153df8f3dbda7b35b70850ed4b3e9c8cec4d5", - "https://deno.land/std@0.204.0/crypto/timing_safe_equal.ts": "f6edc08d702f660b1ab3505b74d53a9d499e34a1351f6ab70f5ce8653fee8fb7", - "https://deno.land/std@0.204.0/crypto/to_hash_string.ts": "c78050f72a9d4a690d85e83a85a43c2951d23a04d5b2a4537b2ebc00854421be", - "https://deno.land/std@0.204.0/crypto/unstable/keystack.ts": "624c2f2d8fa6711d44fcb8fbca346e7fb56afa7aa3fcf2d8e6db32978e9fbac2", - "https://deno.land/std@0.204.0/encoding/_util.ts": "f368920189c4fe6592ab2e93bd7ded8f3065b84f95cd3e036a4a10a75649dcba", - "https://deno.land/std@0.204.0/encoding/base64.ts": "cc03110d6518170aeaa68ec97f89c6d6e2276294b30807e7332591d7ce2e4b72", - "https://deno.land/std@0.204.0/encoding/base64url.ts": "7608862858d28a003f9d6cb78dd61e645ecd1ae1f45faf0e09a306eafe66b16e", - "https://deno.land/std@0.204.0/encoding/hex.ts": "d41e9c3f7dd9d4738c40c2b9e6db5eb32e9dc103360291aff63a5c3fccdb45a6", - "https://deno.land/std@0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", - "https://deno.land/std@0.204.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", - "https://deno.land/std@0.204.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", - "https://deno.land/std@0.204.0/io/buf_reader.ts": "f7a43953b05eecbaea56ebcb654035bc91e117d6986a0b2d346c1d0de1d4dac4", - "https://deno.land/std@0.204.0/io/buffer.ts": "2108faba32659e5a390bb59b1b4578ff0120b185d9310dd6fbf3b3a3d5775715", - "https://deno.land/std@0.204.0/io/read_lines.ts": "8d09daed983812ef4cee6e5333633e789673aeecbe109da09d281d4ceaf75d5c", - "https://deno.land/std@0.204.0/json/common.ts": "ecd5e87d45b5f0df33238ed8b1746e1444da7f5c86ae53d0f0b04280f41a25bb", - "https://deno.land/std@0.204.0/jsonc/mod.ts": "b88dce28eb3645667caa856538ae2fe87af51410822544a0b45a4177ef3bd7dd", - "https://deno.land/std@0.204.0/jsonc/parse.ts": "c1096e2b7ffb4996d7ed841dfdb29a4fccc78edcc55299beaa20d6fe5facf7b6", - "https://deno.land/std@0.204.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", - "https://deno.land/std@0.204.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", - "https://deno.land/std@0.204.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", - "https://deno.land/std@0.204.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.204.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.204.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", - "https://deno.land/std@0.204.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", - "https://deno.land/std@0.204.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", - "https://deno.land/std@0.204.0/path/_common/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", - "https://deno.land/std@0.204.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.204.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", - "https://deno.land/std@0.204.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", - "https://deno.land/std@0.204.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", - "https://deno.land/std@0.204.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", - "https://deno.land/std@0.204.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.204.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", - "https://deno.land/std@0.204.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", - "https://deno.land/std@0.204.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", - "https://deno.land/std@0.204.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", - "https://deno.land/std@0.204.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", - "https://deno.land/std@0.204.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", - "https://deno.land/std@0.204.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", - "https://deno.land/std@0.204.0/path/glob.ts": "9c77cf47db1d786e2ebf66670824d03fd84ecc7c807cac24441eb9d5cb6a2986", - "https://deno.land/std@0.204.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", - "https://deno.land/std@0.204.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", - "https://deno.land/std@0.204.0/path/mod.ts": "2d62a0a8b78a60e8e6f485d881bac6b61d58573b11cf585fb7c8fc50d9b20d80", - "https://deno.land/std@0.204.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", - "https://deno.land/std@0.204.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", - "https://deno.land/std@0.204.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", - "https://deno.land/std@0.204.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", - "https://deno.land/std@0.204.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.204.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", - "https://deno.land/std@0.204.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", - "https://deno.land/std@0.204.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", - "https://deno.land/std@0.204.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", - "https://deno.land/std@0.204.0/path/posix/glob.ts": "86c3f06d1c98303613c74650961c3e24bdb871cde2a97c3ae7f0f6d4abbef445", - "https://deno.land/std@0.204.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", - "https://deno.land/std@0.204.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", - "https://deno.land/std@0.204.0/path/posix/mod.ts": "6bfa8a42d85345b12dbe8571028ca2c62d460b6ef968125e498602b43b6cf6b6", - "https://deno.land/std@0.204.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", - "https://deno.land/std@0.204.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", - "https://deno.land/std@0.204.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", - "https://deno.land/std@0.204.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", - "https://deno.land/std@0.204.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", - "https://deno.land/std@0.204.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", - "https://deno.land/std@0.204.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", - "https://deno.land/std@0.204.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", - "https://deno.land/std@0.204.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", - "https://deno.land/std@0.204.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", - "https://deno.land/std@0.204.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", - "https://deno.land/std@0.204.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", - "https://deno.land/std@0.204.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", - "https://deno.land/std@0.204.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", - "https://deno.land/std@0.204.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.204.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", - "https://deno.land/std@0.204.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", - "https://deno.land/std@0.204.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", - "https://deno.land/std@0.204.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", - "https://deno.land/std@0.204.0/path/windows/glob.ts": "0286fb89ecd21db5cbf3b6c79e2b87c889b03f1311e66fb769e6b905d4142332", - "https://deno.land/std@0.204.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", - "https://deno.land/std@0.204.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", - "https://deno.land/std@0.204.0/path/windows/mod.ts": "c3d1a36fbf9f6db1320bcb4fbda8de011d25461be3497105e15cbea1e3726198", - "https://deno.land/std@0.204.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", - "https://deno.land/std@0.204.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", - "https://deno.land/std@0.204.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", - "https://deno.land/std@0.204.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", - "https://deno.land/std@0.204.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", - "https://deno.land/std@0.204.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", - "https://deno.land/std@0.204.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", - "https://deno.land/std@0.204.0/streams/read_all.ts": "3b20a50af87d1bfebefa9c2dbda49e2b214d8ab0382ffdcc8ce858af80a912be", - "https://deno.land/std@0.204.0/streams/write_all.ts": "4cdd36256f892fe7aead46338054f6ea813a63765e87bda4c60e8c5a57d1c5c1", - "https://deno.land/std@0.204.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", - "https://deno.land/std@0.204.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", - "https://deno.land/std@0.204.0/testing/mock.ts": "6576b4aa55ee20b1990d656a78fff83599e190948c00e9f25a7f3ac5e9d6492d", - "https://deno.land/std@0.204.0/yaml/_error.ts": "b59e2c76ce5a47b1b9fa0ff9f96c1dd92ea1e1b17ce4347ece5944a95c3c1a84", - "https://deno.land/std@0.204.0/yaml/_loader/loader.ts": "63ec7f0a265dbbabc54b25a4beefff7650e205160a2d75c7d8f8363b5f84851a", - "https://deno.land/std@0.204.0/yaml/_loader/loader_state.ts": "0841870b467169269d7c2dfa75cd288c319bc06f65edd9e42c29e5fced91c7a4", - "https://deno.land/std@0.204.0/yaml/_mark.ts": "dcd8585dee585e024475e9f3fe27d29740670fb64ebb970388094cad0fc11d5d", - "https://deno.land/std@0.204.0/yaml/_state.ts": "ef03d55ec235d48dcfbecc0ab3ade90bfae69a61094846e08003421c2cf5cfc6", - "https://deno.land/std@0.204.0/yaml/_type/binary.ts": "24d49614463a7339a8a16d894919c2ec18a10588ae360ec352093b60e2cc8b0d", - "https://deno.land/std@0.204.0/yaml/_type/bool.ts": "5bfa75da84343d45347b521ba4e5aeace9fe6f53447405290d53315a3fc20e66", - "https://deno.land/std@0.204.0/yaml/_type/float.ts": "056bd3cb9c5586238b20517511014fb24b0e36f98f9f6073e12da308b6b9808a", - "https://deno.land/std@0.204.0/yaml/_type/function.ts": "ff574fe84a750695302864e1c31b93f12d14ada4bde79a5f93197fc33ad17471", - "https://deno.land/std@0.204.0/yaml/_type/int.ts": "563ad074f0fa7aecf6b6c3d84135bcc95a8269dcc15de878de20ce868fd773fa", - "https://deno.land/std@0.204.0/yaml/_type/map.ts": "7b105e4ab03a361c61e7e335a0baf4d40f06460b13920e5af3fb2783a1464000", - "https://deno.land/std@0.204.0/yaml/_type/merge.ts": "8192bf3e4d637f32567917f48bb276043da9cf729cf594e5ec191f7cd229337e", - "https://deno.land/std@0.204.0/yaml/_type/mod.ts": "060e2b3d38725094b77ea3a3f05fc7e671fced8e67ca18e525be98c4aa8f4bbb", - "https://deno.land/std@0.204.0/yaml/_type/nil.ts": "606e8f0c44d73117c81abec822f89ef81e40f712258c74f186baa1af659b8887", - "https://deno.land/std@0.204.0/yaml/_type/omap.ts": "cfe59a294726f5cea705c39a61fd2b08199cf48f4ccd6b040cb550ec0f38d0a1", - "https://deno.land/std@0.204.0/yaml/_type/pairs.ts": "0032fdfe57558d21696a4f8cf5b5cfd1f698743177080affc18629685c905666", - "https://deno.land/std@0.204.0/yaml/_type/regexp.ts": "1ce118de15b2da43b4bd8e4395f42d448b731acf3bdaf7c888f40789f9a95f8b", - "https://deno.land/std@0.204.0/yaml/_type/seq.ts": "95333abeec8a7e4d967b8c8328b269e342a4bbdd2585395549b9c4f58c8533a2", - "https://deno.land/std@0.204.0/yaml/_type/set.ts": "f28ba44e632ef2a6eb580486fd47a460445eeddbdf1dbc739c3e62486f566092", - "https://deno.land/std@0.204.0/yaml/_type/str.ts": "a67a3c6e429d95041399e964015511779b1130ea5889fa257c48457bd3446e31", - "https://deno.land/std@0.204.0/yaml/_type/timestamp.ts": "706ea80a76a73e48efaeb400ace087da1f927647b53ad6f754f4e06d51af087f", - "https://deno.land/std@0.204.0/yaml/_type/undefined.ts": "94a316ca450597ccbc6750cbd79097ad0d5f3a019797eed3c841a040c29540ba", - "https://deno.land/std@0.204.0/yaml/_utils.ts": "26b311f0d42a7ce025060bd6320a68b50e52fd24a839581eb31734cd48e20393", - "https://deno.land/std@0.204.0/yaml/parse.ts": "1fbbda572bf3fff578b6482c0d8b85097a38de3176bf3ab2ca70c25fb0c960ef", - "https://deno.land/std@0.204.0/yaml/schema.ts": "96908b78dc50c340074b93fc1598d5e7e2fe59103f89ff81e5a49b2dedf77a67", - "https://deno.land/std@0.204.0/yaml/schema/core.ts": "fa406f18ceedc87a50e28bb90ec7a4c09eebb337f94ef17468349794fa828639", - "https://deno.land/std@0.204.0/yaml/schema/default.ts": "0047e80ae8a4a93293bc4c557ae8a546aabd46bb7165b9d9b940d57b4d88bde9", - "https://deno.land/std@0.204.0/yaml/schema/extended.ts": "0784416bf062d20a1626b53c03380e265b3e39b9409afb9f4cb7d659fd71e60d", - "https://deno.land/std@0.204.0/yaml/schema/failsafe.ts": "d219ab5febc43f770917d8ec37735a4b1ad671149846cbdcade767832b42b92b", - "https://deno.land/std@0.204.0/yaml/schema/json.ts": "5f41dd7c2f1ad545ef6238633ce9ee3d444dfc5a18101e1768bd5504bf90e5e5", - "https://deno.land/std@0.204.0/yaml/schema/mod.ts": "4472e827bab5025e92bc2eb2eeefa70ecbefc64b2799b765c69af84822efef32", - "https://deno.land/std@0.204.0/yaml/type.ts": "65553da3da3c029b6589c6e4903f0afbea6768be8fca61580711457151f2b30f", - "https://deno.land/std@0.213.0/fmt/colors.ts": "aeaee795471b56fc62a3cb2e174ed33e91551b535f44677f6320336aabb54fbb", - "https://deno.land/std@0.221.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376", - "https://deno.land/std@0.221.0/encoding/base64.ts": "8ccae67a1227b875340a8582ff707f37b131df435b07080d3bb58e07f5f97807", - "https://deno.land/std@0.221.0/io/types.ts": "acecb3074c730b5ff487ba4fe9ce51e67bd982aa07c95e5f5679b7b2f24ad129", - "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", - "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", - "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", - "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", - "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", - "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", - "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", - "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", - "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", - "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", - "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", - "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", - "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", - "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", - "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", - "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", - "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", - "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", - "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", - "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", - "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", - "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", - "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", - "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", - "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", - "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", - "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", - "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", - "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", - "https://deno.land/std@0.224.0/bytes/concat.ts": "86161274b5546a02bdb3154652418efe7af8c9310e8d54107a68aaa148e0f5ed", - "https://deno.land/std@0.224.0/bytes/copy.ts": "08d85062240a7223e6ec4e2af193ad1a50c59a43f0d86ac3a7b16f3e0d77c028", - "https://deno.land/std@0.224.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "7cd490ae1553c97459bd02de4c3f0a552768a85621949b2366003f3cf84b99d7", - "https://deno.land/std@0.224.0/crypto/_wasm/mod.ts": "e89fbbc3c4722602ff975dd85f18273c7741ec766a9b68f6de4fd1d9876409f8", - "https://deno.land/std@0.224.0/crypto/crypto.ts": "e58d78f3db111a499261dbab037ec78cc89da0516a50e1f0205665980a3417e3", - "https://deno.land/std@0.224.0/crypto/mod.ts": "9148fb70ca3d64977e9487b2002d3b1026e8ad8a2078774b807586ba3c77e3bb", - "https://deno.land/std@0.224.0/crypto/timing_safe_equal.ts": "bc3622b5aec05e2d8b735bf60633425c34333c06cfb6c4a9f102e4a0f3931ced", - "https://deno.land/std@0.224.0/crypto/unstable_keystack.ts": "c2a6f6ed67a4e78745e3c9b490ebb7c12f6066f5c2fe0c69d353961909dc82dd", - "https://deno.land/std@0.224.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376", - "https://deno.land/std@0.224.0/encoding/base64.ts": "dd59695391584c8ffc5a296ba82bcdba6dd8a84d41a6a539fbee8e5075286eaf", - "https://deno.land/std@0.224.0/encoding/base64url.ts": "ef40e0f18315ab539f17cebcc32839779e018d86dea9df39d94d302f342a1713", - "https://deno.land/std@0.224.0/encoding/hex.ts": "6270f25e5d85f99fcf315278670ba012b04b7c94b67715b53f30d03249687c07", - "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", - "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", - "https://deno.land/std@0.224.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", - "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", - "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", - "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", - "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", - "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", - "https://deno.land/std@0.224.0/io/_constants.ts": "3c7ad4695832e6e4a32e35f218c70376b62bc78621ef069a4a0a3d55739f8856", - "https://deno.land/std@0.224.0/io/buf_reader.ts": "aa6d589e567c964c8ba1f582648f3feac45e88ab2e3d2cc2c9f84fd73c05d051", - "https://deno.land/std@0.224.0/io/read_all.ts": "876c1cb20adea15349c72afc86cecd3573335845ae778967aefb5e55fe5a8a4a", - "https://deno.land/std@0.224.0/io/read_lines.ts": "17b96f87dbebc5edb976b3d83dc7713bddb994e9c8cb688012d6c6c26803fb9e", - "https://deno.land/std@0.224.0/io/types.ts": "acecb3074c730b5ff487ba4fe9ce51e67bd982aa07c95e5f5679b7b2f24ad129", - "https://deno.land/std@0.224.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038", - "https://deno.land/std@0.224.0/json/common.ts": "33f1a4f39a45e2f9f357823fd0b5cf88b63fb4784b8c9a28f8120f70a20b23e9", - "https://deno.land/std@0.224.0/jsonc/mod.ts": "1756f094e00894ec27416b4fcccbcf445e73892a83cf1937de3aad7de2d5da7c", - "https://deno.land/std@0.224.0/jsonc/parse.ts": "06fbe10f0bb0cba684f7902bf7de5126b16eb0e5a82220c98a4b86675c7f9cff", - "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", - "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", - "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", - "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", - "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", - "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", - "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", - "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", - "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", - "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", - "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", - "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", - "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", - "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", - "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", - "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", - "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", - "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", - "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", - "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", - "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", - "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", - "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", - "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", - "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", - "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", - "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", - "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", - "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", - "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", - "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", - "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", - "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", - "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", - "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", - "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", - "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", - "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", - "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", - "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", - "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", - "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", - "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", - "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", - "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", - "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", - "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", - "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", - "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", - "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", - "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", - "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", - "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", - "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", - "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", - "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", - "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", - "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", - "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", - "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", - "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", - "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", - "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", - "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", - "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", - "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", - "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", - "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", - "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", - "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", - "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", - "https://deno.land/std@0.224.0/testing/_test_suite.ts": "f10a8a6338b60c403f07a76f3f46bdc9f1e1a820c0a1decddeb2949f7a8a0546", - "https://deno.land/std@0.224.0/testing/bdd.ts": "3e4de4ff6d8f348b5574661cef9501b442046a59079e201b849d0e74120d476b", - "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", - "https://deno.land/std@0.224.0/yaml/_error.ts": "f38cdebdb69cde16903d9aa2f3b8a3dd9d13e5f7f3570bf662bfaca69fef669e", - "https://deno.land/std@0.224.0/yaml/_loader/loader.ts": "bf9e8a99770b59bc887b43ebccea108cbe9146ae32d91f7ce558d62c946d3fe3", - "https://deno.land/std@0.224.0/yaml/_loader/loader_state.ts": "ee216de6040551940b85473c3185fdb7a6f3030b77153f87a6b7f63f82e489ea", - "https://deno.land/std@0.224.0/yaml/_mark.ts": "61097a614857fcebf7b2ecad057916d74c90cd160117a33c9e74bac60457410a", - "https://deno.land/std@0.224.0/yaml/_state.ts": "f3b1c1fd11860302f1f33e35e9ce089bf069d4943e8d67516cd6bedbba058c13", - "https://deno.land/std@0.224.0/yaml/_type/binary.ts": "f1a6e1d83dcc52b21cc3639cd98be44051cfc54065cc4f2a42065bce07ebc07d", - "https://deno.land/std@0.224.0/yaml/_type/bool.ts": "121743b23ba82a27ad6a3ec6298c7f5b0908f90e52707f8644a91f7ad51ed2ef", - "https://deno.land/std@0.224.0/yaml/_type/float.ts": "c5ed84b0aec1ec5dc05f6abfaaff672e8890d4d44a42120b4445c9754fca4eba", - "https://deno.land/std@0.224.0/yaml/_type/function.ts": "bbf705058942bf3370604b37eb77a10aadd72f986c237c9f69b43378a42202c1", - "https://deno.land/std@0.224.0/yaml/_type/int.ts": "c2dc88438a60fccc8d2226042bd18b9967753adaf6bd145feb8b99d567e432ce", - "https://deno.land/std@0.224.0/yaml/_type/map.ts": "ae2acb1cb837fb8e96c75c98611cfd45af847d0114ab5336333c318e7d4b12f4", - "https://deno.land/std@0.224.0/yaml/_type/merge.ts": "ad0d971f91d2fb9f4ab3eba0c837eae357b1804d6b798adc99dc917bc5306b11", - "https://deno.land/std@0.224.0/yaml/_type/mod.ts": "e8929d7b1c969a74f76338d4eb380ef8c4a26cd6441117d521f076b766e9c265", - "https://deno.land/std@0.224.0/yaml/_type/nil.ts": "cbe4387d02d5933322c21b25d8955c5e6228c492e391a6fb82dcf4f498cc421c", - "https://deno.land/std@0.224.0/yaml/_type/omap.ts": "cda915105ab22ba9e1d6317adacee8eec2d8ddaf864cc2f814e3e476946e72c6", - "https://deno.land/std@0.224.0/yaml/_type/pairs.ts": "dd39bb44c1b9abaf6172c63f73350475933151f07e05253b81f7860c9b507177", - "https://deno.land/std@0.224.0/yaml/_type/regexp.ts": "e49eb9e1c9356fd142bc15f7f323820d411fcc537b5ba3896df9a8b812d270a4", - "https://deno.land/std@0.224.0/yaml/_type/seq.ts": "2deffc7f970869bc01a1541b4961d076329a1c2b30b95e07918f3132db7c3fe2", - "https://deno.land/std@0.224.0/yaml/_type/set.ts": "be8a9e7237a7ffc92dfbe7f5e552d84b7eeba60f3f73cc77fc3c59d3506c74ea", - "https://deno.land/std@0.224.0/yaml/_type/str.ts": "88f0a1ba12295520cd57e96cd78d53aa0787d53c7a1c506155f418c496c2f550", - "https://deno.land/std@0.224.0/yaml/_type/timestamp.ts": "277a41a40fb93c3b2b3f5c373bf11b0b7856cc6a7b919e8ea130755e4029edc5", - "https://deno.land/std@0.224.0/yaml/_type/undefined.ts": "9d215953c65740f1764e0bdca021007573473f0c49e087f00d9ff02817ecfc97", - "https://deno.land/std@0.224.0/yaml/_utils.ts": "91bbe28b5e7000b9594e40ff5353f8fe7a7ba914eec917e1202cbaf5ac931c58", - "https://deno.land/std@0.224.0/yaml/parse.ts": "f45278d9ebccb789af4eceeffa5c291e194bcf1fa9aab1b34ff52c2bd4a9d886", - "https://deno.land/std@0.224.0/yaml/schema.ts": "a0f7956d997852b5d1c6564bd73eb7352175cfba439707ac819b65b5a2ec173a", - "https://deno.land/std@0.224.0/yaml/schema/core.ts": "0a37c07710e3df4eb4edc02f4edf623bf8df5af72b34d8a7c0229d0bac2a7043", - "https://deno.land/std@0.224.0/yaml/schema/default.ts": "1367fd30420c7071ecc67e5b470838474e8259aaf64460f314af4b6bd8da497c", - "https://deno.land/std@0.224.0/yaml/schema/extended.ts": "248180c22697f37ed173057eae62ce4879865bb59f30c4908d698bed5edcc7c5", - "https://deno.land/std@0.224.0/yaml/schema/failsafe.ts": "0ac1cae5b86d8fe2c83ad0a17f8adc33106a452b7139f84e4b0bfaee2206730e", - "https://deno.land/std@0.224.0/yaml/schema/json.ts": "a0228a0c0bad7dece17ab848774fcadc2ccb5e51775c2d58d21d486917ba3ba1", - "https://deno.land/std@0.224.0/yaml/schema/mod.ts": "0e1558a4823834f106675e48ddc15338e04f6f18469d1a7d6b3f0e1ab06abcb2", - "https://deno.land/std@0.224.0/yaml/type.ts": "708dde5f20b01cc1096489b7155b6af79a217d585afb841128e78c3c2391eb5c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/ansi.ts": "481e382126922ff686dd664f4bae6bc44df44bc5ed883c0f5f8a8b3776e5a983", - "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/ansi_escapes.ts": "193b3c3a4e520274bd8322ca4cab1c3ce38070bed1898cb2ade12a585dddd7c9", - "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/chain.ts": "eca61b1b64cad7b9799490c12c7aa5538d0f63ac65a73ddb6acac8b35f0a5323", - "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/deps.ts": "f48ae5d066684793f4a203524db2a9fd61f514527934b458006f3e57363c0215", - "https://deno.land/x/cliffy@v1.0.0-rc.4/ansi/ansi.ts": "481e382126922ff686dd664f4bae6bc44df44bc5ed883c0f5f8a8b3776e5a983", - "https://deno.land/x/cliffy@v1.0.0-rc.4/ansi/ansi_escapes.ts": "193b3c3a4e520274bd8322ca4cab1c3ce38070bed1898cb2ade12a585dddd7c9", - "https://deno.land/x/cliffy@v1.0.0-rc.4/ansi/chain.ts": "eca61b1b64cad7b9799490c12c7aa5538d0f63ac65a73ddb6acac8b35f0a5323", - "https://deno.land/x/cliffy@v1.0.0-rc.4/ansi/deps.ts": "4d93b041735f4967fa56c7aeb6be490c07522fb81c419f5fe3bef6b72190db8c", - "https://deno.land/x/is_what@v4.1.15/src/index.ts": "e55b975d532b71a0e32501ada85ae3c67993b75dc1047c6d1a2e00368b789af0", - "https://deno.land/x/libpkgx@v0.14.1/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.14.1/src/deps.ts": "cbed32141c67030c1c2ea035408213585d8e14a808bd85319a7337c6c1c39fbf", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useConfig.ts": "099044d3a7b6ec5805eef13f222a566b251a355ffc7b45c9934d790d953e861e", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useDownload.ts": "fc51347e5e9c5a3e4c9405dfbc96fb60b57d8ec90e41cf89876adf263db54b8e", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useFetch.ts": "c2a3aa7a01c5ab51ceb9f105638ef95fe135a17aa5fd6ad14c543022ad4fa3cc", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useInventory.ts": "62da5e879c4e02e1b25b5621cd659438ca906d5f8775a4dccddc0d2c17a1e961", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useMoustaches.ts": "417aeabd35096fd81c05b71b151b92049add2d3ead5036e0063b8c10ee682863", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useOffLicense.ts": "469cc8ef33f4fd361fbf338df5a2fbc0d2cd62e38a251d0ef2b52d70ea36873d", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/usePantry.ts": "738a783a27f8e1209fd95cfcdb609e953b5ff7d433f5b3334f9664eacba7e7d2", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useShellEnv.ts": "686635512ef0bd7f982982b6ed555f106ffd618b2ba344e47e50ca195181aa70", - "https://deno.land/x/libpkgx@v0.14.1/src/hooks/useSync.ts": "f4afd053f8d814b14d1b5d3fe240c132dbcfdd289e38243999fe6d80d438a9dc", - "https://deno.land/x/libpkgx@v0.14.1/src/plumbing/hydrate.ts": "74f295585c4760005cc7676db9f3d9eb67d92b78e197e02445f42823d1643884", - "https://deno.land/x/libpkgx@v0.14.1/src/plumbing/install.ts": "5f0b96d82bec57a0e1e612a77aed83cfd1ab04bc04a9a374aa23185d8d3d6100", - "https://deno.land/x/libpkgx@v0.14.1/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.14.1/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.14.1/src/plumbing/which.ts": "24778f645c9aaea67df51b671474fffe01c1d3b188552943e24d61d51fa6b80d", - "https://deno.land/x/libpkgx@v0.14.1/src/porcelain/install.ts": "b97860e7c106fe5dac66cd6833729b832d70658561d582ff52b933a4d30718c7", - "https://deno.land/x/libpkgx@v0.14.1/src/porcelain/run.ts": "b7143ac5e1bea4c0852bfdfdf5039aa0e4e7acac2572751303218e3c57710daa", - "https://deno.land/x/libpkgx@v0.14.1/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/Path.ts": "001a723aac860ccbd40bcf06cd64cbba9172894d48d6bb0fa3c53bc96e35be02", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/flock.deno.ts": "29f6bea74f9feb856f7b5b80a2fe58b337955181e2a922255f76c843a92c08e5", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/pkg.ts": "5ef9cc1955a5067489ac4d2af8615652505cb17a48745e811cb786ab89377547", - "https://deno.land/x/libpkgx@v0.14.1/src/utils/semver.ts": "defa0cc3f08059189d47e5c3600c640fdf16cc555c7c81c3b72de5d1f4180307", - "https://deno.land/x/libpkgx@v0.15.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.15.0/src/deps.ts": "6695e9edfc72497bc47ba8e3deed99a5d5f860e6fd2e4081a8e5df30b453cc19", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useConfig.ts": "702f5c10f856f59db04e3bfce52814ae214d0ca2ff51a331e85e84ff573c5566", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useDownload.ts": "62b2043fcbc06a0925eb06fc5ed5f2f2ef3779e8ce10bc44b0163e9bd9b8cbc0", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useInventory.ts": "39b022ee12443bb0bc6c8755eda860a227dba131a439ea0e043bae6132ece34b", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useMoustaches.ts": "417aeabd35096fd81c05b71b151b92049add2d3ead5036e0063b8c10ee682863", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useOffLicense.ts": "9a7a35bd83a8dbb5a25d532e2ab885b5fb77101cc38fad10c5f3ea6a49a24e02", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/usePantry.ts": "7535fb2bb9997fcb08f150381ca713b340c2db4742020908a5dbbe906d630e6a", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.15.0/src/hooks/useSync.ts": "c098321da6c59126a3b8150bcea5e6fc43348c00810ba6db88ce4f5de0fdb556", - "https://deno.land/x/libpkgx@v0.15.0/src/plumbing/hydrate.ts": "74f295585c4760005cc7676db9f3d9eb67d92b78e197e02445f42823d1643884", - "https://deno.land/x/libpkgx@v0.15.0/src/plumbing/install.ts": "19244fb150e80a0bbbed90e4a3f47e2c5aa3e0919ff060cec1db0b789730b560", - "https://deno.land/x/libpkgx@v0.15.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.15.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.15.0/src/plumbing/which.ts": "24778f645c9aaea67df51b671474fffe01c1d3b188552943e24d61d51fa6b80d", - "https://deno.land/x/libpkgx@v0.15.0/src/porcelain/install.ts": "6e41d94226b8eb33a9fbb591e5ddd95e2c5e0c2449cc617ac5f365746d1e4592", - "https://deno.land/x/libpkgx@v0.15.0/src/porcelain/run.ts": "20c60bebeca51daf12d9e5baefe8670844162308734f8a189a1f484c4155d671", - "https://deno.land/x/libpkgx@v0.15.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/Path.ts": "5d5dee2a79168f0d006414629e8adefd41e80439ac74bf4dd898ca3ccc4e42c9", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/flock.deno.ts": "f91792244734befb21c1caa10ac4908694dd37b44f0e9748c78c4f1aa003eb7a", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/pkg.ts": "5ef9cc1955a5067489ac4d2af8615652505cb17a48745e811cb786ab89377547", - "https://deno.land/x/libpkgx@v0.15.0/src/utils/semver.ts": "defa0cc3f08059189d47e5c3600c640fdf16cc555c7c81c3b72de5d1f4180307", - "https://deno.land/x/libpkgx@v0.16.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.16.0/src/deps.ts": "6ce708d6ceed8617b7de363421f478aa48d913dc3d2b9b0fe67c7b9eef779213", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useConfig.ts": "c29d8f9c6dc7b5289c485b3ff37a6e0f04d9a51031813e892015e69afc512a64", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useDownload.ts": "62b2043fcbc06a0925eb06fc5ed5f2f2ef3779e8ce10bc44b0163e9bd9b8cbc0", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useInventory.ts": "39b022ee12443bb0bc6c8755eda860a227dba131a439ea0e043bae6132ece34b", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useOffLicense.ts": "9a7a35bd83a8dbb5a25d532e2ab885b5fb77101cc38fad10c5f3ea6a49a24e02", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/usePantry.ts": "cdbc3bd1abf41eb2d7369fdc6adf1fe9baca0fc8f3df5aac23fa64730972d821", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.16.0/src/hooks/useSync.ts": "c098321da6c59126a3b8150bcea5e6fc43348c00810ba6db88ce4f5de0fdb556", - "https://deno.land/x/libpkgx@v0.16.0/src/plumbing/hydrate.ts": "74f295585c4760005cc7676db9f3d9eb67d92b78e197e02445f42823d1643884", - "https://deno.land/x/libpkgx@v0.16.0/src/plumbing/install.ts": "19244fb150e80a0bbbed90e4a3f47e2c5aa3e0919ff060cec1db0b789730b560", - "https://deno.land/x/libpkgx@v0.16.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.16.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.16.0/src/plumbing/which.ts": "24778f645c9aaea67df51b671474fffe01c1d3b188552943e24d61d51fa6b80d", - "https://deno.land/x/libpkgx@v0.16.0/src/porcelain/install.ts": "6e41d94226b8eb33a9fbb591e5ddd95e2c5e0c2449cc617ac5f365746d1e4592", - "https://deno.land/x/libpkgx@v0.16.0/src/porcelain/run.ts": "20c60bebeca51daf12d9e5baefe8670844162308734f8a189a1f484c4155d671", - "https://deno.land/x/libpkgx@v0.16.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/Path.ts": "72bbb8afa6a40e3cfbf01d4d9069e0818e7abaf0021703a20b0677f06c01e883", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/flock.deno.ts": "f91792244734befb21c1caa10ac4908694dd37b44f0e9748c78c4f1aa003eb7a", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/pkg.ts": "5ef9cc1955a5067489ac4d2af8615652505cb17a48745e811cb786ab89377547", - "https://deno.land/x/libpkgx@v0.16.0/src/utils/semver.ts": "eec31f4345ea483eac7e0fbfdcc0061499077df03c4b6ec7666cd0d29c6cab98", - "https://deno.land/x/libpkgx@v0.17.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.17.0/src/deps.ts": "4315cdd8b0c9eef84e30f73080b8838fb5fcece1ef28f0cc3ae2b6d7ad21eb43", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useConfig.ts": "c29d8f9c6dc7b5289c485b3ff37a6e0f04d9a51031813e892015e69afc512a64", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useDownload.ts": "62b2043fcbc06a0925eb06fc5ed5f2f2ef3779e8ce10bc44b0163e9bd9b8cbc0", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useInventory.ts": "0f47a71f51f752e53e2e32786e68375a314f6fc6bb225cd626a412a83086ebbd", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useOffLicense.ts": "9a7a35bd83a8dbb5a25d532e2ab885b5fb77101cc38fad10c5f3ea6a49a24e02", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/usePantry.ts": "cdbc3bd1abf41eb2d7369fdc6adf1fe9baca0fc8f3df5aac23fa64730972d821", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.17.0/src/hooks/useSync.ts": "c098321da6c59126a3b8150bcea5e6fc43348c00810ba6db88ce4f5de0fdb556", - "https://deno.land/x/libpkgx@v0.17.0/src/plumbing/hydrate.ts": "c75f151ed307532ce9c2bf62c61e6478bb1132f95a11b848e02ea2dec08c2ff3", - "https://deno.land/x/libpkgx@v0.17.0/src/plumbing/install.ts": "19244fb150e80a0bbbed90e4a3f47e2c5aa3e0919ff060cec1db0b789730b560", - "https://deno.land/x/libpkgx@v0.17.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.17.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.17.0/src/plumbing/which.ts": "24778f645c9aaea67df51b671474fffe01c1d3b188552943e24d61d51fa6b80d", - "https://deno.land/x/libpkgx@v0.17.0/src/porcelain/install.ts": "6e41d94226b8eb33a9fbb591e5ddd95e2c5e0c2449cc617ac5f365746d1e4592", - "https://deno.land/x/libpkgx@v0.17.0/src/porcelain/run.ts": "20c60bebeca51daf12d9e5baefe8670844162308734f8a189a1f484c4155d671", - "https://deno.land/x/libpkgx@v0.17.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/Path.ts": "72bbb8afa6a40e3cfbf01d4d9069e0818e7abaf0021703a20b0677f06c01e883", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/flock.deno.ts": "c57b32f656d06140e58886973beec20661f1d0809bf652376acc64969b85942a", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/pkg.ts": "e737cc9a98cd6a2797668c6ef856128692290256a521cc3906bd538410925451", - "https://deno.land/x/libpkgx@v0.17.0/src/utils/semver.ts": "eec31f4345ea483eac7e0fbfdcc0061499077df03c4b6ec7666cd0d29c6cab98", - "https://deno.land/x/libpkgx@v0.18.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.18.0/src/deps.ts": "4315cdd8b0c9eef84e30f73080b8838fb5fcece1ef28f0cc3ae2b6d7ad21eb43", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useConfig.ts": "c29d8f9c6dc7b5289c485b3ff37a6e0f04d9a51031813e892015e69afc512a64", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useDownload.ts": "62b2043fcbc06a0925eb06fc5ed5f2f2ef3779e8ce10bc44b0163e9bd9b8cbc0", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useInventory.ts": "0f47a71f51f752e53e2e32786e68375a314f6fc6bb225cd626a412a83086ebbd", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useOffLicense.ts": "9a7a35bd83a8dbb5a25d532e2ab885b5fb77101cc38fad10c5f3ea6a49a24e02", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/usePantry.ts": "0796cde2c4fca6d3790303af5299a1ac4d83478c0e28465f0820e0e47a703836", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useSync.ts": "591fd302dc3b8bf04a835ea2b8ed5e4558c4ba50a1f52df714e9f84a66acaf27", - "https://deno.land/x/libpkgx@v0.18.0/src/hooks/useSyncCache.ts": "d8cb23f2d9cd6a1baba46a94633be41d3559bdff7f3232e791b752f701ad4257", - "https://deno.land/x/libpkgx@v0.18.0/src/plumbing/hydrate.ts": "c75f151ed307532ce9c2bf62c61e6478bb1132f95a11b848e02ea2dec08c2ff3", - "https://deno.land/x/libpkgx@v0.18.0/src/plumbing/install.ts": "19244fb150e80a0bbbed90e4a3f47e2c5aa3e0919ff060cec1db0b789730b560", - "https://deno.land/x/libpkgx@v0.18.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.18.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.18.0/src/plumbing/which.ts": "f918211e561e56aabf6909e06fa10fa3be06ffebd9e7cc28ce57efef4faff27d", - "https://deno.land/x/libpkgx@v0.18.0/src/porcelain/install.ts": "6e41d94226b8eb33a9fbb591e5ddd95e2c5e0c2449cc617ac5f365746d1e4592", - "https://deno.land/x/libpkgx@v0.18.0/src/porcelain/run.ts": "20c60bebeca51daf12d9e5baefe8670844162308734f8a189a1f484c4155d671", - "https://deno.land/x/libpkgx@v0.18.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/Path.ts": "72bbb8afa6a40e3cfbf01d4d9069e0818e7abaf0021703a20b0677f06c01e883", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/flock.deno.ts": "c57b32f656d06140e58886973beec20661f1d0809bf652376acc64969b85942a", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/pkg.ts": "e737cc9a98cd6a2797668c6ef856128692290256a521cc3906bd538410925451", - "https://deno.land/x/libpkgx@v0.18.0/src/utils/semver.ts": "eec31f4345ea483eac7e0fbfdcc0061499077df03c4b6ec7666cd0d29c6cab98", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/mod.ts": "7ce0a19f9cea3475cc94750ece61c20d857f1c3a279ad38cd029a3f8d9b7b03e", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/src/constants.ts": "85fd27aa6e199093f25f5f437052e16fd0e0870b96ca9b24a98e04ddc8b7d006", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/src/database.ts": "902fe762776cacf049ac0d175a15f6fecf2e93be464f31e3bca4ff302a7a5aee", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/src/ffi.ts": "a3ba0fb636301d6d2c6b6998ce82239b66be59fae7cb1a9859e2e3aaf0582159", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/src/statement.ts": "65d01d6824dac78c44a795657cb0fc3d3ddcd5f32c64e0a3284fcadaa5b092b2", - "https://deno.land/x/libpkgx@v0.18.0/vendor/sqlite3@0.10.0/src/util.ts": "49a936fd7c0483c4baca9d7d7442351931888010af2f4b8de6e95eee29788f98", - "https://deno.land/x/libpkgx@v0.18.1/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.18.1/src/deps.ts": "4315cdd8b0c9eef84e30f73080b8838fb5fcece1ef28f0cc3ae2b6d7ad21eb43", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useConfig.ts": "2ace9d5ac8b6018a0a21c249f737a0708e271409f70b00bb9da24b81d163bcd0", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useDownload.ts": "62b2043fcbc06a0925eb06fc5ed5f2f2ef3779e8ce10bc44b0163e9bd9b8cbc0", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useInventory.ts": "0f47a71f51f752e53e2e32786e68375a314f6fc6bb225cd626a412a83086ebbd", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useOffLicense.ts": "9a7a35bd83a8dbb5a25d532e2ab885b5fb77101cc38fad10c5f3ea6a49a24e02", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/usePantry.ts": "0796cde2c4fca6d3790303af5299a1ac4d83478c0e28465f0820e0e47a703836", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useSync.ts": "591fd302dc3b8bf04a835ea2b8ed5e4558c4ba50a1f52df714e9f84a66acaf27", - "https://deno.land/x/libpkgx@v0.18.1/src/hooks/useSyncCache.ts": "d8cb23f2d9cd6a1baba46a94633be41d3559bdff7f3232e791b752f701ad4257", - "https://deno.land/x/libpkgx@v0.18.1/src/plumbing/hydrate.ts": "c75f151ed307532ce9c2bf62c61e6478bb1132f95a11b848e02ea2dec08c2ff3", - "https://deno.land/x/libpkgx@v0.18.1/src/plumbing/install.ts": "19244fb150e80a0bbbed90e4a3f47e2c5aa3e0919ff060cec1db0b789730b560", - "https://deno.land/x/libpkgx@v0.18.1/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.18.1/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.18.1/src/plumbing/which.ts": "f918211e561e56aabf6909e06fa10fa3be06ffebd9e7cc28ce57efef4faff27d", - "https://deno.land/x/libpkgx@v0.18.1/src/porcelain/install.ts": "6e41d94226b8eb33a9fbb591e5ddd95e2c5e0c2449cc617ac5f365746d1e4592", - "https://deno.land/x/libpkgx@v0.18.1/src/porcelain/run.ts": "20c60bebeca51daf12d9e5baefe8670844162308734f8a189a1f484c4155d671", - "https://deno.land/x/libpkgx@v0.18.1/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/Path.ts": "72bbb8afa6a40e3cfbf01d4d9069e0818e7abaf0021703a20b0677f06c01e883", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/flock.deno.ts": "c57b32f656d06140e58886973beec20661f1d0809bf652376acc64969b85942a", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/pkg.ts": "e737cc9a98cd6a2797668c6ef856128692290256a521cc3906bd538410925451", - "https://deno.land/x/libpkgx@v0.18.1/src/utils/semver.ts": "eec31f4345ea483eac7e0fbfdcc0061499077df03c4b6ec7666cd0d29c6cab98", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/mod.ts": "7ce0a19f9cea3475cc94750ece61c20d857f1c3a279ad38cd029a3f8d9b7b03e", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/src/constants.ts": "85fd27aa6e199093f25f5f437052e16fd0e0870b96ca9b24a98e04ddc8b7d006", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/src/database.ts": "902fe762776cacf049ac0d175a15f6fecf2e93be464f31e3bca4ff302a7a5aee", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/src/ffi.ts": "a3ba0fb636301d6d2c6b6998ce82239b66be59fae7cb1a9859e2e3aaf0582159", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/src/statement.ts": "65d01d6824dac78c44a795657cb0fc3d3ddcd5f32c64e0a3284fcadaa5b092b2", - "https://deno.land/x/libpkgx@v0.18.1/vendor/sqlite3@0.10.0/src/util.ts": "49a936fd7c0483c4baca9d7d7442351931888010af2f4b8de6e95eee29788f98", - "https://deno.land/x/libpkgx@v0.19.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.19.0/src/deps.ts": "d3d34c58358cc7404a0cb4dfdc633d65eb3a14d43d68aef0c544f67866370a34", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useConfig.ts": "d5a02ee7a191fb4a2c3cd1721690ab6cf0338c9680847a9e9c4a6c9ea94df025", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useDownload.ts": "9baaa67b3eb8b6f5b46965b622af5e912d7ff0a6cf0e61eb24a3175d6dd73c64", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useInventory.ts": "f459d819ab676a7e3786522d856b7670e994e4a755b0d1609b53c8b4ebe0c959", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useOffLicense.ts": "1c41ef6882512b67a47fcd1d1c0ce459906d6981a59f6be86d982594a7c26058", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/usePantry.ts": "5e9f6dae8042b2b7635a1e7e4492f15b576d7a735eec7604bb824f7676d14d95", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useShellEnv.ts": "14d29f23b8e256590318cfd5fb840955038429c4cac8f71dfbf8deb46b7d88ec", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useSync.ts": "7ea0ba2fb0825886b311d5c2597aa668691822f4db47d81614bacf01c06366cd", - "https://deno.land/x/libpkgx@v0.19.0/src/hooks/useSyncCache.ts": "d8cb23f2d9cd6a1baba46a94633be41d3559bdff7f3232e791b752f701ad4257", - "https://deno.land/x/libpkgx@v0.19.0/src/plumbing/hydrate.ts": "c75f151ed307532ce9c2bf62c61e6478bb1132f95a11b848e02ea2dec08c2ff3", - "https://deno.land/x/libpkgx@v0.19.0/src/plumbing/install.ts": "3053e94adf214585364b4602d90e3a10c7a3600e4d3aa51ef8e2e526342dee65", - "https://deno.land/x/libpkgx@v0.19.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://deno.land/x/libpkgx@v0.19.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.19.0/src/plumbing/which.ts": "f918211e561e56aabf6909e06fa10fa3be06ffebd9e7cc28ce57efef4faff27d", - "https://deno.land/x/libpkgx@v0.19.0/src/porcelain/install.ts": "85caffe3842ab63bf6d59c6c5c9fb93fbc95a0d5652488d93b95d865722b67b9", - "https://deno.land/x/libpkgx@v0.19.0/src/porcelain/run.ts": "55cc9124dca732e2f5557a8c451daebecb109c86b2f4347fa1e433aedf35ab5a", - "https://deno.land/x/libpkgx@v0.19.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/Path.ts": "72bbb8afa6a40e3cfbf01d4d9069e0818e7abaf0021703a20b0677f06c01e883", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/flock.ts": "c57b32f656d06140e58886973beec20661f1d0809bf652376acc64969b85942a", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/pkg.ts": "e737cc9a98cd6a2797668c6ef856128692290256a521cc3906bd538410925451", - "https://deno.land/x/libpkgx@v0.19.0/src/utils/semver.ts": "979e0a6cb79588004eefb2d64bac95b860bbb6f4e6023bc62b961a52994a195b", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/mod.ts": "7ce0a19f9cea3475cc94750ece61c20d857f1c3a279ad38cd029a3f8d9b7b03e", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/src/constants.ts": "85fd27aa6e199093f25f5f437052e16fd0e0870b96ca9b24a98e04ddc8b7d006", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/src/database.ts": "562c9577b9dea07cab576bd4d249dd22ae4c451a23b98bda66758302f136bb62", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/src/ffi.ts": "ddffcee178b3e72c45be385efd8b4434f7196cafe45a0046ae68df9af307c7f3", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/src/statement.ts": "2be7ffebbb72a031899dbf189972c5596aa73eabfc8a382a1bac9c5c111b0026", - "https://deno.land/x/libpkgx@v0.19.0/vendor/sqlite3@0.10.0/src/util.ts": "49a936fd7c0483c4baca9d7d7442351931888010af2f4b8de6e95eee29788f98", - "https://deno.land/x/libpkgx@v0.20.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://deno.land/x/libpkgx@v0.20.0/src/deps.ts": "4135fc00efbacb68e3700a5498d236f2a70a5e36284954d87aa5d7becc62282d", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useConfig.ts": "d5a02ee7a191fb4a2c3cd1721690ab6cf0338c9680847a9e9c4a6c9ea94df025", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useDownload.ts": "3f9133486008146809508783b977e3480d0a43238ace27f78565fb9679aa9906", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useFetch.ts": "ecf29342210b8eceed216e3bb73fcc7ea5b3ea5059686cf344ed190ca42ff682", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useInventory.ts": "f459d819ab676a7e3786522d856b7670e994e4a755b0d1609b53c8b4ebe0c959", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useMoustaches.ts": "e9166ddace759315782be0f570a4cd63c78e3b85592d59b75ddd33a0e401aa6b", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useOffLicense.ts": "1c41ef6882512b67a47fcd1d1c0ce459906d6981a59f6be86d982594a7c26058", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/usePantry.ts": "5e9f6dae8042b2b7635a1e7e4492f15b576d7a735eec7604bb824f7676d14d95", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useShellEnv.ts": "ae2388d3f15d2e03435df23a8392ace21d3d4f0c83b2575a9670ab7badc389c3", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useSync.ts": "ea605a0eaa43ab9988d36dd6150e16dd911c4be45b7b0f2add6b236636bd517c", - "https://deno.land/x/libpkgx@v0.20.0/src/hooks/useSyncCache.ts": "85375b787fbdd15aa86f3cc6cc12e730869cc475a95d48ea50e69b3b24bb0a04", - "https://deno.land/x/libpkgx@v0.20.0/src/plumbing/hydrate.ts": "c75f151ed307532ce9c2bf62c61e6478bb1132f95a11b848e02ea2dec08c2ff3", - "https://deno.land/x/libpkgx@v0.20.0/src/plumbing/install.ts": "2a4e19fae70fef7ba0be454fd5b7efed4d7d19a5141d26d3b26124ab792007ed", - "https://deno.land/x/libpkgx@v0.20.0/src/plumbing/link.ts": "0ed6198de737ebeab1704d375c732c9264fb0cfa7f2aedddb90f51d100174a73", - "https://deno.land/x/libpkgx@v0.20.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://deno.land/x/libpkgx@v0.20.0/src/plumbing/which.ts": "f918211e561e56aabf6909e06fa10fa3be06ffebd9e7cc28ce57efef4faff27d", - "https://deno.land/x/libpkgx@v0.20.0/src/porcelain/install.ts": "85caffe3842ab63bf6d59c6c5c9fb93fbc95a0d5652488d93b95d865722b67b9", - "https://deno.land/x/libpkgx@v0.20.0/src/porcelain/run.ts": "55cc9124dca732e2f5557a8c451daebecb109c86b2f4347fa1e433aedf35ab5a", - "https://deno.land/x/libpkgx@v0.20.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/Path.ts": "45303993a377363277e6c201160f36f1f9a5997632db03f473b618968d568e58", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/flock.ts": "5fd77f6b53c3a90888cf20a7726e9047aad2c766e4ec2fbf7cf2f916b98d99a4", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/pkg.ts": "e737cc9a98cd6a2797668c6ef856128692290256a521cc3906bd538410925451", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/read-lines.ts": "6d947ccd5f8e48701ed9c5402b6ac5144df3fce60d666f19b6506edbc36c8367", - "https://deno.land/x/libpkgx@v0.20.0/src/utils/semver.ts": "da22a0e0cf74de792cc4d44c01ec5b767463816c8abb4b5fb86583ccda573298", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/mod.ts": "7ce0a19f9cea3475cc94750ece61c20d857f1c3a279ad38cd029a3f8d9b7b03e", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/src/constants.ts": "85fd27aa6e199093f25f5f437052e16fd0e0870b96ca9b24a98e04ddc8b7d006", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/src/database.ts": "49569b0f279cfc3e42730002ae789a2694da74deb212e63a4b4e6640dc4d70ba", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/src/ffi.ts": "ddffcee178b3e72c45be385efd8b4434f7196cafe45a0046ae68df9af307c7f3", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/src/statement.ts": "2be7ffebbb72a031899dbf189972c5596aa73eabfc8a382a1bac9c5c111b0026", - "https://deno.land/x/libpkgx@v0.20.0/vendor/sqlite3@0.10.0/src/util.ts": "19815a492dd8f4c684587238dc20066de11782137de549cd4c9709d1b548247e", - "https://deno.land/x/outdent@v0.8.0/mod.ts": "72630e680dcc36d5ae556fbff6900b12706c81a6fd592345fc98bcc0878fb3ca", - "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", - "https://deno.land/x/plug@1.0.1/deps.ts": "35ea2acd5e3e11846817a429b7ef4bec47b80f2d988f5d63797147134cbd35c2", - "https://deno.land/x/plug@1.0.1/download.ts": "8d6a023ade0806a0653b48cd5f6f8b15fcfaa1dbf2aa1f4bc90fc5732d27b144", - "https://deno.land/x/plug@1.0.1/mod.ts": "5dec80ee7a3a325be45c03439558531bce7707ac118f4376cebbd6740ff24bfb", - "https://deno.land/x/plug@1.0.1/types.ts": "d8eb738fc6ed883e6abf77093442c2f0b71af9090f15c7613621d4039e410ee1", - "https://deno.land/x/plug@1.0.1/util.ts": "5ba8127b9adc36e070b9e22971fb8106869eea1741f452a87b4861e574f13481", - "https://deno.land/x/semver@v1.4.1/mod.ts": "0b79c87562eb8a1f008ab0d98f8bb60076dd65bc06f1f8fdfac2d2dab162c27b", - "https://deno.land/x/sqlite3@0.10.0/deno.json": "23a6c0d74415633fa61af1170cbc711b43876ea6ff7b9bcb65d66b658eae4c6e", - "https://deno.land/x/sqlite3@0.10.0/deps.ts": "f6035f0884a730c0d55b0cdce68846f13bbfc14e8afbf0b3cd4f12a52b4107b7", - "https://deno.land/x/sqlite3@0.10.0/mod.ts": "d41b8b30e1b20b537ef4d78cae98d90f6bd65c727b64aa1a18bffbb28f7d6ec3", - "https://deno.land/x/sqlite3@0.10.0/src/blob.ts": "3681353b3c97bc43f9b02f8d1c3269c0dc4eb9cb5d3af16c7ce4d1e1ec7507c4", - "https://deno.land/x/sqlite3@0.10.0/src/constants.ts": "85fd27aa6e199093f25f5f437052e16fd0e0870b96ca9b24a98e04ddc8b7d006", - "https://deno.land/x/sqlite3@0.10.0/src/database.ts": "85b4db98e1ea9a5c802299513ed169f44f63f6a52dfb18087184413b54d2c539", - "https://deno.land/x/sqlite3@0.10.0/src/ffi.ts": "381b263cd63f7bce367270a945a86afe3826c3362d068da6706912c31c550e26", - "https://deno.land/x/sqlite3@0.10.0/src/statement.ts": "9be952b2cf8e58ae4d6378e693783992a6fbe6c50d20afe5a3fbbec4449f0b2c", - "https://deno.land/x/sqlite3@0.10.0/src/util.ts": "3892904eb057271d4072215c3e7ffe57a9e59e4df78ac575046eb278ca6239cd", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/mod.ts": "14a69905ffad8064444c02d146008efeb6a0ddf0fe543483839af18e01684f5a", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/deps.ts": "cbed32141c67030c1c2ea035408213585d8e14a808bd85319a7337c6c1c39fbf", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useCache.ts": "9f3cc576fabae2caa6aedbf00ab12a59c732be1315471e5a475fef496c1e35ae", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useCellar.ts": "c1e264fcb732423734f8c113fc7cb80c97befe8f13ed9d24906328bc5526c72d", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useConfig.ts": "099044d3a7b6ec5805eef13f222a566b251a355ffc7b45c9934d790d953e861e", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useDownload.ts": "fc51347e5e9c5a3e4c9405dfbc96fb60b57d8ec90e41cf89876adf263db54b8e", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useFetch.ts": "c2a3aa7a01c5ab51ceb9f105638ef95fe135a17aa5fd6ad14c543022ad4fa3cc", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useInventory.ts": "62da5e879c4e02e1b25b5621cd659438ca906d5f8775a4dccddc0d2c17a1e961", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useMoustaches.ts": "417aeabd35096fd81c05b71b151b92049add2d3ead5036e0063b8c10ee682863", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useOffLicense.ts": "469cc8ef33f4fd361fbf338df5a2fbc0d2cd62e38a251d0ef2b52d70ea36873d", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/usePantry.ts": "738a783a27f8e1209fd95cfcdb609e953b5ff7d433f5b3334f9664eacba7e7d2", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useShellEnv.ts": "686635512ef0bd7f982982b6ed555f106ffd618b2ba344e47e50ca195181aa70", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/hooks/useSync.ts": "f4afd053f8d814b14d1b5d3fe240c132dbcfdd289e38243999fe6d80d438a9dc", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/plumbing/hydrate.ts": "74f295585c4760005cc7676db9f3d9eb67d92b78e197e02445f42823d1643884", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/plumbing/install.ts": "5f0b96d82bec57a0e1e612a77aed83cfd1ab04bc04a9a374aa23185d8d3d6100", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/plumbing/link.ts": "acb98bf014414208f14c5f57fa36e2aa66ae9bee69d763120c3ddb656e244063", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/plumbing/resolve.ts": "9425e0d201ee440a8dc011940046f0bb6d94aa29cd738e1a8c39ca86e55aad41", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/plumbing/which.ts": "24778f645c9aaea67df51b671474fffe01c1d3b188552943e24d61d51fa6b80d", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/porcelain/install.ts": "b97860e7c106fe5dac66cd6833729b832d70658561d582ff52b933a4d30718c7", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/porcelain/run.ts": "b7143ac5e1bea4c0852bfdfdf5039aa0e4e7acac2572751303218e3c57710daa", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/types.ts": "dc1a4e6458d11454282f832909838c56f786a26eed54fb8ab5675d6691ebf534", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/Path.ts": "001a723aac860ccbd40bcf06cd64cbba9172894d48d6bb0fa3c53bc96e35be02", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/error.ts": "b0d3130f5cdfc0cc8ea10f93fea0e7e97d4473ddc9bc527156b0fcf24c7b939c", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/flock.deno.ts": "29f6bea74f9feb856f7b5b80a2fe58b337955181e2a922255f76c843a92c08e5", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/host.ts": "3b9e0d4cb05f9bde0ee8bcb0f8557b0a339f6ef56dfb1f08b2cfa63b44db91ee", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/misc.ts": "a4d7944da07066e5dd2ef289af436dc7f1032aed4272811e9b19ceeed60b8491", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/pkg.ts": "5ef9cc1955a5067489ac4d2af8615652505cb17a48745e811cb786ab89377547", - "https://raw.githubusercontent.com/pkgxdev/libpkgx/v0.14.0/src/utils/semver.ts": "eb67414471e209c7fb179365b5d446227119ecbb63e4004c9045a90b5a16329a" - }, - "workspace": { - "dependencies": [ - "jsr:@cliffy/ansi@^1.0.0-rc.7", - "jsr:@cspotcode/outdent@0.8", - "jsr:@std/assert@^1.0.6", - "jsr:@std/fmt@^1.0.2", - "jsr:@std/io@0.225", - "jsr:@std/jsonc@^1.0.1", - "jsr:@std/path@^1.0.6", - "jsr:@std/testing@^1.0.3", - "jsr:@std/yaml@^1.0.5" - ] - } -} diff --git a/docs/README.md b/docs/README.md index 3c7d6340..34ab095d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,33 +1,41 @@ -# What is `pkgx`? +# `pkgx` -`pkgx` is a single, *standalone binary* that can *run anything*. +`pkgx` is a 4MB, *standalone binary* that can *run anything*. + +## Quick Start ```sh -$ pkgx deno@1.33 --version -deno 1.33.4 +brew install pkgxdev/made/pkgx || curl https://pkgx.sh | sh ``` -# Run Anything +[Getting Started Guide](getting-started.md) + +## Using `pkgx` -|

Terminals
Run anything in your terminal

|

Docker
Run anything in Docker

| +|

Run Anything
Run anything with `pkgx`

|

Scripting
Write scripts in any language with all the tools you need available from L:1

| | ----- | ----- | -|

CI/CD
Run anything in CI/CD

|

Scripting
Run anything in scripts

| -|

Editors
Run anything in Editors & IDEs

| +# The `pkgx` Ecosystem -# Using `pkgx` +`pkgx` is more than a package runner, it’s a composable primitive that can be +used to build a whole ecosystem of tools. Here’s what we’ve built so far: -|

Run Anything
Run anything with `pkgx `

|

Shell Integration
Seamlessly add pkgs to your terminal sessions

| -| ----- | ----- | +## `dev` +`dev` uses shellcode and `pkgx` to create “virtual environments” for any +project and any toolset. -# Using `dev` +> [https://github.com/pkgxdev/dev](https://github.com/pkgxdev/dev) -|

Using `dev`
Auto-detect and install project dependencies

| | -| ----- | ----- | +## `pkgm` +`pkgm` installs `pkgx` packages to `/usr/local`. -# Feedback & Support +> [https://github.com/pkgxdev/pkgm](https://github.com/pkgxdev/pkgm) -|

Support
We appreciate you choosing pkgx and we're here to help with any issues you encounter

| | -| ----- | ----- | +## `mash` + +`mash` is a package manager for scripts that use `pkgx` to make the whole +open source ecosystem available to them. + +> [https://github.com/pkgxdev/mash](https://github.com/pkgxdev/mash) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index de606ab6..90aa8d26 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -2,35 +2,34 @@ * [Highlights](README.md) -## Run Anywhere +--- -* [`pkgx` & Terminals](run/anywhere/terminals.md)​ -* [`pkgx` & Scripting](run/anywhere/scripts.md) -* [`pkgx` & Docker](run/anywhere/docker.md) -* [`pkgx` & CI/CD](run/anywhere/ci-cd.md) -* [`pkgx` & Editors](run/anywhere/editors.md) +* [Getting Started](getting-started.md) +## `pkgx` -## Using `pkgx` +* [Running Commands](pkgx.md) +* [Scripting](scripting.md) -* [Run Anything](running-anything.md) -* [Shell Integration](shell-integration.md) -* [`pkgx install`](pkgx-install.md) +## The `pkgx` Ecosystem - -## Using `dev` - -* [`dev`](dev.md) +* [`dev`](https://github.com/pkgxdev/dev) +* [`pkgm`](https://github.com/pkgxdev/pkgm) +* [`mash`](https://github.com/pkgxdev/mash) ​ ## Appendix * [FAQ](faq.md) * Deeper Dives - * [Shellcode Mechanics and User Manual](deeper-dives/shellcode.md) * [How `pkgx` Works: A Conceptual Overview](deeper-dives/conceptual-overview.md) * [Using `pkgx` with a C/C++ Pipeline](deeper-dives/c++.md) * Packaging - * [Contributing pkgs](pantry.md) - * [API](pantry-api.md) -* [Feedback & Support](support.md) + * [Contributing Packages](pkging/pantry.md) + * [API](pkging/pantry-api.md) + +## Linktree + +* [GitHub](https://github.com/pkgxdev/) +* [Discord](https://discord.gg/rNwNUY83XS) +* [𝕏](https://x.com/pkgxdev) diff --git a/docs/deeper-dives/conceptual-overview.md b/docs/deeper-dives/conceptual-overview.md index bc9a92d9..2af2fb79 100644 --- a/docs/deeper-dives/conceptual-overview.md +++ b/docs/deeper-dives/conceptual-overview.md @@ -15,13 +15,13 @@ pkgx node start Is in fact implicitly: ```sh -pkgx +node node start +pkgx +node -- node start ``` Which more precisely† is in fact: ```sh -pkgx +nodejs.org node start +pkgx +nodejs.org -- node start ``` > † see [disambiguation](pkgx-cmd.md#disambiguation) @@ -44,18 +44,8 @@ XDG_DATA_DIRS=~/.pkgx/unicode.org/v71.1.0/share:~/.pkgx/zlib.net/v1.2.13/share:~ DYLD_FALLBACK_LIBRARY_PATH=~/.pkgx/unicode.org/v71.1.0/lib:~/.pkgx/openssl.org/v1.1.1u/lib:~/.pkgx/zlib.net/v1.2.13/lib ``` -This is a composable primitive, and is used by our [shellcode](shellcode.md), -eg. `env +node` is basically just: +This is a composable primitive, you could imagine `pkgx npm start` to be: ```sh -$ eval "$(pkgx +node)" - -$ node --version -Node.js v20.5.0 -``` - -And thus you could imagine `pkgx npm start` to be: - -```sh -env "$(pkgx +node)" npm start +env "$(pkgx +npmjs.org)" npm start ``` diff --git a/docs/deeper-dives/shellcode.md b/docs/deeper-dives/shellcode.md deleted file mode 100644 index 274c11b5..00000000 --- a/docs/deeper-dives/shellcode.md +++ /dev/null @@ -1,53 +0,0 @@ -# `pkgx` Shellcode - -The `pkgx` shellcode is not integrated by default, first study the shellcode -by dumping to your terminal: - -```sh -pkgx --shellcode -``` - -As you can see the shellcode is a handful of shell functions to achieve the -following: - -* a handler for `env +pkg` - * our handler uses pkgx primitives to install and inject the requested pkgs - into the shell session -* a command not found handler - * to suggest `x` invocations for commands we support -* Some `_` support prefixed functions for the above -* We add `~/.local/bin` to your `PATH` - * we configure packages that install things themselves to install things there - -* a change directory hook so `dev` environments can be activated and - deactivated automatically when you change directories - - -## Integrating the Shellcode - -First dry run the integration: - -```sh -pkgx integrate --dry-run -``` - -Then integrate: - -```sh -eval "$(pkgx integrate)" -``` - - -## Deintegrating the Shellcode - -If at any point you want to deintegrate the `pkgx` shellcode type: - -```sh -pkgx deintegrate -``` - - - -# Dive Deeper - -[Shellcode Mechanics and User Manual](shellcode.md) diff --git a/docs/dev.md b/docs/dev.md deleted file mode 100644 index 088b46ce..00000000 --- a/docs/dev.md +++ /dev/null @@ -1,192 +0,0 @@ -# Using `dev` - -{% hint style="danger" %} -`dev` requires shell integration. - -```sh -pkgx integrate --dry-run -``` - -{% endhint %} - -`dev` is a tool for utilizing developer environments. It is built on top of -`pkgx` and the pkgx pkging primitives and automatically determines the packages -you need based on your project’s keyfiles. - -## Getting Started - -`dev` requires `pkgx` to be integrated with your shell. - -```sh -pkgx integrate -``` - -## Activating `dev` Environments - -```sh -$ node --version -command not found: node - -$ ls -package.json … - -$ dev -found package.json; adding `node` to the environment -(+node) $ node --version -v20.5.0 -``` - -Because there’s a `package.json` we know you want `node`. - -If there’s a `.node-version` file we read that: - -```sh -$ cat .node-version -v18 - -$ dev - -(+node=18.16) $ node --version -v18.16.0 -``` - -We understand almost all keyfile conventions. If we don’t understand one you -use [let us know] and we’ll add it. - -{% hint style="info" %} -The environment is only active while your terminal is inside that directory. - -This is persisted across terminal sessions. - -If you want to disable this behavior, deactivate it inside the project -directory: - -```sh -(+node) $ dev off -``` - -{% endhint %} - -{% hint style="info" %} -We even add version control systems like `git`, `svn` and `hg`. -{% endhint %} - -{% hint style="success" %} -Because we read the keyfiles of different project types, use of `dev` is -entirely optional for your users and coworkers. They can either use `dev`, -manually construct the environment with `pkgx` or source their deps themselves. -{% endhint %} - - -## Customizing the Environment - -Projects require specific versions. To facilitate this we allow you to -supplement the project files that indicate tooling. For example in a -`package.json` file: - -```json -{ - "name": "my-project", - "dependencies": […], - "pkgx": "node@16.20.1 imagemagick optipng@0" -} -``` - -In files that support comments we use YAML front matter: - -```toml -# pyproject.toml - -#--- -# pkgx: -# python@3.10 -#--- -``` - -Our preference is comments, JSON doesn’t support comments so we have to stick -a `pkgx` node in there. - -{% hint style="info" %} -We read all the files in your project, but only at the root level. If you move -up a level and it has its own environment you will need to activate that -separately. -{% endhint %} - - -### Overriding Defaults - -Multiple projects can read `package.json`. If you want to use `bun` rather -than `node` just specify that in your `package.json` (or `pkgx.yaml`): - -```json -{ - "name": "my-project", - "dependencies": […], - "pkgx": "bun@0.5" - } -} -``` - -### `pkgx.yaml` - -We supplement the existing files to be less intrusive, but if you prefer you -can instead add a `pkgx.yaml` (or `.pkgx.yaml`) file to your repo. - -The format is the same as that of YAML front matter, thus for example: - -```yaml -dependencies: - python@3.10 node@16.20.1 -env: - FOO: bar -``` - -### Controlling Shell Environment Variables - -It can be convenient to control shell environment variables for work projects. - -```sh -$ cat pyproject.toml - -#--- -# dependencies: -# deno@1.35 -# env: -# DENO_DIR: ./deno -# VERSION: 1.2.3 -#--- - -$ dev - -(+deno+python) $ echo $VERSION -1.2.3 -``` - -{% hint style="info" %} -You can either prefix the YAML with a root `pkgx` node as above or drop -that considering our metadata is universal this seems acceptable, but using -a `pkgx` root is safer. If you use a `pkgx` and you only have deps you can -specify just the deps. We support specification as strings, arrays or -dictionaries so pick the one that feels right to you. -{% endhint %} - -## Adding New Dependencies to an Activated Developer Environment - -Edit the relevant files and `cd .` to trigger the environment to reload. - - -## Using Activated Environments in Editors - -Generally programmer editors should see tools if the environments are -activated. If no, [let us know] and we’ll fix it. - - -## Deactivating `dev` - -```sh -$ dev off -``` - - - -[let us know]: https://github.com/pkgxdev/pkgx/issues/new diff --git a/docs/faq.md b/docs/faq.md index 2e0562f2..570f7c50 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -8,45 +8,27 @@ Typically you want to upgrade `pkgx` so either: 2. `curl -LSsf pkgx.sh | sh` > [!NOTE] -> Indeed! Our installer installs and upgrades too. - -OTOH, `pkgx` packages itself so: - -```sh -pkgx@latest npx@latest cowsay@latest 'fancy a cuppa?' -``` - -Is a valid command, provided you have shell integration. +> Yes. Our installer upgrades `pkgx` too. ## How do I run the latest version of a specific pkg? -Unless otherwise instructed, `pkgx` executes the latest version of pkgs that -*are cached*. The first time you run a pkg the latest version will be -cached, but after that updates will only be fetched if requested. - -```sh -pkgx deno@latest -``` +Unless otherwise instructed, `pkgx` executes the latest version of packages +that *are downloaded*. The first time you run a package the latest version +will be downloaded, but after that updates will only be fetched if requested +or required by other packages. -For us neophiliacs we have written a [`mash`] script to check for newer -versions of what you have cached and fetch them: +For us neophiliacs we have written a [`mash`] script to upgrade your `pkgx` +packages: ```sh pkgx mash pkgx/cache upgrade ``` -> [OSS.app](https://pkgx.app) can automatically install updates. - ## How do I “install” pkgs? -To make pkgs available to the wider system use -[`pkgx install`](pkgx-install.md). - -{% hint style="info" %} -You can update installed packages with `pkgx install foo@latest` -{% endhint %} +Use [`pkgm`](https://github.com/pkgxdev/pkgm). ## What is a package? @@ -59,9 +41,8 @@ A package is: Relative to some other packaging systems: -* No scripts are executed post install -* Packages must work as is from any location provided their deps are installed - in parallel (we say our pkgs are “relocatable“) +* No scripts are executed post “install” +* Packages must work from any location (we say our pkgs are “relocatable“) ## A package version I need is unavailable @@ -71,9 +52,9 @@ Sorry about that. Open a [ticket] asking for it and we’ll build it. [ticket]: https://github.com/pkgxdev/pantry/issues/new -## I need to pin a pkg to greater than v20.1.3 but less than v21 +## I need a pkg greater than v20.1.3 but less than v21 -The commonly used `@` syntax would pin the pkg to v20.1.x (`@20.1.3`). +The commonly used `@` syntax would evaluate to v20.1.x (`@20.1.3`). To provide more control we support the [full semantic version range syntax](https://devhints.io/semver). So for the @@ -94,9 +75,9 @@ Typing `pkgx +deno` dumps the environment to the terminal, if you add additional commands then those commands are invoked in that environment. -## How do I list what packages are cached? +## How do I list what packages are downloaded? -We have created a [`mash`] script to list cached packages: +We have created a [`mash`] script to list everything `pkgx` has downloaded: ```sh pkgx mash pkgx/cache ls @@ -114,11 +95,11 @@ You may need to contribute to the [pantry](pantry.md). ## Where do you put pkgs? -Everything goes in `~/.pkgx`. eg. Deno v1.2.3 installs an independent POSIX -prefix to `~/.pkgx/deno.land/v1.2.3`, thus the `deno` executable is at +Everything goes in `~/.pkgx`. eg. Deno v1.2.3 is an independent POSIX +prefix at `~/.pkgx/deno.land/v1.2.3`, thus the `deno` executable is at `~/.pkgx/deno.land/v1.2.3/bin/deno`. -We also install symlinks for majors, minors and latest: +We also create symlinks for majors, minors and latest: ```sh $ cd ~/.pkgx/deno.land @@ -156,48 +137,32 @@ can use pkgx to use your package automatically. {% endhint %} -## How should I recommend people install my pkg with pkgx? +## How should I recommend people use my pkg with pkgx? ```sh -$ pkgx your-package --args +pkgx your-package --args ``` You can also recommend our shell one-liner if you like: ```sh -sh <(curl https://pkgx.sh) +your-package sh +sh <(curl https://pkgx.sh) your-package --args ``` -Will for example install pkgx and your pkg then open a new shell with it -available to the environment. +This is neat because `pkgx` is *not installed* and it runs your package from a +temporary location making this a very low friction way to try out your +package. +Finally, you can have them try your package out via our Docker image: -## What happened to ”Magic”? - -We removed “magic” from pkgx at v1 because it had a number of unsolvable -issues. If you want it back however fortunately the shellcode is simple: - -```bash -function command_not_found_handle { - pkgx -- "$*" -} -# NOTE in zsh append an `r` ie `command_not_found_handler`` +```sh +docker run pkgxdev/pkgx your-package --args ``` - -## I added a package to the pantry but `pkgx foo` fails - -Try `pkgx --sync foo` to force a pantry sync. Typically this isn’t needed but -this flag can help in confusing situations. - - ## How do I uninstall `pkgx`? -We’ll provide `pkgx uninstall pkgx` at some point, for now: - ```sh -pkgx deintegrate -sudo rm /usr/local/bin/pkgx +sudo rm /usr/local/bin/pkg[xm] rm -rf ~/.pkgx ``` @@ -206,8 +171,8 @@ Then there are a couple platform specific cache/data directories: ### macOS ```sh -rm -rf "${XDG_CACHE_HOME:-$HOME/Library/Caches}/pkgx" -rm -rf "${XDG_DATA_HOME:-$HOME/Library/Application Support}"/pkgx +rm -rf ~/Library/Caches/pkgx +rm -rf ~/Application\ Support/pkgx ``` ### Non macOS @@ -221,7 +186,7 @@ rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}"/pkgx ### Caveats -Though not a problem unique to `pkgx` you should note that tools installed +Though not a problem unique to `pkgx` you should note that tools run with `pkgx` may have polluted your system during use. Check directories like: * `~/.local` @@ -249,21 +214,11 @@ following [semver] syntax: ## Where does `pkgx` store files -* pkgs are cached to `~/.pkgx` (`$PKGX_DIR` overrides) - -* pkg tarballs are cached to - * `~/Library/Caches/pkgx` on Mac - * `~/.cache/pkgx` on *nix - * `%LOCALAPPDATA%/cache/pkgx` on Windows - * ⚠️⚠️`$XDG_CACHE_HOME` overrides on all platforms +* pkgs are downloaded to `~/.pkgx` (`$PKGX_DIR` overrides) * runtime data like the [pantry] is stored in: - * `~/Library/Application Support/pkgx` on Mac - * `~/.local/share/pkgx` on *nix + * `~/Library/Caches/pkgx` on Mac + * `${XDG_CACHE_HOME:-$HOME/.cache}/pkgx` on *nix * `%LOCALAPPDATA%/pkgx` on Windows - * ⚠️⚠️ `$XDG_DATA_HOME` overrides on all platforms - -> If `$XDG_STATE_HOME` is set then `$XDG_STATE_HOME/pkgx` is used for some -> temporary shellcode state. ## What happens if two packages provide the same named program? diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 00000000..828f9708 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,100 @@ +# Getting Started + +1. ## Homebrew + + ```sh + brew install pkgxdev/made/pkgx + ``` + +2. ## cURL Installer + + Our installer both installs and upgrades `pkgx`: + + ```sh + curl -fsS https://pkgx.sh | sh + ``` + + {% hint style='info' %} + Wanna read that script before you run it? + [github.com/pkgxdev/setup/installer.sh][installer] + {% endhint %} + +3. ## Download Manually + + `pkgx` is a standalone binary, so you can just download it directly: + + ```sh + # download it to `./pkgx` + curl -o ./pkgx \ + --compressed --fail --proto '=https' \ + https://pkgx.sh/$(uname)/$(uname -m) + + # install it to `/usr/local/bin/pkgx` + sudo install -m 755 pkgx /usr/local/bin + ``` + + For your convenience we provide a `.tgz` so you can one-liner that: + + ```sh + curl -Ssf https://pkgx.sh/$(uname)/$(uname -m).tgz | sudo tar xz -C /usr/local/bin + ``` + + You can also download straight from [GitHub Releases]. + +4. ## Cargo + + ```sh + cargo install pkgx + ``` + +5. ## Docker + + ```sh + docker run -it pkgxdev/pkgx + + # or, eg. + docker run pkgxdev/pkgx +python@3.10 node@22 start + ``` + + Or in your `Dockerfile`: + + ```Dockerfile + FROM pkgxdev/pkgx + RUN pkgx +node@16 npm start + ``` + + > [hub.docker.com/r/pkgxdev/pkgx](https://hub.docker.com/r/pkgxdev/pkgx) + +6. ## GitHub Actions + + ```yaml + - uses: pkgxdev/setup@v2 + ``` + + > [github.com/pkgxdev/setup](https://github.com/pkgxdev/setup) + + {% hint style='success' %} + `pkgx` makes it easy to consistently use the GNU or BSD versions of core + utilities across different platforms—handy for cross-platform CI/CD + scripts. + + ```sh + pkgx +gnu.org/coreutils ls + ``` + + {% endhint %} + +7. ## Arch Linux + + If you're on Arch Linux (or any of it's derivatives) you can also use the + [`pkgx` AUR] (latest released version) or [`pkgx-git` AUR] (latest + development version, might not be stable). + + {% hint style='warning' %} + The AURs are community-maintained and might be out-of-date. Use them with caution. + {% endhint %} + +[GitHub Releases]: https://github.com/pkgxdev/pkgx/releases +[installer]: https://github.com/pkgxdev/setup/blob/main/installer.sh +[`pkgx` AUR]: https://aur.archlinux.org/packages/pkgx +[`pkgx-git` AUR]: https://aur.archlinux.org/packages/pkgx-git diff --git a/docs/pantry-api.md b/docs/pkging/pantry-api.md similarity index 86% rename from docs/pantry-api.md rename to docs/pkging/pantry-api.md index 26a36477..147e2373 100644 --- a/docs/pantry-api.md +++ b/docs/pkging/pantry-api.md @@ -29,8 +29,9 @@ The [pantry] is our API for pkg metadata. ## libpkgx -We build the cli and the gui on top of [libpkgx]. You may find it useful to -add pkging powers to your apps. +Install and run `pkgx` packages from your apps. + +* [Rust](https://github.com/pkgxdev/pkgx) +* [TypeScript](https://github.com/pkgxdev/libpkgx) [pantry]: https://github.com/pkgxdev/pantry -[libpkgx]: https://github.com/pkgxdev/libpkgx diff --git a/docs/pantry.md b/docs/pkging/pantry.md similarity index 61% rename from docs/pantry.md rename to docs/pkging/pantry.md index 5a4d0cff..716db868 100644 --- a/docs/pantry.md +++ b/docs/pkging/pantry.md @@ -1,17 +1,3 @@ -# Missing Packages - -Firstly it is possible your pantry needs an update: - -```sh -pkgx --sync -``` - -Thus if you were trying to run `foo` but it failed do `pkgx --sync foo` -to see if that helps. - -*Typically this is not needed*—we automatically sync the pantry in most -circumstances that need it. This flag is provided just in case. - # Packaging There’s millions of open source projects and `pkgx` needs your help to package @@ -22,10 +8,11 @@ Visit [github.com/pkgxdev/pantry] for the full documentation. {% endhint %} {% hint style="info" %} -Curious about a specific pkg? `pkgx +brewkit -- pkg edit deno` will open deno’s +Curious about a specific pkg? `pkgx bk edit deno` will open deno’s `package.yml` in your editor. {% endhint %} + # Packagers Who Care You trust us to just work and make your workflows happen. @@ -33,13 +20,16 @@ We take this job seriously and we go the extra mile on a per-package basis, for example: * Our `git` ignores `.DS_Store` files by default -* our RubyGems defaults to user-installs and ensures gems are in `PATH` -* Our `python` comes unversioned so the huge numbers of scripts that invoke `/usr/bin/env python` actually work +* Our RubyGems defaults to user-installs and ensures gems are in `PATH` +* Our `python` comes unversioned so the huge numbers of scripts that invoke + `/usr/bin/env python` actually work * Our `pyenv` automatically installs the python versions it needs Additionally, we insist our pkgs are relocatable, which is why we can install in your home directory (but this also means you could pick up the whole -`~/.pkgx` directory and bundle it with your app.) We also begin packaging new releases almost immediately as soon as they go live using various automations. +`~/.pkgx` directory and bundle it with your app.) We also begin packaging +new releases almost immediately as soon as they go live using various +automations. We care about your developer experience, *not ours*. diff --git a/docs/pkgx-install.md b/docs/pkgx-install.md deleted file mode 100644 index fa5616e7..00000000 --- a/docs/pkgx-install.md +++ /dev/null @@ -1,92 +0,0 @@ -# `pkgx install` - -To install `node`: - -```sh -$ pkgx install node -installed: ~/.local/bin/node -``` - -Installations are just stubs that precisely specify what version of tools you -want: - -```sh -$ cat ~/.local/bin/node -#!/bin/sh -exec pkgx +nodejs.org=20.5.1 node "$@" -``` - -{% hint style="info" %} -Indeed, we only install one file to the system environment. -Packages cache to `~/.pkgx`, if you were to delete `~/.pkgx` the installed shim -would still work. -{% endhint %} - -{% hint style="success" %} -Because `pkgx install` operates via a `pkgx` shim, only the tools you install -become available to the system environment. Open source is complex and tools -can have hundreds of dependencies. With `pkgx` you don’t pollute your system -with these unwanted tools (thus avoiding a myriad of unexpected consequences). - -**All your tools have minimal surface area.** -{% endhint %} - - -## `sudo pkgx install` - -If you invoke `pkgx install` with `sudo` we install to `/usr/local/bin`. - -{% hint style="info" %} -It would be insecure to store the package caches in userland (`~/.pkgx`) -and tools would not work for multiple users anyway; so if you install with -root we also cache the packages (root-owned ofc) in `/usr/local/opt`. -{% endhint %} - - -## Installing the active environment - -As a convenience `pkgx install` (unadorned) installs the active environment. - -```sh -$ env +node - -(+node) $ pkgx install -installed: ~/.local/bin/node - -$ env -node -error: node is installed # installed items are removed from the environment - -$ env +node -error: node is installed - -$ env +node@20 -error: node@20 is installed - -$ env +node@18 -# ^^ this is ok since you didn’t install this version - -(+node@18) $ node --version -v18.17.0 -``` - - -## Controlling Installed Package Versions - -```sh -pkgx install ruff@latest -``` - -Similarly: - -```sh -pkgx install node@16 -``` - - -## `pkgx uninstall` - -```sh -[sudo] pkgx uninstall node -``` - -If you `sudo` installed you will need to `sudo` uninstall. diff --git a/docs/pkgx.md b/docs/pkgx.md new file mode 100644 index 00000000..0cfd18b8 --- /dev/null +++ b/docs/pkgx.md @@ -0,0 +1,128 @@ +# Run Anything + +With `pkgx` it couldn’t be simpler to run anything from the Open Source +ecosystem: + +```sh +$ pkgx openai --version +openai 1.59.6 +``` + +## Run Any Version + +```sh +$ pkgx postgres@12 --version +postgres (PostgreSQL) 12.14 +``` + +{% hint style="info" %} + +### SemVer + +Generally you probably want `@` syntax, but if you need more specificity we +fully support [SemVer]: + +```sh +$ pkgx postgres^12 --version +postgres (PostgreSQL) 12.14 + +$ pkgx "postgres>=12<14" --version +postgres (PostgreSQL) 13.11 + +$ pkgx deno=1.35.3 --version +deno 1.35.3 +``` + +{% endhint %} + +### Running the Latest Version + +`pkgx foo` runs the latest “foo” that **is installed**. + +If you want to ensure the latest version of “foo” is installed, use +`pkgx mash pkgx/upgrade foo`. + + +## Adding Additional Packages to the Execution Environment + +It can be useful to run a program with additional packages in the environment. + +```sh +pkgx +openssl cargo build +``` + +Here `+pkg` syntax added OpenSSL to Cargo’s environment. Thus the build will +see the OpenSSL headers and libraries. + + +## Disambiguation + +In some cases `pkgx foo` may be ambiguous because multiple packages provide +`foo`. + +In such cases `pkgx` will error and ask you be more specific by using +fully-qualified-names: + +```sh +$ pkgx yarn --version +error: multiple projects provide `yarn`. please be more specific: + + pkgx +classic.yarnpkg.com yarn --version + pkgx +yarnpkg.com yarn --version +``` + +In general it's a good idea to specify fully qualified names in +scripts, etc. since you want these to work forever. + + +## Running System Commands + +It can be useful to run system commands with a package environment injected. +To do this either specify the full path of the system executable: + +```sh +pkgx +llvm.org /usr/bin/make +``` + +Or separate your commands with `--`: + +```sh +pkgx +llvm.org -- make # finds `make` in PATH, failing if none found +``` + +{% hint style="warning" %} + +If you only specified `make` rather than `/usr/bin/make` or separating with +`-- make` then `pkgx` would install GNU make for you and use that. + +{% endhint %} + + +## Dumping the Environment + +If you don’t specify anything to run, `pkgx` will install any `+pkg`s and then +dump the environment: + +```sh +$ pkgx +gum +PATH="$HOME/.pkgx/charm.sh/gum/v0.14.5/bin${PATH:+:$PATH}" +``` + +This can be useful in scripts or for adding tools to your shell: + +```sh +$ eval "$(pkgx +gum)" +$ gum --version +gum version 0.14.5 +``` + +For this mode we can also output JSON: `pkgx +gum --json`. + + +## Silent Mode + +```sh +pkgx --silent gum format 'no progress information is output' +``` + +[SemVer]: https://devhints.io/semver diff --git a/docs/run/anywhere/ci-cd.md b/docs/run/anywhere/ci-cd.md deleted file mode 100644 index 683ad867..00000000 --- a/docs/run/anywhere/ci-cd.md +++ /dev/null @@ -1,65 +0,0 @@ -# `pkgx` & CI/CD - -## GitHub Actions - -```sh -- uses: pkgxdev/setup@v1 -- run: pkgx go@1.20 build -``` - -Installs `pkgx` so you can then run `go build` with go version ^1.20. - -```sh -- uses: pkgxdev/setup@v1 - with: - +: node@16 -- run: node --version -``` - -`node` v16 will be available in your job. - -### `dev` - -```sh -- uses: actions/checkout@v3 -- uses: pkgxdev/dev@v1 -``` - -The developer environment for your project will be available during the job. - -## Other CI/CD Providers - -```sh -curl https://pkgx.sh | sh -``` - -`pkgx` will be installed. Use as per general terminal -guidelines, eg: - -```sh -pkgx +node@16 npm start -``` - -{% hint style="warning" %} - -At this time `dev` requires shell integration. - -You can try eg. - -```sh -eval "$(curl https://pkgx.sh)" && dev && npm start -``` - -But in our experience CI/CD environments resist our shell integration. - -{% endhint %} - -{% hint style="success" %} -`pkgx` can make it easy to use the GNU or BSD versions of core utilities -across platforms. - -```sh -pkgx +gnu.org/coreutils ls -``` - -{% endhint %} diff --git a/docs/run/anywhere/docker.md b/docs/run/anywhere/docker.md deleted file mode 100644 index 0d1dc9c0..00000000 --- a/docs/run/anywhere/docker.md +++ /dev/null @@ -1,85 +0,0 @@ -# `pkgx` & Docker - -We provide an image based on Debian Buster (slim) preloaded with `pkgx`: - -```sh -$ docker run -it pkgxdev/pkgx -docker $ env +node@16 -docker $ npm start -``` - -You can use this as a base: - -```Dockerfile -FROM pkgxdev/pkgx -RUN pkgx +node@16 npm start -``` - -Or if you want to use `pkgx` in another image: - -```Dockerfile -FROM archlinux -RUN curl -Ssf --proto '=https' https://pkgx.sh | sh -RUN pkgx +node@16 npm start -``` - -{% hint style="success" %} -We have binaries for Linux aarch64 (arm64) thus Docker on your Apple Silicon -Mac is as fast and easy as deployments. -{% endhint %} - -{% hint style="warning" %} -At this time our shellcode doesn’t work in Docker, but we are working on -making `pkgx` able to be a proxy shell for these situations. -{% endhint %} - - -## `dev` - -`dev` works in our image but because Docker steps are each individual shells -our shellcode only works in that step. Thus: - -```Dockerfile -FROM pkgxdev/pkgx -RUN dev && npm start -``` - -To use `dev` in another image is more complicated but there are a couple -options: - -```Dockerfile -FROM ubuntu - -# Install curl in case it isn't already -RUN apt update && apt upgrade && apt install curl - -RUN eval "$(curl https://pkgx.sh)" && dev && npm start -``` - -{% hint style="info" %} -`eval`ing our installer integrates the shell code into the current shell. -{% endhint %} - -Alternatively you can use `pkgx integrate` provided docker is instructed to -use a more advanced shell like bash: - -```Dockerfile -FROM ubuntu - -SHELL ["/bin/bash", "-c"] - -# Install curl in case it isn't already -RUN apt update && apt upgrade && apt install curl - -RUN curl https://pkgx.sh | sh -RUN pkgx integrate -RUN dev && npm start # you still have to run `dev && ` tho -``` - ---- - -{% hint style="info" %} - -[https://hub.docker.com/r/pkgxdev/pkgx](https://hub.docker.com/r/pkgxdev/pkgx) - -{% endhint %} diff --git a/docs/run/anywhere/editors.md b/docs/run/anywhere/editors.md deleted file mode 100644 index 8fdd406e..00000000 --- a/docs/run/anywhere/editors.md +++ /dev/null @@ -1,20 +0,0 @@ -# `pkgx` & Editors - -For editors like Visual Studio Code, [`dev`](../../dev.md) is enough to have -it see the tools a project may need. - -{% hint style="danger" %} -Make sure you open the project at that directory! -{% endhint %} - -Editors that open from the terminal will inherit the active environment you -added via `pkgx` (`dev` uses `pkgx` to construct environments). - -For other editors and IDEs you will likely need to `sudo pkgx install` to -ensure the tools are available. - - -## Extensions & Plugins - -We intend to create `pkgx` extensions for programmable editors that support -them. Thus tools will automatically be available whatever you are working on. diff --git a/docs/run/anywhere/scripts.md b/docs/run/anywhere/scripts.md deleted file mode 100644 index 20d8ea85..00000000 --- a/docs/run/anywhere/scripts.md +++ /dev/null @@ -1,103 +0,0 @@ -# `pkgx` & Scripting - -## `pkgx` is a “Universal” Interpreter - -```sh -$ pkgx ./script.py -pkgx: running python ./script.py - -$ pkgx ./script.ts -pkgx: running: deno run ./script.ts - -$ head -n1 ./script -#!/usr/bin/ruby -$ pkgx ./script -pkgx: running: ruby ./script -``` - -We read the shebang and install the interpreter before executing the script. -If there is no shebang we use the default interpreter for the file extension. - -## shebangs - -You can use `pkgx` as the [shebang] for your scripts: - -```python -#!/usr/bin/env -S pkgx python@3.9 - -import sys - -print(sys.version) -``` - -```sh -$ chmod +x ./my-script.py -$ ./my-script.py -3.9.17 -``` - -{% hint style="info" %} -Using `env` to invoke `pkgx` is typical for tools that have no POSIX location. - -The `-S` parameter is required to pass multiple arguments. -{% endhint %} - - -### Including Additional pkgs - -Scripts are the glue that allows open source to be composed into powerful new -tools. With our `+pkg` syntax you make anything in open source available to -your script. - -```sh -#!/usr/bin/env -S pkgx +openssl deno run - -Deno.dlopen("libssl.dylib") -``` - -## Shell Scripting - -```sh -#!/bin/sh - -eval "$(pkgx --shellcode)" -# ^^ integrates `pkgx` during this script execution - -env +openai -# ^^ requires integration - -openai --version -``` - -Robustness requires precisely specifying your environment: - -```sh -#!/usr/bin/env -S pkgx bash>=4 - -source <(pkgx --shellcode) -# ^^ bash >=4 is required for this syntax, and eg macOS only comes with bash 3 -``` - -{% hint style="info" %} - -### Super Portable Scripts - -If you like you can use our cURL-installer in your scripts. If `pkgx` is -installed then the script just exits and uses that `pkgx`, if it’s not -installed, it installs pkgx to a **temporary directory** first. - -```sh -#!/bin/sh - -eval "$(curl -Ssf https://pkgx.sh)" - -which pkgx #=> /tmp/pkgx.sh/pkgx -echo $PATH #=> /tmp/pkgx.sh:$PATH - -pkgx +node@16 which node #=> /tmp/pkgx.sh/nodejs.org/v16/bin/node -``` - -{% endhint %} - - -[shebang]: https://en.wikipedia.org/wiki/Shebang_(Unix) diff --git a/docs/run/anywhere/terminals.md b/docs/run/anywhere/terminals.md deleted file mode 100644 index 4c1803e5..00000000 --- a/docs/run/anywhere/terminals.md +++ /dev/null @@ -1,62 +0,0 @@ -# Getting Started - -Installing with [`brew`] is most straight forward: - -```sh -brew install pkgxdev/made/pkgx -``` - -# Other Ways to Install - -1. After [`brew`] our installer is easiest: - -```sh -curl -fsS https://pkgx.sh | sh -``` - -{% hint style='info' %} -Wanna read that script before you run it? [github.com/pkgxdev/setup/installer.sh][installer] -{% endhint %} - -  - -2. `pkgx` is a standalone binary, so (if you want) you can just download it directly: - -```sh -# download it to `./pkgx` -curl -o ./pkgx --compressed -f --proto '=https' https://pkgx.sh/$(uname)/$(uname -m) - -# install it to `/usr/local/bin/pkgx` -sudo install -m 755 pkgx /usr/local/bin - -# check it works -pkgx --help -``` - -For your convenience we provide a `.tgz` so you can one-liner that: - -```sh -curl -Ssf https://pkgx.sh/$(uname)/$(uname -m).tgz | sudo tar xz -C /usr/local/bin -``` - -  - -3. You can also download straight from [GitHub Releases]. - -{% hint style='warning' %} -If you download manually you’ll need to move the binary somewhere in -your `PATH`. -{% endhint %} - -4. If you're on Arch Linux (or any of it's derivatives) you can also use the [`pkgx` AUR] (latest released version) or [`pkgx-git` AUR] (latest development version, might not be stable). - -{% hint style='warning' %} -The AURs are community-maintained and might be out-of-date. Use them with caution. -{% endhint %} - - -[`brew`]: https://brew.sh -[GitHub Releases]: https://github.com/pkgxdev/pkgx/releases -[installer]: https://github.com/pkgxdev/setup/blob/main/installer.sh -[`pkgx` AUR]: https://aur.archlinux.org/packages/pkgx -[`pkgx-git` AUR]: https://aur.archlinux.org/packages/pkgx-git diff --git a/docs/running-anything.md b/docs/running-anything.md deleted file mode 100644 index 2a807ea0..00000000 --- a/docs/running-anything.md +++ /dev/null @@ -1,173 +0,0 @@ -# `pkgx`—pkg runner - -Run anything: - -```sh -$ pkgx openai --version -openai 0.27.8 -``` - -{% hint style="success" %} -Command not found? Command no problem. - -```sh -$ deno run https://example.com/hello-world.ts -^^ type `x` to run that - -$ x -env +deno && deno run https://example.com/hello-world.ts -deno: hello, world! - -$ deno --version -deno 1.36.1 -# ^^ deno is added to the environment for the session duration -``` - -The above requires [shell integration] (run: `pkgx integrate --dry-run`). - -{% endhint %} - - -## Run Any Version - -```sh -$ pkgx postgres@12 --version -postgres (PostgreSQL) 12.14 -``` - -{% hint style="info" %} - -Generally you probably want `@` syntax, but if you need more specificity we -fully support [SemVer]: - -```sh -$ pkgx postgres^12 --version -postgres (PostgreSQL) 12.14 - -$ pkgx "postgres>=12<14" --version -postgres (PostgreSQL) 13.11 - -$ pkgx deno=1.35.3 --version -deno 1.35.3 -``` - -{% endhint %} - -### Running the Latest Version - -`pkgx foo` runs the latest “foo” that **is installed**. - -If you want to ensure the latest version of foo is installed, use -`pkgx foo@latest`. - -{% hint style="info" %} - -Specify `pkgx@latest` to ensure you have the latest `pkgx` installed. - -```sh -$ pkgx@latest npx@latest cowsay@latest 'fancy a cuppa?' - ________________ -< fancy a cuppa? > - ---------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || -``` - -The newer pkgx is installed to `~/.pkgx` like every other pkg. If you need -the installed `pkgx` to be updated then `brew upgrade` or re-run the -installer. - -{% endhint %} - - -## Endpoints - -Some packages provide typical usages via an `endpoint` entry in their [pantry] -entry and can be started via `pkgx +brewkit -- run`. - -These are often used to do the equivalent of a project’s getting -started steps. For example `pkgx +brewkit -- run llama.cpp` downloads the model and launches a -chat interface and `pkgx +brewkit -- run stable-diffusion-webui` launches the web-ui. - - -## Adding Additional Packages to the Execution Environment - -With our shellcode `env +openssl` adds OpenSSL to the shell environment. -When using `pkgx` as a runner you can add additional packages to the execution -environment with the same syntax: - -```sh -$ pkgx +openssl cargo build -pkgx: added ~/.pkgx/openssl.org/v1.1.1 to the execution environment -cargo: Compiling my-rust-ssl-proj -``` - -{% hint style="info" %} -Idiomatically, `-node` can be used to exclude a package that may already -exist in the environment. - -`'-*'` can be used to exclude everything. Note that you typically will need -to quote the asterisk since shells will otherwise interpret it as an attempt -to glob. - -{% endhint %} - -{% hint style="info" %} - -The first argument that doesn’t start with `-` or `+` is considered the -first argument to the runner. All arguments that follow are passed to that -program. - -{% endhint %} - - -## Disambiguation - -In some cases `pkgx foo` may be ambiguous because multiple packages provide -`foo`. - -In such cases `pkgx` will error and ask you be more specific by using -fully-qualified-names : - -```sh -$ pkgx yarn --version -error: multiple projects provide `yarn`. please be more specific: - - pkgx +classic.yarnpkg.com yarn --version - pkgx +yarnpkg.com yarn --version -``` - -In general it's a good idea to specify fully qualified names in -scripts, etc. since you want these to work forever. - - -## Running System Commands - -It can be useful to run system commands with a package environment injected. -To do this either specify the full path of the system executable: - -```sh -pkgx +llvm.org /usr/bin/make -``` - -Or use `--` which is the standard POSIX way to tell tools like `pkgx` to stop -processing args: - -```sh -pkgx +llvm.org -- make # finds `make` in PATH, failing if none found -``` - -{% hint style="warning" %} - -If you only specified `make` rather than `/usr/bin/make` then `pkgx` would -install GNU make for you and use that. - -{% endhint %} - - -[SemVer]: https://devhints.io/semver -[pantry]: pantry.md -[shell integration]: shell-integration.md diff --git a/docs/scripting.md b/docs/scripting.md new file mode 100644 index 00000000..babf11d6 --- /dev/null +++ b/docs/scripting.md @@ -0,0 +1,125 @@ +# `pkgx` & Scripting + +You can use `pkgx` as the [shebang] for your scripts: + +```python +#!/usr/bin/env -S pkgx python@3.9 + +import sys + +print(sys.version) +``` + +```sh +$ chmod +x ./my-script.py +$ ./my-script.py +3.9.17 +``` + +{% hint style="info" %} +Using `env` to invoke `pkgx` is typical for tools that have no POSIX location. + +The `-S` parameter is required to pass multiple arguments. +{% endhint %} + + +## Including Additional pkgs + +Scripts are the glue that allows open source to be composed into powerful new +tools. With our `+pkg` syntax you make anything in open source available to +your script. + +```sh +#!/usr/bin/env -S pkgx +openssl deno run + +Deno.dlopen("libssl.dylib") +``` + +{% hint style="info" %} +Robustness requires precisely specifying your environment: + +```sh +#!/usr/bin/env -S pkgx bash>=4 + +source <(pkgx dev --shellcode) +# ^^ bash >=4 is required for this syntax, and eg macOS only comes with bash 3 +``` + +{% endhint %} + + +## Scripting for Various Languages & Their Dependencies + +### Python + +Use `uv` to import PyPi dependencies: + +```python +#!/usr/bin/env -S pkgx +python@3.11 uv run --script + +# /// script +# dependencies = [ +# "requests<3", +# "rich", +# ] +# /// + +import requests +from rich.pretty import pprint + +resp = requests.get("https://peps.python.org/api/peps.json") +data = resp.json() +pprint([(k, v["title"]) for k, v in data.items()][:10]) +``` + +### Ruby + +Use [Bundler](https://bundler.io): + +```ruby +#!/usr/bin/env -S pkgx ruby@3 + +require 'bundler/inline' + +gemfile do + source 'https://rubygems.org' + gem 'ruby-macho', '~> 3' +end +``` + +### JavaScript & TypeScript + +Use [Deno](https://deno.land): + +```javascript +#!/usr/bin/env -S pkgx deno@2 run + +import fs from "npm:fs"; +``` + +### Rust, Go, C, C++, etc + +Use [Scriptisto]: + +```sh +#!/usr/bin/env -S pkgx +cargo scriptisto + +# snip… type `pkgx scriptisto new cargo` for the rest. +``` + + +## Mash + +We think `pkgx` scripting is so powerful that we made a whole package +manager to show it off. + +> [https://github.com/pkgxdev/mash](https://github.com/pkgxdev/mash) + + +## Other Examples + +We make use of `pkgx` scripting all over our repositories. Check them out! + + +[shebang]: https://en.wikipedia.org/wiki/Shebang_(Unix) +[Scriptisto]: https://github.com/igor-petruk/scriptisto diff --git a/docs/shell-integration.md b/docs/shell-integration.md deleted file mode 100644 index b7307c6f..00000000 --- a/docs/shell-integration.md +++ /dev/null @@ -1,115 +0,0 @@ -# Shell Integration - -## Installing the Shell Integration - -The first time you try to use a command that requires shell integration `pkgx` -will let you know: - -```sh -$ env +node -pkgx: error: shellcode not loaded -pkgx: ^^run: eval "$(pkgx integrate)" -``` - -Integration is minimal. We write one line to your `.shellrc` that adds a few -functions to your shell environment. If you like check it out first: - -```sh -pkgx --shellcode -``` - -If you like what you see then you can see what integrate will do in a dry run: - -```sh -pkgx integrate --dry-run -``` - -And then finally integrate: - -```sh -$ eval "$(pkgx integrate)" - -$ env +node -(+node) $ node --version -Node.js v20.5.0 -``` - -{% hint style="info" %} -`eval`ing the integration means you can immediately start using it, but if -you prefer you can run `pkgx integrate` by itself—it’s just the integration -won’t start working until you start a new terminal session. -{% endhint %} - -{% hint style="warning" %} -Once integrated every terminal session will have `pkgx` integration. -If for some reason you need a session without this integration you can unload: - -```sh -pkgx unload -``` - -{% endhint %} - -{% hint style="info" %} -To deintegrate `pkgx`’s shellcode you can either: - -1. Run `pkgx deintegrate`; or -2. Open your `~/.shellrc` file in an editor and remove our one-liner -{% endhint %} - - -## Using the Shell Integration - -`env +pkg` creates temporary, isolated *package environments* for you to run -commands in. - -```sh -$ env +node -(+node) $ node --version -v20.5.1 -``` - -{% hint style="info" %} -The `(+pkg)` that prefixes your prompt indicates your terminal’s environment -has been supplemented with the named pkgs. -{% endhint %} - -{% hint style="warning" %} -Our shell integration intercepts calls to `env` only if you are trying to -control the package environment. Other uses are forwarded. Since our -integration is shellcode it will only exist at your prompts, not any deeper -like in shell scripts. -{% endhint %} - -Package environments created with `env +pkg` do not persist beyond the current -terminal session. All the same if you need to remove pkgs from your -current session use `env -pkg`. - - -## Creating Environments for Specific Versions - -```sh -$ env +deno@1.33 -(+deno) $ deno --version -deno 1.33.4 -``` - -{% hint style="info" %} -When you create environments the packages you request are installed if -necessary. -{% endhint %} - - -## Supplementing the Environment with Multiple Packages - -```sh -$ env +node@16 +imagemagick +optipng -``` - -Or: - -```sh -$ env +node@16 -$ env +imagemagick -$ env +optipng -``` diff --git a/docs/support.md b/docs/support.md deleted file mode 100644 index f2aa0525..00000000 --- a/docs/support.md +++ /dev/null @@ -1,5 +0,0 @@ -We appreciate your using `pkgx` and would love to help you solve any problems -you may be having. - -* [Discord](https://discord.gg/rNwNUY83XS) -* [x.com/pkgxdev](https://x.com/pkgxdev) (DMs open) diff --git a/entrypoint.ts b/entrypoint.ts deleted file mode 100644 index fe6ffcba..00000000 --- a/entrypoint.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - this file seeks to be the only file that is “impure” - ie. using the global environment to affect the program - */ - -import { render as perror } from "./src/err-handler.ts" -import { setColorEnabled } from "@std/fmt/colors" -import clicolor from "./src/utils/clicolor.ts" - -setColorEnabled(clicolor(Deno.stderr)) - -///////////////////////////////////////////////////////// backwards compatability -const argstr = Deno.args.join(' ') - -if (/--hook(=[a-z]+)?/.test(argstr) || argstr == '--env --keep-going --silent --dry-run=w/trace' || argstr == '-Eds' || argstr.endsWith("/magic -Esk --chaste env")) { - perror('deprecated', 'magic', [ - ['type `pkgx integrate --dry-run` to use our new integrations'] - ], 'https://blog.pkgx.sh/v1') -} -if (parseInt(Deno.env.get("PKGX_LVL")!) >= 10) { - perror('fatal', 'PKGX_LVL >= 10', [], 'https://github.com/orgs/pkgxdev/discussions/11') - Deno.exit(2) -} -if (argstr == '--prefix') { - console.error("%cdeprecated: %cuse ${PKGX_DIR:-$HOME/.pkgx} instead", 'color: red', 'color: initial') - console.log(Deno.env.get("PKGX_DIR") || Deno.env.get("HOME") + "/.pkgx") - Deno.exit(0) -} -if (argstr == 'install' || argstr == 'unload') { - console.error('pkgx: error: shellcode not loaded') - console.error('pkgx: ^^run: eval "$(pkgx integrate)"') - Deno.exit(1) -} - -//////////////////////////////////////////////////////////////////////////// main -import err_handler from "./src/err-handler.ts" -import parse_args from "./src/parse-args.ts" -import { isNumber } from "is-what" -import app from "./src/app.ts" -import { utils } from "pkgx" -const { flatmap } = utils - -try { - const args = parse_args(Deno.args) - - if (args.flags.verbosity === undefined) { - let shv: number | undefined - if (Deno.env.get("DEBUG")) { - args.flags.verbosity = 2 - } else if ((shv = flatmap(Deno.env.get("VERBOSE"), parseInt)) && isNumber(shv)) { - args.flags.verbosity = shv - } else if (Deno.env.get("CI")) { - args.flags.verbosity = -1 // quieter but not silent - } else { - args.flags.verbosity = 0 - } - } - - if (args.flags.verbosity <= -2) { - console.log = () => {} - console.error = () => {} - } - - //FIXME ⌄⌄ can’t figure this out - // deno-lint-ignore no-explicit-any - await app(args as any, logger_prefix()) -} catch (err) { - const code = err_handler(err) - Deno.exit(code) -} - -/////////////////////////////////////////////////////////////////////////// utils -function logger_prefix() { - if (Deno.env.get("CI") || !Deno.stdin.isTerminal()) { - return 'pkgx' - } -} diff --git a/fixtures/.node-version b/fixtures/.node-version deleted file mode 100644 index f274881e..00000000 --- a/fixtures/.node-version +++ /dev/null @@ -1 +0,0 @@ -v16.16.0 diff --git a/fixtures/.ruby-version b/fixtures/.ruby-version deleted file mode 100644 index e4604e3a..00000000 --- a/fixtures/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.2.1 diff --git a/fixtures/.terraform-version b/fixtures/.terraform-version deleted file mode 100644 index 9c6d6293..00000000 --- a/fixtures/.terraform-version +++ /dev/null @@ -1 +0,0 @@ -1.6.1 diff --git a/fixtures/.yarnrc b/fixtures/.yarnrc deleted file mode 100644 index ff41eed2..00000000 --- a/fixtures/.yarnrc +++ /dev/null @@ -1,7 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -# BAR: 1 -# --- diff --git a/fixtures/.yarnrc.yml b/fixtures/.yarnrc.yml deleted file mode 100644 index e797da6d..00000000 --- a/fixtures/.yarnrc.yml +++ /dev/null @@ -1,6 +0,0 @@ -#--- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -#--- diff --git a/fixtures/Cargo.toml b/fixtures/Cargo.toml deleted file mode 100644 index 4a41bdd6..00000000 --- a/fixtures/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -# --- -# pkgx: -# dependencies: -# - zlib.net^1.2 -# env: -# FOO: BAR -# --- - -[package] -name = "pkgx example" -version = "0.0.1" -authors = ["mxcl"] -autobins = false diff --git a/fixtures/Gemfile b/fixtures/Gemfile deleted file mode 100644 index b19bbcbd..00000000 --- a/fixtures/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -# --- -gem 'rails', '5.0.0' -gem 'sqlite3' diff --git a/fixtures/action.yml/empty/action.yml b/fixtures/action.yml/empty/action.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/fixtures/action.yml/not-node/action.yml b/fixtures/action.yml/not-node/action.yml deleted file mode 100644 index 8f3c557c..00000000 --- a/fixtures/action.yml/not-node/action.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Elixir Script -author: Jon Lauridsen -description: Run simple Elixir scripts -branding: - color: orange - icon: code -inputs: - script: - description: The script to run - required: true - debug: - description: Whether to tell the GitHub client to log details of its requests. true or false. Default is to run in debug mode when the GitHub Actions step debug logging is turned on. - default: ${{ runner.debug == '1' }} -outputs: - result: - description: The stringified return value of the script -runs: - using: docker - image: Dockerfile diff --git a/fixtures/action.yml/std/action.yml b/fixtures/action.yml/std/action.yml deleted file mode 100644 index c619cfcd..00000000 --- a/fixtures/action.yml/std/action.yml +++ /dev/null @@ -1,9 +0,0 @@ -runs: - using: 'node16' - main: 'main.js' - -pkgx: - dependencies: - zlib.net^1.2 - env: - FOO: BAR diff --git a/fixtures/bun.lockb b/fixtures/bun.lockb deleted file mode 100644 index e69de29b..00000000 diff --git a/fixtures/cdk.json b/fixtures/cdk.json deleted file mode 100644 index e8ccd943..00000000 --- a/fixtures/cdk.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, - "@aws-cdk/aws-efs:denyAnonymousAccess": true, - "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, - "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, - "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, - "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, - "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, - "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, - "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true - } -} diff --git a/fixtures/deno.json/arr/deno.json b/fixtures/deno.json/arr/deno.json deleted file mode 100644 index 022b1133..00000000 --- a/fixtures/deno.json/arr/deno.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pkgx": ["zlib.net^1.2"] -} diff --git a/fixtures/deno.json/std/deno.json b/fixtures/deno.json/std/deno.json deleted file mode 100644 index 35f2a11e..00000000 --- a/fixtures/deno.json/std/deno.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pkgx": { - "dependencies": { - "zlib.net": "^1.2" - }, - "env": { - "FOO": "BAR" - } - } -} diff --git a/fixtures/deno.jsonc b/fixtures/deno.jsonc deleted file mode 100644 index 35f2a11e..00000000 --- a/fixtures/deno.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pkgx": { - "dependencies": { - "zlib.net": "^1.2" - }, - "env": { - "FOO": "BAR" - } - } -} diff --git a/fixtures/go.mod b/fixtures/go.mod deleted file mode 100644 index 688d5170..00000000 --- a/fixtures/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -// --- -// dependencies: -// zlib.net: ^1.2 -// testing.org: 1.2.3 -// env: -// FOO: BAR -// --- - -module github.com/pkgxdev/go-mod-example - -require ( - github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect - github.com/gorilla/mux v1.6.2 - github.com/sirupsen/logrus v1.2.0 - gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect -) diff --git a/fixtures/package.json/arr/package.json b/fixtures/package.json/arr/package.json deleted file mode 100644 index bfdf5689..00000000 --- a/fixtures/package.json/arr/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pkgx": ["zlib.net^1.2", "deno@1.33"] -} diff --git a/fixtures/package.json/engines/package.json b/fixtures/package.json/engines/package.json deleted file mode 100644 index f6cf017e..00000000 --- a/fixtures/package.json/engines/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "engines": { - "node": "~16.16.1", - "npm": "~9.7.1", - "yarn": "~1.22.10", - "pnpm": "~7.33.7" - } -} - diff --git a/fixtures/package.json/packageManager/package.json b/fixtures/package.json/packageManager/package.json deleted file mode 100644 index 54463826..00000000 --- a/fixtures/package.json/packageManager/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "packageManager": "pnpm@7.33.7+sha256.d1581d46ed10f54ff0cbdd94a2373b1f070202b0fbff29f27c2ce01460427043" -} - diff --git a/fixtures/package.json/std/package.json b/fixtures/package.json/std/package.json deleted file mode 100644 index 35f2a11e..00000000 --- a/fixtures/package.json/std/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pkgx": { - "dependencies": { - "zlib.net": "^1.2" - }, - "env": { - "FOO": "BAR" - } - } -} diff --git a/fixtures/package.json/str/package.json b/fixtures/package.json/str/package.json deleted file mode 100644 index a2b62b54..00000000 --- a/fixtures/package.json/str/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pkgx": "zlib.net^1.2 python.org@latest" -} diff --git a/fixtures/package.json/volta/package.json b/fixtures/package.json/volta/package.json deleted file mode 100644 index 83c70e9b..00000000 --- a/fixtures/package.json/volta/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "volta": { - "node": "16.16.1", - "npm": "9.7.1", - "yarn": "1.22.10", - "pnpm": "7.33.7" - } -} - diff --git a/fixtures/pixi.toml b/fixtures/pixi.toml deleted file mode 100644 index b6e97173..00000000 --- a/fixtures/pixi.toml +++ /dev/null @@ -1,7 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# python.org: '@latest' -# env: -# FOO: BAR -# --- diff --git a/fixtures/pkgx.yml b/fixtures/pkgx.yml deleted file mode 100644 index 3240f75f..00000000 --- a/fixtures/pkgx.yml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - zlib.net: ^1.2 - python.org: latest -env: - FOO: BAR diff --git a/fixtures/pyproject.toml/poetry-yaml-fm/pyproject.toml b/fixtures/pyproject.toml/poetry-yaml-fm/pyproject.toml deleted file mode 100644 index ababfd6c..00000000 --- a/fixtures/pyproject.toml/poetry-yaml-fm/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -# pyproject. toml -# --- -# pkgx: -# python@3.10 -# --- - -[tool-poetry] -name = "magicaitrainer" -version = "0.1.0" -description = -authors = ["jlphilli"] -readme = "README. md" diff --git a/fixtures/pyproject.toml/poetry/pyproject.toml b/fixtures/pyproject.toml/poetry/pyproject.toml deleted file mode 100644 index 6031d921..00000000 --- a/fixtures/pyproject.toml/poetry/pyproject.toml +++ /dev/null @@ -1,13 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -# --- - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool-poetry.dependencies] -python = "3.11.4" diff --git a/fixtures/pyproject.toml/std/pyproject.toml b/fixtures/pyproject.toml/std/pyproject.toml deleted file mode 100644 index 7e7c3058..00000000 --- a/fixtures/pyproject.toml/std/pyproject.toml +++ /dev/null @@ -1,9 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -# --- - -[project] -name = "pkgx example" diff --git a/fixtures/python-version/broken/.python-version b/fixtures/python-version/broken/.python-version deleted file mode 100644 index 86a410dd..00000000 --- a/fixtures/python-version/broken/.python-version +++ /dev/null @@ -1 +0,0 @@ -broken \ No newline at end of file diff --git a/fixtures/python-version/commented/.python-version b/fixtures/python-version/commented/.python-version deleted file mode 100644 index 5de512a5..00000000 --- a/fixtures/python-version/commented/.python-version +++ /dev/null @@ -1,3 +0,0 @@ -# hi - -3.11 diff --git a/fixtures/python-version/std/.python-version b/fixtures/python-version/std/.python-version deleted file mode 100644 index c8cfe395..00000000 --- a/fixtures/python-version/std/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10 diff --git a/fixtures/requirements.txt b/fixtures/requirements.txt deleted file mode 100644 index ff764d56..00000000 --- a/fixtures/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -# --- -# dependencies: -# zlib.net: ^1.2 -# env: -# FOO: BAR -# --- -tensorflow==2.3.1 -uvicorn==0.12.2 -fastapi==0.63.0 diff --git a/fixtures/skaffold.yaml/empty/skaffold.yaml b/fixtures/skaffold.yaml/empty/skaffold.yaml deleted file mode 100644 index 53eb66cf..00000000 --- a/fixtures/skaffold.yaml/empty/skaffold.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v4beta7 -kind: Config -metadata: - name: skaffold-fixture diff --git a/fixtures/skaffold.yaml/manifests/skaffold.yaml b/fixtures/skaffold.yaml/manifests/skaffold.yaml deleted file mode 100644 index 0cf1bcf8..00000000 --- a/fixtures/skaffold.yaml/manifests/skaffold.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v4beta7 -kind: Config -metadata: - name: skaffold-fixture -manifests: - kpt: [] - kustomize: {} - helm: - releases: [] \ No newline at end of file diff --git a/fixtures/skaffold.yaml/std/skaffold.yaml b/fixtures/skaffold.yaml/std/skaffold.yaml deleted file mode 100644 index 14bf384d..00000000 --- a/fixtures/skaffold.yaml/std/skaffold.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v4beta7 -kind: Config -metadata: - name: skaffold-fixture -build: - local: - useDockerCLI: true -deploy: - kubeContext: minikube - kubectl: {} - helm: {} - kpt: {} - docker: - images: [] -manifests: - kpt: [] - kustomize: {} - helm: - releases: [] - -profiles: - - name: skaffold-fixture \ No newline at end of file diff --git a/fixtures/yarn.lock b/fixtures/yarn.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/clean-prefix.sh b/scripts/clean-prefix.sh deleted file mode 100755 index e48c63c1..00000000 --- a/scripts/clean-prefix.sh +++ /dev/null @@ -1,8 +0,0 @@ -for x in ~/.pkgx/*; do - case $(basename "$x") in - deno.land);; - pkgx.sh);; - *) - rm -rf "$x" - esac -done diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index a23a5a8c..f0bf6fe9 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -43,19 +43,26 @@ if [ $v_new = $v_latest ]; then exit 1 fi -gum confirm "prepare draft release for $v_new?" || exit 1 +is_prerelease=$(_is_prerelease $v_new) -git push origin main +if ! gh release view v$v_new >/dev/null 2>&1; then + gum confirm "prepare draft release for $v_new?" || exit 1 -is_prerelease=$(_is_prerelease $v_new) + gh release create \ + v$v_new \ + --draft=true \ + --prerelease=$is_prerelease \ + --generate-notes \ + --notes-start-tag=v$v_latest \ + --title=v$v_new -gh release create \ - v$v_new \ - --draft=true \ - --prerelease=$is_prerelease \ - --generate-notes \ - --notes-start-tag=v$v_latest \ - --title=v$v_new + git push origin main +else + gum format "> existing $v_new draft found, using that" + echo #spacer + + git push origin main +fi gh workflow run cd.yml --raw-field version="$v_new" # ^^ infuriatingly does not tell us the ID of the run diff --git a/scripts/run-coverage.sh b/scripts/run-coverage.sh deleted file mode 100755 index ffaefe5e..00000000 --- a/scripts/run-coverage.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env -S pkgx +genhtml +deno bash - -# TODO use git to determine file changes and open those reports - -set -eo pipefail - -rm -rf cov_profile -deno task test --fail-fast --coverage=cov_profile -deno coverage cov_profile --lcov --exclude=tests/ --output=cov_profile/coverage_file.lcov -genhtml -o cov_profile cov_profile/coverage_file.lcov - -if test "$1" != "--reload"; then - open cov_profile/index.html -else - osascript -e 'tell application "Safari" to tell the current tab of the front window to do JavaScript "location.reload()"' -fi diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index 2e43cf14..00000000 --- a/src/app.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { hooks, Package, PackageRequirement, PackageSpecification, utils } from "pkgx" -import { Logger as IInstallLogger } from "./prefab/install.ts" -import internal_activate from "./modes/internal.activate.ts" -import shell_completion from "./modes/shell-completion.ts" -import parse_pkg_str from "./prefab/parse-pkg-str.ts" -import InstallLogger from "./utils/InstallLogger.ts" -import internal_use from "./modes/internal.use.ts" -import { Args as BaseArgs } from "./parse-args.ts" -import { setColorEnabled } from "@std/fmt/colors" -import { AmbiguityError } from "./utils/error.ts" -import integrate from "./modes/integrate.ts" -import uninstall from "./modes/uninstall.ts" -import shellcode from "./modes/shellcode.ts" -import provider from "./modes/provider.ts" -import clicolor from "./utils/clicolor.ts" -import { blurple } from "./utils/color.ts" -import install from "./modes/install.ts" -import version from "./modes/version.ts" -import Logger from "./utils/Logger.ts" -import help from "./modes/help.ts" -import repl from "./modes/repl.ts" -import env from "./modes/env.ts" -import x from "./modes/x.ts" - -const { usePantry, useSync } = hooks -const { flatmap } = utils - -type Args = BaseArgs & { flags: { verbosity: number } } - -export default async function({ flags, ...opts }: Args, logger_prefix?: string) { - if (flags.sync) { - try { - const logger: Logger = new Logger('sync') - const printer = { - syncing() { - logger.replace('fetching') - }, - caching() { - logger.replace('caching') - }, - syncd() { - logger.clear() - } - } - - await useSync(printer) //TODO Logger - } catch (err) { - if (!flags.keepGoing) throw err - } - } - - const logger = make_logger(flags.verbosity, logger_prefix) - - switch (opts.mode) { - case 'x': { - const { args } = opts - await ensure_pantry() - const {update, ...xopts} = await parse_xopts(opts.pkgs, flags.update) - const pkgs = consolidate(xopts.pkgs) - if (args[0] == 'sh') { - await repl(args.slice(1), { update, pkgs, logger }) - } else { - await x({ args, unknown: opts.unknown, update, pkgs, logger }) - } - } break - case 'integrate': - await integrate('install', opts) - break - case 'internal.use': { - await ensure_pantry() - const xopts = await parse_xopts(opts.pkgs, flags.update) - const rv = await internal_use({ ...xopts, logger }) - if (rv) { - console.log(rv.shellcode) - } - } break - case 'internal.activate': { - await ensure_pantry() - const powder = flatmap(Deno.env.get("PKGX_POWDER"), x => x.split(/\s+/).map(utils.pkg.parse)) ?? [] - const [shellcode, pkgs] = await internal_activate(opts.dir, { powder, logger }) - console.error(`%s %s`, blurple('env'), pkgs.map(x => `+${utils.pkg.str(x)}`).join(' ')) - console.log(shellcode) - if (Deno.env.get("TERM_PROGRAM") == "vscode") { - console.error("pkgx: you may need to: ⌘⇧P workbench.action.reloadWindow") - } - } break - case 'install': - try { - await ensure_pantry() - await install(await Promise.all(opts.args.map(x => parse_pkg_str(x, {latest: 'ok'}))), flags.unsafe) - } catch (err) { - if (err instanceof AmbiguityError) { - err.ctx = 'install' - } - throw err - } - break - case 'uninstall': - await uninstall(opts.args) - break - case 'deintegrate': - await integrate('uninstall', opts) - break - case 'shellcode': - console.log(shellcode()) - break - case 'help': - setColorEnabled(clicolor(Deno.stdout)) - console.log(help(flags.verbosity)) - break - case 'env': { - await ensure_pantry() - const { update, pkgs: xopts } = await parse_xopts(opts.pkgs, flags.update) - const pkgs = consolidate(xopts) - const out = await env({pkgs, update, logger}) - if (out.trim()) { - console.log(out) - } else if (!flags.sync) { - console.error("empty env, try `pkgx --help`") - } - } break - case 'version': - console.log(`pkgx ${version()}`) - break - case 'shell-completion': - console.log(await shell_completion(opts.args).then(x => x.join(" "))) - break - case 'provider': { - const programs = await provider(opts.args) - console.log(programs.join(" ")) - Deno.exit(programs.length ? 0 : 1) - }} -} - -async function ensure_pantry() { - if (usePantry().missing()) { - await useSync() - } -} - -function make_logger(verbosity: number, logger_prefix?: string): IInstallLogger { - const logger = new Logger(logger_prefix) - if (verbosity <= -2 || !Deno.stderr.isTerminal()) { - return { - replace: () => {}, - clear: () => {}, - upgrade: () => undefined - } - } else if (verbosity == -1) { - return { - replace: logger.replace.bind(logger), - clear: logger.clear.bind(logger), - upgrade: () => ({ - installed: ({path}) => { - logger.replace(`${blurple('cached')} ${path}`, {prefix: false}) - } - }) - } - } else { - return { - replace: logger.replace.bind(logger), - clear: logger.clear.bind(logger), - upgrade: function(dry: PackageSpecification[], pending: Package[]) { - return new InstallLogger(dry, pending, logger) - } - } - } -} - -async function parse_xopts(input: { plus: string[], minus: string[] }, update_all: boolean) { - const update: Set = new Set() - - const plus: PackageRequirement[] = [] - for (const promise of input.plus.map(x => parse_pkg_str(x, { latest: 'ok' }))) { - const { update: up, ...pkg } = await promise - plus.push(pkg) - if (up) update.add(pkg.project) - } - - const minus = await Promise.all(input.minus.map(x => parse_pkg_str(x))) - const active = flatmap(Deno.env.get("PKGX_POWDER"), x => x.split(" ").map(utils.pkg.parse)) ?? [] - - const pkgs = { plus, minus, active } - - if (update_all) { - return { pkgs, update: true } - } else if (update.size > 0 ) { - return { pkgs, update } - } else { - return { pkgs, update: false } - } -} - -function consolidate({ plus, minus, active}: { plus: PackageRequirement[], minus: PackageRequirement[], active: PackageRequirement[] }) { - for (const { project } of minus) { - //TODO this is insufficient - active = active.filter(x => x.project != project) - } - return [...active, ...plus] -} diff --git a/src/err-handler.ts b/src/err-handler.ts deleted file mode 100644 index 7cc38cca..00000000 --- a/src/err-handler.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { DownloadError, InstallationNotFoundError, PantryError, PantryParseError, ResolveError, PkgxError, utils } from "pkgx" -import { AmbiguityError, ProgrammerError, ProvidesError } from "./utils/error.ts" -import announce from "./utils/announce.ts" -import { red } from "./utils/color.ts" - -export default function(err: unknown) { - if (err instanceof InstallationNotFoundError) { - const subtitle = utils.pkg.str(err.pkg) - render("not cached", subtitle, [], 'pkg-not-cached') - } else if (err instanceof DownloadError) { - render("download failure", `${err.status}`, [ - ['there was an issue fetching %s', err.src.toString()] - ], 'http-failure') - } else if (err instanceof ResolveError) { - const { platform, arch } = utils.host() - render('version unavailable', utils.pkg.str(err.pkg), [ - ['please check the following url for available versions'], - ['if it’s not there, we’ll build it! open a ticket on the pantry.'] - ], `https://dist.pkgx.dev/?prefix=${err.pkg.project}/${platform}/${arch}`) - } else if (err instanceof PantryParseError) { - //TODO well not if it's a custom edit tho - render('parse error', err.project, [ - ['this is a serious issue. please report the bug'] - ], `https://github.com/pkgxdev/pkgx/issues/new?title=parse+issue+${err.project}`) - } else if (err instanceof PantryError) { - render('pantry error', err.message, [[JSON.stringify(err.ctx)]], 'pantry-error') - } else if (err instanceof AmbiguityError) { - const args = Deno.args.join(' ') - const projects = (() => { - if (err.ctx != 'install') { - return err.projects.map(p => [` %c pkgx +${p} ${args} %c`, 'background-color: black; color: white', 'color: initial']) - } else { - return err.projects.map(p => [` %c pkgx install ${p} %c`, 'background-color: black; color: white', 'color: initial']) - } - })() - - render('multiple projects provide:', err.arg0, [ - ['pls be more specific:'], - [], ...projects, [] - ] - , 'ambiguous-pkgspec') - - } else if (err instanceof ProvidesError) { - render('nothing provides:', err.arg0, [ - ['we haven’t pkgd this yet. %ccan you?', 'font-weight: bold'] - ], 'https://docs.pkgx.sh/pantry') - } else if (err instanceof ProgrammerError) { - render('programmer error', undefined, [['this is a bug, please report it']], 'https://github.com/pkgxdev/pkgx/issues/new') - } else if (err instanceof PkgxError) { - console.error('%c × %s', 'color: red', err.message) - } else if (err instanceof Error) { - const title = 'unexpected error' - const ctx = err.stack?.split('\n').map(x => [x]) ?? [['no stack trace']] - render(title, err.message, ctx, 'https://github.com/pkgxdev/pkgx/issues/new') - } else { - render('unknown error', `${err}`, [['no context']], 'https://github.com/pkgxdev/pkgx/issues/new') - } - return 1 -} - -export function render(title: string, subtitle: string | undefined, body: (string[])[], help: string | undefined) { - announce({ title, subtitle, body, help, color: red }) -} diff --git a/src/modes/env.test.ts b/src/modes/env.test.ts deleted file mode 100644 index d3178dad..00000000 --- a/src/modes/env.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { null_logger as logger } from "../utils/test-utils.ts" -import specimen, { _internals } from "./env.ts" -import { assertEquals } from "@std/assert" -import * as mock from "@std/testing/mock" -import { semver } from "pkgx" -import undent from "outdent" - -Deno.test("env.ts", async () => { - const pkg = { project: "pkg1", constraint: new semver.Range("^1") } - - const stub1 = mock.stub(_internals, "install", () => Promise.resolve({installations: [], pkgenv: []})) - const stub2 = mock.stub(_internals, "construct_env", () => Promise.resolve({ - PATH: "/a", FOO: "bar baz" - })) - - try { - const rv = await specimen({ pkgs: [pkg], update: false, logger }) - assertEquals(rv, undent` - PATH=/a - FOO="bar baz" - `) - } finally { - stub1.restore() - stub2.restore() - } -}); diff --git a/src/modes/env.ts b/src/modes/env.ts deleted file mode 100644 index f8b95204..00000000 --- a/src/modes/env.ts +++ /dev/null @@ -1,21 +0,0 @@ -import escape_if_necessary from "../utils/sh-escape.ts" -import construct_env from "../prefab/construct-env.ts" -import install, { Logger } from "../prefab/install.ts" -import { PackageRequirement } from "pkgx" - -export default async function({ pkgs, ...opts }: { - pkgs: PackageRequirement[], - update: boolean | Set, - logger: Logger -}) { - const { install, construct_env } = _internals - const installations = await install(pkgs, opts) - const env = await construct_env(installations) - return Object.entries(env).map(([key, value]) => - `${key}=${escape_if_necessary(value)}` - ).join("\n") -} - -export const _internals = { - install, construct_env -} diff --git a/src/modes/help.ts b/src/modes/help.ts deleted file mode 100644 index 9312689e..00000000 --- a/src/modes/help.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { dim } from "../utils/color.ts" -import undent from "outdent" - -export default function(verbosity = 0) { - - if (verbosity <= 0) { - // 10| 20| 30| 40| 50| 60| 70| | 80| - return undent` - usage: - pkgx [+pkg@x.y…] [--] [arg…] - - examples: - 05 $ pkgx node@18 --eval 'console.log("pkgx.sh")' - $ pkgx +openssl cargo build - $ pkgx@latest npx@latest cowsay@latest 'fancy a cuppa?' - $ env +bun # https://docs.pkgx.sh/shell-integration - - 10 more: - $ pkgx --help --verbose - $ open https://docs.pkgx.sh - `.replaceAll('$', dim('$')).replaceAll(/#.*/g, dim) - } else { - // 10| 20| 30| 40| 50| 60| 70| | 80| - return undent` - usage: - pkgx [+pkg@x.y…] [--] [arg…] - - • assembles the requested environment, installing packages as necessary - • automatically determines additional packages based on the args - • executes program and args in that environment - - flags: - --sync #synchronize pantry † - --update #update the pkgenv to latest versions w/in constraints - --verbose[=n] #set verbosity ‡ - - #• repetitions override previous values - - #† typically not needed, we automatically synchronize when appropriate - #‡ see VERBOSE - - aliases: - --silent #no chat, no errors, no output; just exit code (--verbose=-2) § - --quiet #minimal chat, errors, & output (--verbose=-1) - - #§ silences pkgx, *not the executed program* - - modes: - install #installs stubs to PATH - uninstall #uninstall stubs - integrate [--dry-run] #integrate with found shells - deintegrate [--dry-run] #deintegrates all shell integrations - - alt. modes: - --help #hi mom! - --version #prints pkgx’s version - --provider #prints provider(s); non-0 exit if none - --shell-completion #prints completions for /prefix.*/ - - environments variables: - PKGX_DIR #cache pkgs here, defaults to ~/.pkgx - VERBOSE #{-2: silent, -1: quietish, 0: default, 1: verbose, 2: debug} - DEBUG #alias for \`VERBOSE=2\` - - #• explicit flags override any environment variables - - environmental influencers: - CI #defaults verbosity to -1 (--quiet) - CLICOLOR #see https://bixense.com/clicolors - `.replaceAll(/#(.*)/g, (_,x) => dim(` ${x}`)) - } -} diff --git a/src/modes/install.ts b/src/modes/install.ts deleted file mode 100644 index 162c846b..00000000 --- a/src/modes/install.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { PackageRequirement, Path, PkgxError, hooks, utils } from "pkgx" -import { is_shebang } from "../utils/get-shebang.ts" -import { blurple, dim } from "../utils/color.ts" -import undent from 'outdent' -const { usePantry } = hooks - -// * maybe impl `$XDG_BIN_HOME` - -export function is_unsafe(unsafe: boolean): boolean { - const env = parseInt(Deno.env.get("PKGX_UNSAFE_INSTALL") || "0") ? true : false; - return env || unsafe -} - -export default async function(pkgs: PackageRequirement[], unsafe: boolean) { - const usrlocal = new Path("/usr/local/bin") - let n = 0 - - try { - await write(usrlocal, pkgs) - } catch (err) { - //FIXME we should check if /usr/local/bin is writable, but were having trouble with that - if (err instanceof Deno.errors.PermissionDenied) { - const bindir = Path.home().join(".local/bin") - await write(bindir, pkgs) - if (n > 0 && !Deno.env.get("PATH")?.split(":").includes(bindir.string)) { - console.warn("pkgx: %c`%s` is not in `PATH`", 'color: red', bindir) - } - } else { - throw err - } - } - - if (n == 0) { - console.error('pkgx: no programs provided by pkgs') - } - - async function write(dst: Path, pkgs: PackageRequirement[]) { - const UNSAFE = is_unsafe(unsafe) - for (const pkg of pkgs) { - const programs = await usePantry().project(pkg).provides() - program_loop: - for (const program of programs) { - - // skip for now since we would require specific versions and we haven't really got that - if (program.includes("{{")) continue - - const pkgstr = utils.pkg.str(pkg) - - let script = "" - - if (UNSAFE) { - const config = hooks.useConfig() - const pkgdir = pkgstr.split("/").slice(0, -1).join("/") - config.cache.join(`pkgx/envs/${pkgdir}`).mkdir("p") - //FIXME: doing `set -a` clears the args env - script = undent` - #MANAGED BY PKGX - if [ "$PKGX_UNINSTALL" != 1 ]; then - ARGS="$*" - ENV_FILE="$\{XDG_CACHE_DIR:-$HOME/.cache\}/pkgx/envs/${pkgstr}.env" - PKGX_DIR="$\{PKGX_DIR:-$HOME/.pkgx\}" - - pkgx_resolve() { - mkdir -p "$(dirname "$ENV_FILE")" - pkgx +${pkgstr} 1>"$ENV_FILE" - run - } - run() { - if test -e "$ENV_FILE" && test -e "$PKGX_DIR/${pkgstr}/v*/bin/${program}"; then - set -a - # shellcheck source=$ENV_FILE - . "$ENV_FILE" - set +a - exec "$PKGX_DIR/${pkgstr}/v*/bin/${program}" "$ARGS" - else - pkgx_resolve - fi - } - run - else - cd "$(dirname "$0")" || exit - rm ${programs.join(" ")} && echo "uninstalled: ${pkgstr}" >&2 - fi` - } else { - script = undent` - if [ "$PKGX_UNINSTALL" != 1 ]; then - exec pkgx +${pkgstr} -- ${program} "$@" - else - cd "$(dirname "$0")" - rm -f ${programs.map(p => `'${p}'`).join(' ')} && echo "uninstalled: ${pkgstr}" >&2 - fi` - } - - const f = dst.mkdir('p').join(program) - - if (f.exists()) { - if (!f.isFile()) throw new PkgxError(`${f} already exists and is not a file`) - - const fd = is_shebang(f).catch(() => undefined) - if (!fd) throw new PkgxError(`${f} already exists and is not a pkgx installation`) - - const lines = f.readLines() - const { value: shebang } = await lines.next() - if (shebang != "#!/bin/sh") { - throw new PkgxError(`${f} already exists and is not a pkgx installation`) - } - while (true) { - const { value, done } = await lines.next() - if (done) { - throw new PkgxError(`${f} already exists and is not a pkgx installation`) - } - const found = value.match(/^\s*exec pkgx \+([^ ]+)/)?.[1] - const unsafe_found = value.match(/#MANAGED BY PKGX/); - if (found) { - if (found != pkgstr) { - throw new PkgxError(`different version already installed: ${blurple(program)} ${dim(`(${found})`)}`) - } - n++ - console.warn(`pkgx: already installed: ${blurple(program)} ${dim(`(${found})`)}`) - continue program_loop - } else if (unsafe_found) { - n++ - console.warn(`pkgx: already install: ${blurple(program)} ${dim("(UNSAFE)")}`); - continue program_loop - } - } - } - - f.write({ force: true, text: undent` - #!/bin/sh - ${script}` - }).chmod(0o755) - console.error('pkgx: installed:', f) - n++ - } - } - } -} diff --git a/src/modes/integrate.test.ts b/src/modes/integrate.test.ts deleted file mode 100644 index 1b9ef79d..00000000 --- a/src/modes/integrate.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -import { afterEach, beforeEach, describe, afterAll, it } from "@std/testing/bdd" -import specimen0, { _internals } from "./integrate.ts" -import { assertRejects } from "@std/assert" -import * as mock from "@std/testing/mock" -import { isString } from "is-what" -import { Path } from "pkgx" - -describe("integrate.ts", () => { - let file: Path - let stub1: mock.Stub - - const specimen = async (op: any) => { - await specimen0(op, {dryrun: false}) - await specimen0(op, {dryrun: true}) - } - - const stub2 = mock.stub(_internals, "stderr", () => {}) - const stub3 = mock.stub(_internals, "stdout", x => isString(x)) - - beforeEach(() => { - file = Path.mktemp().join(".bash_profile") - stub1 = mock.stub(_internals, "home", () => file.parent()) - }) - - afterEach(() => { - file.parent().rm({recursive: true}) - stub1.restore() - }) - - afterAll(() => { - stub2.restore() - stub3.restore() - }) - - it("empty", async function() { - file.touch() - - await specimen("install") - await specimen("install") - await specimen("uninstall") - await specimen("uninstall") - }); - - it("not empty", async function() { - file.write({text: "# hi\n"}) - - await specimen("install") - await specimen("uninstall") - }) - - describe("just newlines", function() { - for (let i = 1; i < 5; ++i) { - it (`${i} newlines`, async function() { - file.write({text: "\n"}) - - await specimen("install") - await specimen("uninstall") - }) - } - }) - - it("no files", { - ignore: Deno.build.os != "darwin", // runs on darwin - }, async function() { - file.rm() - await specimen("install") - await specimen("uninstall") - }) - - it("no files", { - ignore: Deno.build.os == "darwin", // runs on linux - }, async () => { - await assertRejects(() => specimen("install")) - }) - - it("isatty", async function() { - const stub = mock.stub(_internals, "isatty", () => true) - try { - file.touch() - await specimen("install") - } finally { - stub.restore() - } - }) -}) diff --git a/src/modes/integrate.ts b/src/modes/integrate.ts deleted file mode 100644 index 18421fb3..00000000 --- a/src/modes/integrate.ts +++ /dev/null @@ -1,131 +0,0 @@ -import readLines from "pkgx/utils/read-lines.ts" -import { Path, PkgxError, utils } from "pkgx" -import announce from "../utils/announce.ts" -import shellcode from "./shellcode.ts" -import { writeAll } from "@std/io" -import { readAll } from "@std/io" -const { flatmap, host } = utils - -//TODO could be a fun efficiency excercise to maintain a separate write file-pointer -//FIXME assumes unix line-endings - -export default async function(op: 'install' | 'uninstall', { dryrun }: { dryrun: boolean }) { - let opd_at_least_once = false - const encode = (e => e.encode.bind(e))(new TextEncoder()) - - const fopts = { read: true, ...dryrun ? {} : {write: true, create: true} } - - here: for (const [file, line] of shells()) { - const fd = await Deno.open(file.string, fopts) - try { - let pos = 0 - for await (const readline of readLines(fd)) { - if (readline.trim().endsWith('#docs.pkgx.sh/shellcode')) { - if (op == 'install') { - _internals.stderr("hook already integrated:", file) - continue here - } else if (op == 'uninstall') { - // we have to seek because readLines is buffered and thus the seek pos is probs already at the file end - await fd.seek(pos + readline.length + 1, Deno.SeekMode.Start) - const rest = await readAll(fd) - - if (!dryrun) await fd.truncate(pos) // deno has no way I can find to truncate from the current seek position - await fd.seek(pos, Deno.SeekMode.Start) - if (!dryrun) await writeAll(fd, rest) - - opd_at_least_once = true - _internals.stderr("removed hook:", file) - - continue here - } - } - - pos += readline.length + 1 // the +1 is because readLines() truncates it - } - - if (op == 'install') { - const byte = new Uint8Array(1) - if (pos) { - await fd.seek(0, Deno.SeekMode.End) // potentially the above didn't reach the end - while (true && pos > 0) { - await fd.seek(-1, Deno.SeekMode.Current) - await fd.read(byte) - if (byte[0] != 10) break - await fd.seek(-1, Deno.SeekMode.Current) - pos -= 1 - } - - if (!dryrun) await writeAll(fd, encode(`\n\n${line} #docs.pkgx.sh/shellcode\n`)) - } - opd_at_least_once = true - _internals.stderr(`${file} << \`${line}\``) - } - } finally { - fd.close() - } - } - if (dryrun && opd_at_least_once) { - const instruction = op == 'install' ? 'eval "$(pkgx integrate)"' : 'pkgx deintegrate' - _internals.stderr() - announce({ - title: 'this was a dry-run', - subtitle: 'to actually perform the above, run:', - body: [[],[` ${instruction}`],[]], - help: 'https://docs.pkgx.sh/shell-integration' - }) - } else switch (op) { - case 'uninstall': - if (!opd_at_least_once) { - _internals.stderr("nothing to deintegrate found") - } - break - case 'install': - if (!_internals.isatty(Deno.stdout)) { - // we're being sourced, output the hook - _internals.stdout(shellcode()) - } else if (opd_at_least_once) { - _internals.stderr("%crestart your terminal%c for `pkgx` hooks to take effect", 'color: #5f5fff', 'color: initial') - } - } -} - -function shells(): [Path, string][] { - const { home, host } = _internals - - const zdotdir = flatmap(Deno.env.get("ZDOTDIR"), Path.abs) ?? home() - //const xdg_dir = flatmap(Deno.env.get("XDG_CONFIG_HOME"), Path.abs) ?? _internals.home().join(".config") - - const std = (_shell: string) => `source <(pkgx --shellcode)` - - const bash = 'eval "$(pkgx --shellcode)"' - const zshpair: [Path, string] = [zdotdir.join(".zshrc"), std("zsh")] - - const candidates: [Path, string][] = [ - zshpair, - [home().join(".bashrc"), bash], - [home().join(".bash_profile"), bash], - // [xdg_dir.join("elvish/rc.elv"), std("elvish")], - // [xdg_dir.join("fish/config.fish"), "pkgx --hook | source"], - ] - - const viable_candidates = candidates.filter(([file]) => file.exists()) - - if (viable_candidates.length == 0) { - if (host().platform == 'darwin') { - /// macOS has no .zshrc by default and we want mac users to get a just works experience - return [zshpair] - } else { - throw new PkgxError("no `.shellrc` files found") - } - } - - return viable_candidates -} - -export const _internals = { - home: Path.home, - host, - isatty: (x: {isTerminal: () => boolean}) => x.isTerminal(), - stdout: console.log, - stderr: console.error -} diff --git a/src/modes/internal.activate.test.ts b/src/modes/internal.activate.test.ts deleted file mode 100644 index b164b641..00000000 --- a/src/modes/internal.activate.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// deno-lint-ignore-file require-await no-explicit-any -import { fixturesd, null_logger as logger } from "../utils/test-utils.ts" -import { _internals as _devenv_internals } from "../utils/devenv.ts" -import specimen0, { _internals } from "./internal.activate.ts" -import { assertEquals, assertRejects } from "@std/assert" -import * as mock from "@std/testing/mock" -import { SemVer, Path, utils, semver } from "pkgx" - -Deno.test("internal.activate.ts", async runner => { - - const specimen = (path: Path) => specimen0(path, { logger, powder: [] }) - - const stub1 = mock.stub(_internals, "install", async () => { - const installations = [{ - pkg: { project: "nodejs.org", version: new SemVer("20.1.2") }, path: new Path("/opt/nodejs.org/v20.1.2"), - }] - return { installations, pkgenv: [{ project: "nodejs.org", constraint: new semver.Range("^20") }] } - }) - const stub2 = mock.stub(_internals, "construct_env", () => Promise.resolve({ - PATH: "/opt/nodejs.org/v20.1.2/bin", - FOO: "BAR" - })) - const stub3 = mock.stub(_internals, "datadir", () => Path.mktemp()) - const stub4 = mock.stub(_devenv_internals, "find", pkg => utils.pkg.parse(pkg) as any) - - try { - await runner.step("happy", async () => { - await specimen(fixturesd) - }) - - await runner.step("no dir", async () => { - await assertRejects(() => specimen(new Path("/a/b/c/pkgx"))) - }) - - await runner.step("forbidden dirs", async () => { - await assertRejects(() => specimen(Path.root)) - await assertRejects(() => specimen(Path.home())) - }) - - await runner.step("no devenv", async () => { - await assertRejects(() => specimen(Path.root.join("usr"))) - }) - - await runner.step("existing env matches", async () => { - const stub = mock.stub(_internals, "getenv", key => { - if (key == 'FOO' || key == 'PS1') { - return 'BAR' - } else { - return Deno.env.get(key) - } - }) - try { - await specimen(fixturesd) - } finally { - stub.restore() - } - }) - - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - } - - await runner.step("coverage++", () => { - _internals.datadir() - }) - - await runner.step("apply userenv", () => { - const userenv = { PATH: "/foo/bar:$PATH" } - const env = { PATH: "/baz:$PATH"} - _internals.apply_userenv(env, userenv) - assertEquals(env.PATH, "/foo/bar:/baz:$PATH") - }) -}) diff --git a/src/modes/internal.activate.ts b/src/modes/internal.activate.ts deleted file mode 100644 index 22feded9..00000000 --- a/src/modes/internal.activate.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { PackageRequirement, Path, PkgxError, hooks, utils } from "pkgx" -import escape_if_necessary from "../utils/sh-escape.ts" -import construct_env from "../prefab/construct-env.ts" -import install, { Logger } from "../prefab/install.ts" -import { blurple } from "../utils/color.ts" -import devenv from "../utils/devenv.ts" -import undent from "outdent" - -export default async function(dir: Path, { powder, ...opts }: { powder: PackageRequirement[], logger: Logger }) { - const { install, construct_env, datadir, getenv, apply_userenv } = _internals - - if (!dir.isDirectory()) { - throw new PkgxError(`not a directory: ${dir}`) - } - if (dir.eq(Path.home()) || dir.eq(Path.root)) { - throw new PkgxError(`refusing to activate: ${dir}`) - } - - const { pkgs, env: userenv } = await devenv(dir) - - const devenv_pkgs = [...pkgs] - pkgs.push(...powder) - - if (pkgs.length <= 0 && Object.keys(userenv).length <= 0) { - throw new PkgxError("no env") - } - - /// indicate to our shell scripts that this devenv is activated - const persistence = datadir().join("dev", dir.string.slice(1)).mkdir('p').join("dev.pkgx.activated").touch() - - const installations = await install(pkgs, { update: false, ...opts }) - const env = await construct_env(installations) - - /// we only want to tell the user about NEW packages added to the env - const rv_pkgenv = (installed => { - const set = new Set(devenv_pkgs.map(({project}) => project)) - return installed.filter(x => set.has(x.project)) - })(installations.pkgenv) - - // substitute or replace calculated env with user-supplied env from the keyfiles - apply_userenv(env, userenv) - - let rv = '' - for (const [key, value] of Object.entries(env)) { - const existing_value = getenv(key) - if (value == existing_value) { - delete env[key] - continue - } - - //NOTE strictly env which we model ourselves on does not do value escaping which results in output - // that cannot be sourced if the value contains spaces - rv += `export ${key}=${escape_if_necessary(value)}\n` - } - - // if (/\(pkgx\)/.test(getenv("PS1") ?? '') == false) { - // //FIXME doesn't work with warp.dev for fuck knows why reasons - // // https://github.com/warpdotdev/Warp/issues/3492 - // rv += `export PS1="(pkgx) $PS1"\n` - // } - - rv += `export PKGX_POWDER="${installations.pkgenv.map(utils.pkg.str).join(' ')}"\n` - rv += `export PKGX_PKGENV="${installations.installations.map(({pkg}) => utils.pkg.str(pkg)).join(' ')}"\n\n` - - rv += "_pkgx_reset() {\n" - for (const key in env) { - const old = getenv(key) - if (old !== undefined) { - //TODO don’t export if not currently exported! - rv += ` export ${key}=${escape_if_necessary(old)}\n` - } else { - rv += ` unset ${key}\n` - } - } - - // const ps1 = getenv('PS1') - // rv += ps1 ? ` export PS1="${ps1}"\n` : " unset PS1\n" - // rv += " unset -f _pkgx_reset\n" - - rv += "}\n" - rv += "\n" - - const raw_off_string = rv_pkgenv.map(x => `-${utils.pkg.str(x)}`).join(' ') - const off_string = rv_pkgenv.map(x => `-${escape_if_necessary(utils.pkg.str(x))}`).join(' ') - - rv += undent` - _pkgx_should_deactivate_devenv() { - suffix="\${PWD#"${dir}"}" - test "$PWD" != "${dir}$suffix" - } - - _pkgx_dev_off() { - echo '${blurple('env')} ${raw_off_string}' >&2 - - env ${off_string} - - if [ "$1" != --shy ]; then - rm -f "${persistence}" - fi - - unset -f _pkgx_dev_off _pkgx_should_deactivate_devenv - - ` - - for (const key in userenv) { - const value = getenv(key) - if (value) { - rv += ` export ${key}=${escape_if_necessary(value)}\n` - } else { - rv += ` unset ${key}\n` - } - } - - rv += "}" - - return [rv, rv_pkgenv] as [string, PackageRequirement[]] -} - -function apply_userenv(env: Record, userenv: Record) { - for (const [key, value] of Object.entries(userenv)) { - if (!(key in env) && !value.includes(`$${key}`) && !value.includes(`\${${key}}`)) { - /// user supplied env completely overrides this key or the key is empty - env[key] = value - } else { - env[key] = value - .replaceAll(`$${key}`, env[key]) - .replaceAll(`\${${key}}`, env[key]) - } - } -} - -export const _internals = { - install, - construct_env, - datadir: () => hooks.useConfig().data, - getenv: Deno.env.get, - apply_userenv -} diff --git a/src/modes/internal.use.test.ts b/src/modes/internal.use.test.ts deleted file mode 100644 index bc009700..00000000 --- a/src/modes/internal.use.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -// deno-lint-ignore-file require-await -import { semver, SemVer, Path, Installation, utils } from "pkgx" -import { null_logger as logger } from "../utils/test-utils.ts" -import specimen0, { _internals } from "./internal.use.ts" -import * as mock from "@std/testing/mock" - -Deno.test("internal.use.ts", async runner => { - - const specimen = (pkgstr: string) => { - const pkgs = pkgstr.split(' ') - const plus = pkgs.compact(x => x.startsWith('+') ? utils.pkg.parse(x.slice(1)) : undefined) - const minus = pkgs.compact(x => x.startsWith('-') ? utils.pkg.parse(x.slice(1)) : undefined) - const active = pkgs.compact(x => x.startsWith('.') ? utils.pkg.parse(x.slice(1)) : undefined) - return specimen0({ pkgs: {plus, minus, active }, logger, update: false }) - } - - const stub1 = mock.stub(_internals, "install", async pkgs => { - let installations: Installation[] - if (pkgs[0].project != 'bytereef.org/mpdecimal') { - installations = [{ - pkg: { project: "nodejs.org", version: new SemVer("20.1.2") }, path: new Path("/opt/nodejs.org/v20.1.2"), - }] - } else { - installations = [{ - pkg: { project: "bytereef.org/mpdecimal", version: new SemVer("20.1.2") }, path: new Path("/opt/bytereef.org/mpdecimal/v20.1.2"), - }] - } - return { installations, pkgenv: [{project: 'nodejs.org', constraint: new semver.Range('^20')}] } - }) - const stub2 = mock.stub(_internals, "construct_env", () => Promise.resolve({ - PATH: "/opt/nodejs.org/v20.1.2/bin", - FOO: "BAR" - })) - - try { - await runner.step("primary functionality", async () => { - await specimen("+node") - }) - - await runner.step("no bins", async () => { - await specimen("+bytereef.org/mpdecimal") - }) - - await runner.step("minus/active", async () => { - await specimen("+bytereef.org/mpdecimal -foo .foo") - }) - - await runner.step("empty", async () => { - await specimen("") - }) - - await runner.step("existing env matches", async () => { - const stub4 = mock.stub(_internals, "getenv", key => { - if (key == 'FOO' || key == 'PS1') { - return 'BAR' - } else { - return Deno.env.get(key) - } - }) - try { - await specimen("+node") - } finally { - stub4.restore() - } - }) - - } finally { - stub1.restore() - stub2.restore() - } -}) diff --git a/src/modes/internal.use.ts b/src/modes/internal.use.ts deleted file mode 100644 index 7d9397af..00000000 --- a/src/modes/internal.use.ts +++ /dev/null @@ -1,89 +0,0 @@ -import escape_if_necessary from "../utils/sh-escape.ts" -import construct_env from "../prefab/construct-env.ts" -import install, { Logger } from "../prefab/install.ts" -import { PackageRequirement, utils } from "pkgx" - -interface Pkgs { - plus: PackageRequirement[] - minus: PackageRequirement[] - active: PackageRequirement[] -} - -export default async function(opts: { pkgs: Pkgs, logger: Logger, pkgenv?: Record, update: boolean | Set }) { - const { install, construct_env, getenv } = _internals - - const pkgs = consolidate(opts.pkgs) - - if (pkgs.length == 0) { - return { - shellcode: 'unset PKGX_POWDER PKGX_PKGENV', - pkgenv: [] - } - } else { - let rv = '' - const print = (x: string) => rv += x + '\n' - - const pkgenv = await install(pkgs, opts) - const env = await construct_env(pkgenv) - - for (const [key, value] of Object.entries(env)) { - print(`export ${key}=${escape_if_necessary(value)}`) - } - - print(`export PKGX_POWDER="${pkgenv.pkgenv.map(utils.pkg.str).join(' ')}"`) - print(`export PKGX_PKGENV="${pkgenv.installations.map(({pkg}) => utils.pkg.str(pkg)).join(' ')}"`) - - // if (/\(pkgx\)/.test(getenv("PS1") ?? '') == false) { - // //FIXME doesn't work with warp.dev for fuck knows why reasons - // // https://github.com/warpdotdev/Warp/issues/3492 - // print('export PS1="(pkgx) $PS1"') - // } - - print('') - - print('_pkgx_reset() {') - for (const key in env) { - const old = getenv(key) - if (old !== undefined) { - //TODO don’t export if not currently exported! - print(` export ${key}=${escape_if_necessary(old)}`) - } else { - print(` unset ${key}`) - } - } - - // const ps1 = getenv('PS1') - // print(ps1 ? ` export PS1="${ps1}"` : ' unset PS1') - // print(' unset -f _pkgx_reset _pkgx_install') - - print('}') - - const install_set = (({pkgenv, installations}) => { - const set = new Set(pkgenv.map(({project}) => project)) - return installations.compact(({pkg}) => set.has(pkg.project) && pkg) - })(pkgenv) - - print('') - print('_pkgx_install() {') - print(` command pkgx install ${install_set.map(utils.pkg.str).join(' ')}`) - print(` pkgx ${pkgenv.pkgenv.map(x => `-${utils.pkg.str(x)}`).join(' ')}`) - print('}') - - return {shellcode: rv, pkgenv: install_set} - } -} - -////////////////////////////////////////////////////////////////////////// utils -function consolidate(pkgs: Pkgs) { - //FIXME inadequate - const rv = [...pkgs.active, ...pkgs.plus] - const set = new Set(pkgs.minus.map(({project}) => project)) - return rv.filter(({project}) => !set.has(project)) -} - -////////////////////////////////////////////////////////////////////// internals -export const _internals = { - install, - construct_env, - getenv: Deno.env.get -} diff --git a/src/modes/provider.ts b/src/modes/provider.ts deleted file mode 100644 index 3fbae0f8..00000000 --- a/src/modes/provider.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { plumbing } from "pkgx" -const { which } = plumbing - -export default async function(args: string[]) { - const pp = args.map(arg => which(arg, { all: true, providers: true })) - const rv = await Promise.all(pp) - return rv.flatMap(wut => wut.map(({ project }) => project)) -} diff --git a/src/modes/repl.ts b/src/modes/repl.ts deleted file mode 100644 index d342d783..00000000 --- a/src/modes/repl.ts +++ /dev/null @@ -1,71 +0,0 @@ -import construct_env from "../prefab/construct-env.ts" -import install, { Logger } from "../prefab/install.ts" -import { PackageRequirement, Path, PkgxError, utils } from "pkgx" -import { basename } from "@std/path" -import { dim } from "../utils/color.ts" -import exec from "../utils/execve.ts" - -export default async function(args: string[], { pkgs, ...opts }: { pkgs: PackageRequirement[], update: boolean | Set, logger: Logger }): Promise { - const { cmd, PS1 } = shell() - - const pkgenv = await install(pkgs, opts) - const env = await construct_env(pkgenv) - const pkgs_str = () => pkgenv.installations.map(({pkg}) => dim(utils.pkg.str(pkg))).join(", ") - - for (const key in env) { - env[key] = env[key].replaceAll(new RegExp(`\\\${${key}.*?}`, 'g'), (v => v ? `:${v}` : '')(Deno.env.get(key))) - } - - console.error('this is a temporary shell containing the following packages:') - console.error(pkgs_str()) - console.error("when done type: `exit`") - - if (PS1) env['PS1'] = PS1 - cmd.push(...args) - - exec({ cmd, env }) -} - -/////////////////////////////////////////////////////////////////////////// utils -function shell() { - const SHELL = Deno.env.get('SHELL') - const shell = SHELL?.trim() || find_shell() - const cmd = [shell, '-i'] // interactive - let PS1: string | undefined - - //TODO other shells pls #help-wanted - - switch (basename(shell)) { - case 'bash': - cmd.splice(1, 0, '--norc', '--noprofile') // longopts must precede shortopts - // fall through - case 'sh': - PS1 = "\\[\\033[38;5;63m\\]pkgx\\[\\033[0m\\] %~ " - break - case 'zsh': - PS1 = "%F{086}pkgx%F{reset} %~ " - cmd.push('--no-rcs', '--no-globalrcs') - break - case 'elvish': - cmd.push( - '-norc' - ) - break - case 'fish': - cmd.push( - '--no-config', - '--init-command', - 'function fish_prompt; set_color 5fffd7; echo -n "pkgx"; set_color grey; echo " %~ "; end' - ) - } - - return {cmd, PS1} -} - -function find_shell() { - for (const shell of ['/bin/bash', '/bin/zsh', '/bin/sh']) { - if (new Path(shell).isExecutableFile()) { - return shell - }} - throw new PkgxError("no shell found") -} \ No newline at end of file diff --git a/src/modes/run.test.ts b/src/modes/run.test.ts deleted file mode 100644 index 29afb12a..00000000 --- a/src/modes/run.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -// deno-lint-ignore-file require-await -import { assertEquals, assertRejects, assertThrows } from "@std/assert" -import specimen, { NoEntrypointError, _internals } from "./run.ts" -import { Path, SemVer, semver, hooks } from "pkgx" -import { faker_args } from "../utils/test-utils.ts" -import * as mock from "@std/testing/mock" - -Deno.test("run.ts", async runner => { - const opts = { pkgs: [], update: false, logger: { replace: () => {}, clear: () => {}, upgrade: () => null as any } } - - await runner.step("happy", async () => { - const args = faker_args() - const pkg = { project: "foo.com", constraint: new semver.Range("^2") } - - const stub1 = mock.stub(_internals, "parse_pkg_str", () => (Promise.resolve(pkg))) - const stub2 = mock.stub(_internals, "get_entrypoint", () => Promise.resolve(args.join(' '))) - const stub3 = mock.stub(_internals, "install", async () => { - const installations = [ - { pkg: {project: "foo.com", version: new SemVer("2.3.4")}, path: new Path("/opt/foo.com/v2.3.4") }, - { pkg: {project: "bar.org", version: new SemVer("1.2.3")}, path: new Path("/opt/bar.org/v1.2.3") } - ] - return { installations, pkgenv: [] } - }) - const stub4 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - const stub5 = mock.stub(_internals, "chdir", dir => assertEquals(dir, "/opt/foo.com/v2.3.4")) - const stub6 = stub_execve(args) - - try { - await specimen(args.slice(0, 1), opts) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - stub5.restore() - stub6.restore() - } - }) - - await runner.step("no endpoint", async () => { - const args = faker_args() - const pkg = { project: "foo.com", constraint: new semver.Range("^2") } - - const stub1 = mock.stub(_internals, "parse_pkg_str", () => (Promise.resolve(pkg))) - const stub2 = mock.stub(_internals, "get_entrypoint", async () => { return undefined }) - - try { - await assertRejects(() => specimen(args.slice(0, 1), opts), NoEntrypointError) - } finally { - stub1.restore() - stub2.restore() - } - }) - - await runner.step("coverage++", async () => { - if (hooks.usePantry().missing()) { - assertThrows(() => _internals.get_entrypoint({project: "github.com/ggerganov/llama.cpp"})) - } else { - await _internals.get_entrypoint({project: "github.com/ggerganov/llama.cpp"}) - } - }) -}) - - -function stub_execve(expected_cmd: string[]) { - return mock.stub(_internals, "exec", ({cmd}) => { - assertEquals(expected_cmd, cmd) - return undefined as never - }) -} diff --git a/src/modes/run.ts b/src/modes/run.ts deleted file mode 100644 index c0e73ed8..00000000 --- a/src/modes/run.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { PackageRequirement, PkgxError, hooks } from "pkgx" -import parse_pkg_str from "../prefab/parse-pkg-str.ts" -import construct_env from "../prefab/construct-env.ts" -import install, { Logger } from "../prefab/install.ts" -import execve from "../utils/execve.ts" -import { isString } from "is-what" - -export default async function(args: string[], opts: { - update: boolean | Set, - pkgs: PackageRequirement[], - logger: Logger -}) { - const { exec, install, get_entrypoint, construct_env, chdir, parse_pkg_str } = _internals - - const pkg = await parse_pkg_str(args[0]) - const entrypoint = await get_entrypoint(pkg) - if (!isString(entrypoint)) throw new NoEntrypointError(pkg) - - const pkgenv = await install([pkg, ...opts.pkgs], opts) - - args = entrypoint.split(/\s+/g).concat(args.slice(1)) - - const path = pkgenv.installations.find(({pkg: {project}}) => project == pkg.project)?.path - const env = await construct_env(pkgenv) - - chdir(path!.string) - - exec({ cmd: args, env }) -} - -export const _internals = { - install, - exec: execve, - parse_pkg_str, - construct_env, - chdir: Deno.chdir, - get_entrypoint: (pkg: { project: string }) => hooks.usePantry().project(pkg).yaml().then(x => x?.['entrypoint']) -} - -export class NoEntrypointError extends PkgxError { - constructor(pkg: {project: string}) { - super(`no entrypoint for: ${pkg.project}`) - } -} diff --git a/src/modes/shell-completion.ts b/src/modes/shell-completion.ts deleted file mode 100644 index d9571be3..00000000 --- a/src/modes/shell-completion.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { hooks } from "pkgx" -const { usePantry } = hooks - -export default async function(args: string[]) { - const pantry = usePantry() - const rv: string[] = [] - const promises: Promise[] = [] - for await (const entry of pantry.ls()) { - const project = pantry.project(entry) - const promise = project.provides().then(provides => { - for (const provide of provides) { - if (provide.startsWith(args[0])) { - rv.push(provide) - return - } - } - }) - promises.push(promise) - } - - await Promise.all(promises) - - return rv -} diff --git a/src/modes/shellcode.ts b/src/modes/shellcode.ts deleted file mode 100644 index df0d18bd..00000000 --- a/src/modes/shellcode.ts +++ /dev/null @@ -1,213 +0,0 @@ -import useConfig from 'pkgx/hooks/useConfig.ts' -import undent from 'outdent' -import { Path } from 'pkgx' -import { flatmap } from "pkgx/utils/misc.ts"; - -// NOTES -// * is safely re-entrant (and idempotent) -// * we `eval` for BASH/ZSH because otherwise parsing fails for POSIX `sh` -// * we `eval` for conditional functions since weirdly zsh parses code it isn’t actually branching on -// * we add `~/.local/bin` to `PATH` because we eg `npm i -g` is configured to install there -// * `command_not_found_handler` cannot change the global environment hence we write a file - -// TODO -// ref https://gitlab.freedesktop.org/xdg/xdg-specs/-/issues/14 -// * remove the files we create for command not found handler once any prompt appears -// * need to use a proper tmp location perhaps - -export default function() { - const blurple = (x: string) => `\\033[38;5;63m${x}\\033[0m` - const dim = (x: string) => `\\e[2m${x}\\e[0m` - const datadir = useConfig().data.join("dev") - const tmp = (flatmap(Deno.env.get("XDG_STATE_HOME"), Path.abs) ?? platform_state_default()).join("pkgx") - const sh = '${SHELL:-/bin/sh}' - - return undent` - pkgx() { - case "$1" in - install) - if [ $# -gt 1 ]; then - command pkgx "$@" - elif type _pkgx_install >/dev/null 2>&1; then - _pkgx_install - else - echo "pkgx: nothing to install" >&2 - return 1 - fi;; - unload) - if type _pkgx_reset >/dev/null 2>&1; then - _pkgx_reset - fi - unset -f _pkgx_chpwd_hook _pkgx_should_deactivate_devenv pkgx x command_not_found_handler command_not_found_handle pkgx@latest _pkgx_commit _pkgx_dev_off _pkgx_provider >/dev/null 2>&1 - echo "pkgx: shellcode unloaded" >&2;; - *) - command pkgx "$@";; - esac - } - - if ! type x >/dev/null 2>&1; then - eval 'x() { - case $1 in - "") - if [ -f "${tmp}/shellcode/x.$$" ]; then - if foo="$("${tmp}/shellcode/u.$$")"; then - eval "$foo" - ${sh} "${tmp}/shellcode/x.$$" - unset foo - fi - rm -f "${tmp}/shellcode/"?.$$ - else - echo "pkgx: nothing to run" >&2 - return 1 - fi;; - *) - command pkgx -- "$@";; - esac - }' - fi - - env() { - for arg in "$@"; do - case $arg in - --*) - command env "$@" - return;; - -*);; - +*);; - *) - command env "$@" - return;; - esac - done - if [ $# -eq 0 ]; then - command env - return - elif type _pkgx_reset >/dev/null 2>&1; then - _pkgx_reset - fi - eval "$(command pkgx --internal.use "$@")" - } - - dev() { - if [ "$1" = 'off' ]; then - if type _pkgx_dev_off >/dev/null 2>&1; then - _pkgx_dev_off - else - echo 'dev: environment not active' >&2 - return 1 - fi - elif type _pkgx_dev_off >/dev/null 2>&1; then - echo 'dev: environment already active' >&2 - return 1 - else - if type _pkgx_reset >/dev/null 2>&1; then - _pkgx_reset - fi - eval "$(command pkgx --internal.activate "$PWD" "$@")" - fi - } - - _pkgx_provider() { - if ! command pkgx --silent --provider "$1"; then - command pkgx --sync --keep-going --silent --provider "$1" - fi - } - - command_not_found_handler() { - if [ $1 = pkgx ]; then - echo 'fatal: \`pkgx\` not in PATH' >&2 - return 1 - elif [ -t 2 ] && _pkgx_provider $1; then - echo -e '${dim('^^ type `')}x${dim('` to run that')}' >&2 - - d="${tmp}/shellcode" - mkdir -p "$d" - echo "#!${sh}" > "$d/u.$$" - echo "echo -e \\"${blurple('env')} +$1 ${dim('&&')} $@ \\" >&2" >> "$d/u.$$" - echo "exec pkgx --internal.use +\\"$1\\"" >> "$d/u.$$" - chmod u+x "$d/u.$$" - echo -n "exec " > "$d/x.$$" - for arg in "$@"; do - printf "%q " "$arg" >> "$d/x.$$" - done - - return 127 - else - echo "cmd not found: $1" >&2 - return 127 - fi - } - - _pkgx_chpwd_hook() { - if _pkgx_should_deactivate_devenv >/dev/null 2>&1; then - _pkgx_dev_off --shy - fi - if ! type _pkgx_dev_off >/dev/null 2>&1; then - dir="$PWD" - while [ "$dir" != "/" ]; do - if [ -f "${datadir}/$dir/dev.pkgx.activated" ]; then - if type _pkgx_reset >/dev/null 2>&1; then - _pkgx_reset - fi - eval "$(command pkgx --internal.activate "$dir")" - break - fi - dir="$(dirname "$dir")" - done - fi - } - - if [ -n "$ZSH_VERSION" ] && [ $(emulate) = zsh ]; then - eval 'typeset -ag chpwd_functions - - if [[ -z "\${chpwd_functions[(r)_pkgx_chpwd_hook]+1}" ]]; then - chpwd_functions=( _pkgx_chpwd_hook \${chpwd_functions[@]} ) - fi - - if [ "$TERM_PROGRAM" != Apple_Terminal ]; then - _pkgx_chpwd_hook - fi - - _pkgx() { - local words - words=($(pkgx --shell-completion $1)) - reply=($words) - } - compctl -K _pkgx pkgx' - elif [ -n "$BASH_VERSION" ] && [ "$POSIXLY_CORRECT" != y ] ; then - eval 'cd() { - builtin cd "$@" || return - _pkgx_chpwd_hook - } - - command_not_found_handle() { - command_not_found_handler "$@" - } - - _pkgx_chpwd_hook' - else - POSIXLY_CORRECT=y - echo "pkgx: warning: unsupported shell" >&2 - fi - - if [ "$POSIXLY_CORRECT" != y ]; then - eval 'pkgx@latest() { - command pkgx pkgx@latest "$@" - }' - if [[ "$PATH" != *"$HOME/.local/bin"* ]]; then - export PATH="$HOME/.local/bin:$PATH" - fi - fi - ` -} - -function platform_state_default() { - switch (Deno.build.os) { - case 'darwin': - return Path.home().join("Library/Application Support") - case "windows": - return new Path(Deno.env.get("LOCALAPPDATA")!) - default: - return Path.home().join(".local", "state") - } -} diff --git a/src/modes/uninstall.ts b/src/modes/uninstall.ts deleted file mode 100644 index e8cd8e8e..00000000 --- a/src/modes/uninstall.ts +++ /dev/null @@ -1,34 +0,0 @@ -import parse_pkg_str from "../prefab/parse-pkg-str.ts" -import { hooks, PackageRequirement, Path, PkgxError, utils } from "pkgx" - -export default async function(pkgspecs: string[]) { - const pkgs = await Promise.all(pkgspecs.map(x => parse_pkg_str(x, {latest: 'ok'}))) - - await uninstall(new Path("/usr/local/bin"), pkgs) - await uninstall(Path.home().join(".local/bin"), pkgs) -} - -async function uninstall(prefix: Path, pkgs: PackageRequirement[]) { - for (const pkg of pkgs) { - const programs = await hooks.usePantry().project(pkg).provides() - const pkgstr = utils.pkg.str(pkg) - const config = hooks.useConfig() - const pkgdir = pkgstr.split("/").slice(0, -1).join("/") - //FIXME: it removes the dir successfully. however, it still complains that it didn't delete that - try { - config.cache.join(`pkgx/envs/${pkgdir}`).rm({recursive: true}) - } catch (e) { - console.warn(e); - } - for (const program of programs) { - const f = prefix.join(program) - if (f.isFile()) { - const cmd = new Deno.Command(f.string, {env: {PKGX_UNINSTALL: '1'}}) - const proc = await cmd.spawn().status - if (!proc.success) { - throw new PkgxError(`Couldn’t uninstall: ${f}`) - } - } - } - } -} diff --git a/src/modes/version.ts b/src/modes/version.ts deleted file mode 100644 index 94a990b9..00000000 --- a/src/modes/version.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function() { - throw new Error("unversioned developer build") -} diff --git a/src/modes/x.test.ts b/src/modes/x.test.ts deleted file mode 100644 index 568ddaee..00000000 --- a/src/modes/x.test.ts +++ /dev/null @@ -1,375 +0,0 @@ -// deno-lint-ignore-file require-await no-explicit-any -import { assertEquals, assertRejects, fail } from "@std/assert" -import { Path, SemVer, PkgxError, semver } from "pkgx" -import { faker_args } from "../utils/test-utils.ts" -import { ProvidesError } from "../utils/error.ts" -import specimen, { _internals } from "./x.ts" -import * as mock from "@std/testing/mock" - -//TODO use a testLib thing (libpkgx provides this) -//TODO add faker methods for more of what we hardcode here - - -Deno.test("x.ts", async runner => { - - const opts = { unknown: [], update: false, logger: { replace: () => {}, clear: () => {}, upgrade: () => null as any } } - - await runner.step("unprovided throws", async () => { - const stub1 = mock.stub(_internals, "which", () => Promise.resolve(undefined)) - const stub2 = mock.stub(_internals, "useSync", () => Promise.resolve(undefined)) - try { - await assertRejects( - () => specimen({args: faker_args(), ...opts}), - ProvidesError) - } finally { - stub1.restore() - stub2.restore() - } - }) - - await runner.step("arg0 with absolute path is pass through", async () => { - const path = Path.mktemp().join("foo").touch() - const args = [path.string, ...faker_args()] - const stub1 = stub_execve() - const stub2 = mock.stub(_internals, "install", async pkgs => { - assertEquals(pkgs.length, 0) - return { installations: [], pkgenv: [] } - }) - - try { - await specimen({args, ...opts}) //TODO shouldn't be specifying update: false, should just be mocking libpkgx - - assertEquals(stub1.calls.length, 1) - assertEquals(stub1.calls[0].args[0].cmd, args) - - } finally { - stub1.restore() - stub2.restore() - } - }) - - await runner.step("installs deps", async runner => { - const args = faker_args() - const pkg = { project: "foo.com", constraint: new semver.Range("^2") } - const stub1 = stub_execve() - const stub2 = mock.stub(_internals, "construct_env", async () => ({ PATH: "/a:${PATH}" })) - const stub3 = mock.stub(_internals, "install", async () => { - const installations = [ - { pkg: {project: "foo.com", version: new SemVer("2.3.4")}, path: new Path("/opt/foo.com/v2.3.4") }, - { pkg: {project: "bar.org", version: new SemVer("1.2.3")}, path: new Path("/opt/bar.org/v1.2.3") } - ] - return { installations, pkgenv: [] } - }) - const stub4 = mock.stub(_internals, "which", () => Promise.resolve({...pkg, shebang: args.slice(0, 1) })) - - try { - await runner.step("std", () => specimen({args, ...opts})) - - assertEquals(stub1.calls.length, 1) - assertEquals(stub1.calls[0].args[0].cmd, args) - - await runner.step("@latest", async runner => { - const stub5 = mock.stub(_internals, "getenv", () => undefined) - const new_args = [`${args[0]}@latest`, ...args.slice(1)] - try { - await runner.step("std", () => specimen({args: new_args, ...opts})) - } finally { - stub5.restore() - } - - assertEquals(stub1.calls.length, 2) - assertEquals(stub1.calls[1].args[0].cmd, args) - - await runner.step("std w/update-set", () => specimen({args: new_args, ...opts, update: new Set()})) - - assertEquals(stub1.calls.length, 3) - assertEquals(stub1.calls[2].args[0].cmd, args) - }) - await runner.step("invalid PKGX_LVL", async () => { - const stub = mock.stub(_internals, "getenv", () => "") - try { - await assertRejects(() => specimen({args, ...opts})) - } finally { - stub.restore() - } - }) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - } - }) - - await runner.step("resolves foo@3 but runs foo", async () => { - const pkg = { project: "foo.com", version: new SemVer("3.4.5") } - const pkgspec = { project: "foo.com", constraint: new semver.Range("^3") } - const installations = [{ pkg, path: new Path(`/opt/${pkg.project}/v${pkg.version}`) }] - const stub1 = mock.stub(_internals, "which", () => Promise.resolve({...pkgspec, shebang: ["foo"] })) - const stub2 = mock.stub(_internals, "install", () => Promise.resolve({installations, pkgenv : []})) - const stub3 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - const args = ['foo', 'bar'] - const stub4 = stub_execve() - try { - await specimen({args: ['foo@3', 'bar'], ...opts}) - - assertEquals(stub4.calls.length, 1) - assertEquals(stub4.calls[0].args[0].cmd, args) - - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - } - }) - - await runner.step("multiarg shebangs run", async () => { - const pkg = { project: "foo.com", version: new SemVer("3.4.5") } - const pkgspec = { project: "foo.com", constraint: new semver.Range("^3") } - const installations = [{ pkg, path: new Path(`/opt/${pkg.project}/v${pkg.version}`) }] - const stub1 = mock.stub(_internals, "which", () => Promise.resolve({...pkgspec, shebang: ["foo", "bar"] })) - const stub2 = mock.stub(_internals, "install", () => Promise.resolve({ installations, pkgenv: []})) - const args = ['foo', 'bar', 'baz'] - const stub3 = stub_execve() - const stub4 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - try { - await specimen({args: ['foo', 'baz'], ...opts}) - - assertEquals(stub3.calls.length, 1) - assertEquals(stub3.calls[0].args[0].cmd, args) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - } - }) - - // await runner.step("asked to run something in active pkgenv", async () => { - // const env = { - // "PKGX_PKGENV": "foo.com^2" - // } - // const stub1 = mock.stub(_internals, "which", async () => ({project: "foo.com", constraint: new semver.Range('*'), shebang: ["foo"] })) - // const stub2 = mock.stub(_in_active_pkg_env_internals, "getenv", () => env["PKGX_PKGENV"]) - // const stub3 = mock.stub(_internals, "superenv", () => env) - // const stub4 = stub_execve() - // try { - // await specimen(["foo", "bar"], opts) - - // assertEquals(stub4.calls.length, 1) - // assertEquals(stub4.calls[0].args[0].cmd, ["foo", "bar"]) - // } finally { - // stub1.restore() - // stub2.restore() - // stub3.restore() - // stub4.restore() - // } - // }) - - await runner.step("asked to run something in active pkgenv, but constraint is outside of it", async () => { - const env: Record = { - "PKGX_PKGENV": "foo.com=2.3.4" - } - const pkg = { project: "foo.com", version: new SemVer("3.4.5") } - const installations = [{ pkg, path: new Path(`/opt/${pkg.project}/v${pkg.version}`) }] - const stub1 = mock.stub(_internals, "which", async () => ({project: "foo.com", constraint: new semver.Range('^3'), shebang: ["foo"] })) - const stub3 = mock.stub(_internals, "getenv", key => env[key]) - const stub4 = stub_execve() - const stub5 = mock.stub(_internals, "install", () => Promise.resolve({ installations, pkgenv: []})) - const stub6 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - try { - await specimen({args: ["foo@3", "bar"], ...opts}) - - assertEquals(stub4.calls.length, 1) - assertEquals(stub4.calls[0].args[0].cmd, ["foo", "bar"]) - } finally { - stub1.restore() - stub3.restore() - stub4.restore() - stub5.restore() - stub6.restore() - } - }) - - await runner.step("file path but doesn’t exist", async () => { - const stub1 = mock.stub(_internals, "which", () => Promise.resolve(undefined)) - const stub2 = mock.stub(_internals, "useSync", async () => { fail() }) - try { - await assertRejects( - () => specimen({args: ["/foo/bar"], ...opts}), - PkgxError) - } finally { - stub1.restore() - stub2.restore() - } - }) - - await runner.step("undecorated file path that exists", async () => { - const newwd = Path.mktemp().join("foo").touch().parent() - const oldwd = Path.cwd() - - const stub1 = mock.stub(_internals, "which", () => Promise.resolve(undefined)) - const stub2 = mock.stub(_internals, "useSync", async () => { fail() }) - const stub3 = stub_execve() - - try { - Deno.chdir(newwd.string) - await specimen({args: ["foo"], ...opts}) - - assertEquals(stub3.calls.length, 1) - assertEquals(stub3.calls[0].args[0].cmd, ["foo"]) - } finally { - Deno.chdir(oldwd.string) - stub1.restore() - stub2.restore() - stub3.restore() - } - }) - - await runner.step("failsafe", async () => { - let i = 0 - let useSyncCalled = 0 - const stub1 = mock.stub(_internals, "which", async () => i++ ? { project: "foo", constraint: new semver.Range('*'), shebang: ['baz'] } : undefined) - const stub2 = mock.stub(_internals, "useSync", async () => { useSyncCalled++ }) - const stub3 = stub_execve() - const stub4 = mock.stub(_internals, "install", () => Promise.resolve({ installations: [], pkgenv: []})) - const stub5 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - - try { - await specimen({args: ["foo", "bar"], ...opts}) - - assertEquals(i, 2) - assertEquals(useSyncCalled, 1) - - assertEquals(stub3.calls.length, 1) - assertEquals(stub3.calls[0].args[0].cmd, ["baz", "bar"]) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - stub5.restore() - } - }) - - await runner.step("resolves shebang", async () => { - const f = Path.mktemp().join('foo').touch() - const shebang_args = ['deno', 'run'] - const args_to_script = ['--arg-to-script1', '--arg-to-script2'] - const pkg = { project: "foo.com", constraint: new semver.Range("^2") } - const stub1 = stub_execve() - const stub2 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - const stub3 = mock.stub(_internals, "install", async () => { - const installations = [ - { pkg: {project: "foo.com", version: new SemVer("2.3.4")}, path: new Path("/opt/foo.com/v2.3.4") }, - { pkg: {project: "bar.org", version: new SemVer("1.2.3")}, path: new Path("/opt/bar.org/v1.2.3") } - ] - return { installations, pkgenv: [] } - }) - const stub4 = mock.stub(_internals, "which", () => Promise.resolve({...pkg, shebang: ['should', 'be', 'ignore', 'this'] })) - const stub5 = mock.stub(_internals, "getenv", () => undefined) - const stub6 = mock.stub(_internals, "get_shebang", () => Promise.resolve(shebang_args)) - - try { - await specimen({args: [f.string, ...args_to_script], ...opts}) - - assertEquals(stub1.calls.length, 1) - assertEquals(stub1.calls[0].args[0].cmd, [...shebang_args, f.string, ...args_to_script]) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - stub5.restore() - stub6.restore() - } - }) - - await runner.step("resolves interpreter", async () => { - const f = Path.mktemp().join('foo').touch() - const shebang_args = ['deno', 'run'] - const args_to_script = ['--arg-to-script1', '--arg-to-script2'] - const pkg = { project: "foo.com", constraint: new semver.Range("^2") } - const stub1 = stub_execve() - const stub2 = mock.stub(_internals, "construct_env", () => (Promise.resolve({}))) - const stub3 = mock.stub(_internals, "install", async () => { - const installations = [ - { pkg: {project: "foo.com", version: new SemVer("2.3.4")}, path: new Path("/opt/foo.com/v2.3.4") }, - { pkg: {project: "bar.org", version: new SemVer("1.2.3")}, path: new Path("/opt/bar.org/v1.2.3") } - ] - return { installations, pkgenv: [] } - }) - const stub4 = mock.stub(_internals, "which", () => Promise.resolve({...pkg, shebang: ['should', 'ignore', 'these'] })) - const stub5 = mock.stub(_internals, "getenv", () => undefined) - const stub6 = mock.stub(_internals, "get_shebang", () => Promise.resolve(undefined)) - const stub7 = mock.stub(_internals, "get_interpreter", async () => ({project: 'foo.com', args: shebang_args})) - - try { - await specimen({args: [f.string, ...args_to_script], ...opts}) - - assertEquals(stub1.calls.length, 1) - assertEquals(stub1.calls[0].args[0].cmd, [...shebang_args, f.string, ...args_to_script]) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - stub5.restore() - stub6.restore() - stub7.restore() - } - }) - - // we do not support being invoked `pkgx ./script` where script has a pkgx shebang - //FIXME really this is something we should support - await runner.step("refuses pkgx-script as arg0", async () => { - const f = Path.mktemp().join('foo').touch() - const shebang_args = ['pkgx', 'bar'] - const stub = mock.stub(_internals, "get_shebang", () => Promise.resolve(shebang_args)) - try { - await assertRejects(() => specimen({args: [f.string], ...opts}), PkgxError) - } finally { - stub.restore() - } - }) - - // we do not support being invoked `./script` where script has a pkgx shebang that doesn’t specify any pkg - await runner.step("pkgx is not an interpreter", async () => { - const f = Path.mktemp().join('foo').touch() - const stub = mock.stub(_internals, "get_shebang", () => Promise.resolve(['pkgx'])) - try { - await assertRejects(() => specimen({args: [f.string], ...opts}), PkgxError) - } finally { - stub.restore() - } - }) - - await runner.step("pkgx is not an interpreter", async () => { - const f = Path.mktemp().join('foo').touch() - const stub = mock.stub(_internals, "get_shebang", () => Promise.resolve(['pkgx'])) - try { - await assertRejects(() => specimen({args: [f.string], ...opts}), PkgxError) - } finally { - stub.restore() - } - }) - - await runner.step("shebang specifies non existent pkg", async () => { - const f = Path.mktemp().join('foo').touch() - const stub1 = mock.stub(_internals, "get_shebang", () => Promise.resolve(['foo'])) - const stub2 = mock.stub(_internals, "which", () => Promise.resolve(undefined)) - try { - await assertRejects(() => specimen({args: [f.string], ...opts}), PkgxError) - } finally { - stub1.restore() - stub2.restore() - } - }) -}) - -function stub_execve() { - return mock.stub(_internals, "exec", () => { - return undefined as never // the real function doesn’t return, but for our testing we must - }) -} diff --git a/src/modes/x.ts b/src/modes/x.ts deleted file mode 100644 index 5f89a3f1..00000000 --- a/src/modes/x.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { PackageRequirement, Path, PkgxError, hooks, semver } from "pkgx" -import install, { Logger } from "../prefab/install.ts" -import construct_env from "../prefab/construct-env.ts" -import resolve_arg0 from "../prefab/resolve-arg0.ts" -import { ProvidesError } from "../utils/error.ts" -import get_shebang from "../utils/get-shebang.ts" -import BaseLogger from "../utils/Logger.ts" -import execve from "../utils/execve.ts" -import host from "pkgx/utils/host.ts" -const { usePantry, useSync } = hooks - -//TODO be able to parse @latest for shebang result - - -export default async function({ args, unknown, pkgs: dry, ...opts }: { - update: boolean | Set, - pkgs?: PackageRequirement[], - logger: Logger, - args: string[], - unknown: string[] -}) { - const { install, exec, construct_env, getenv } = _internals - - dry ??= [] - - //TODO if contains PATH items then go directly to isFile path - if (args.length) { - const wut = await find_it(args, dry) - if (wut.pkg) dry.push(wut.pkg) - args = wut.args - - if (wut.update) { - if (opts.update instanceof Set) { - opts.update.add(wut.pkg!.project) - } else if (!opts.update) { - opts.update = new Set([wut.pkg!.project]) - } - } - } - - args.push(...unknown) - - const pkgenv = await install(dry, opts) - const env = await construct_env(pkgenv) - - for (const key in env) { - env[key] = env[key].replaceAll(new RegExp(`\\\${${key}.*?}`, 'g'), (v => v ? `:${v}` : '')(Deno.env.get(key))) - } - - // fork bomb protection - const n = parseInt(getenv('PKGX_LVL') ?? '0') + 1 - if (Number.isNaN(n)) throw new PkgxError("invalid `$PKGX_LVL`") - env['PKGX_LVL'] = `${n}` - - exec({ cmd: args, env }) -} - -////////////////////////////////////////////////////////////////////////// utils -export function barf(fn: () => Error): never { - throw fn() -} - -async function find_it(args: string[], dry: PackageRequirement[]) { - if (args[0].includes('/')) { - return await find_file(args, dry) ?? barf(() => new PkgxError(`no such file: ${args[0]}`)) - } - - // magic pkg expansion - let wut = await find_arg0(args, dry) - if (wut) { - return wut - } - - // white list `open` since it's commonly wanted and not cross platform - if (args[0] == 'open' && host().platform == 'darwin') { - return { - args: ["/usr/bin/open", ...args.slice(1)] - } - } - - /// we now check for a file `./foo` that was specified (plainly) `pkgx foo` - wut = await find_file(args, dry) - if (wut) { - return wut - } - - // be just works: do a sync in case this has been recently added to the pantry - const logger = new BaseLogger('sync') - const printer = { - syncing() { - logger.replace('fetching') - }, - caching() { - logger.replace('caching') - }, - syncd() { - logger.clear() - } - } - await _internals.useSync(printer) - - wut = await find_arg0(args, dry) - if (wut) { - return wut - } - - throw new ProvidesError(args[0]) -} - -interface Wut { - pkg?: PackageRequirement - args: string[] - update?: boolean -} - -async function find_arg0(args: string[], dry: PackageRequirement[]): Promise { - const { which } = _internals - const update = args[0].endsWith("@latest") - const arg0 = update ? args[0].slice(0, -7) : args[0] - const pkg = await which(arg0, dry) - if (pkg) { - args = [...pkg.shebang, ...args.slice(1)] - return { pkg, args, update } - } -} - -async function find_file(args: string[], dry: PackageRequirement[]): Promise { - const { which, get_interpreter, get_shebang } = _internals - - const path = (Path.abs(args[0]) ?? Path.cwd().join(args[0])).isFile() - if (!path) return - - let shebang = await get_shebang(path) - const was_pkgx = shebang?.[0] == 'pkgx' - - if (was_pkgx) { - if (shebang!.length == 1) { - shebang = undefined - } else { - throw new PkgxError("usage: pls invoke script directly") - } - } - - if (shebang) { - const wut = await which(shebang[0], dry) - if (!wut) throw new ProvidesError(shebang[0]) - args = [...shebang, ...args] - return { pkg: wut, args } - } else { - const interpreter = await get_interpreter({ interprets: path.extname() }) - if (interpreter) { - args = [...interpreter.args, ...args] - const pkg = { project: interpreter.project, constraint: new semver.Range('*') } - return { pkg, args } - } else if (was_pkgx) { - throw new PkgxError("cannot infer interpreter") - } else { - return { args } - } - } -} - -////////////////////////////////////////////////////////////////////// internals -export const _internals = { - install, - exec: execve, - which: resolve_arg0, - construct_env, - getenv: Deno.env.get, - useSync, - get_interpreter: usePantry().which, - get_shebang -} diff --git a/src/parse-args.test.ts b/src/parse-args.test.ts deleted file mode 100644 index c6a02b1d..00000000 --- a/src/parse-args.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { assert, assertEquals, assertThrows, fail } from "@std/assert" -import { faker_args } from "./utils/test-utils.ts" -import parse_args from "./parse-args.ts" -import { UsageError } from "./utils/error.ts"; - -Deno.test("parse_args.ts", async runner => { - await runner.step("+pkg", () => { - const rv = parse_args(["+foo", "+bar@2", "-baz"]) - if (rv.mode !== 'env') fail() - assertEquals(rv.pkgs.plus, ["foo", "bar@2"]) - assertEquals(rv.pkgs.minus, ["baz"]) - }) - - await runner.step("--", () => { - const args = faker_args() - const rv = parse_args(['+foo', '--', ...args]) - if (rv.mode !== 'x') fail() - assertEquals(rv.pkgs.plus, ["foo"]) - assertEquals(rv.unknown, args) - }) - - await runner.step("--long-args", async runner => { - for (const arg of ['sync', 'update']) { - await runner.step(`--${arg}`, () => { - const args = faker_args() - const rv = parse_args([`--${arg}`, ...args]) - if (rv.mode !== 'x') fail() - assertEquals(rv.args, args) - assert((rv.flags as any)[arg]) - }) - } - }) - - await runner.step("verbosity", () => { - assertEquals(parse_args(['--verbose']).flags.verbosity, 1) - assertEquals(parse_args(['--verbose=-2']).flags.verbosity, -2) - assertEquals(parse_args(['--silent', '--verbose=2']).flags.verbosity, 2) - assertEquals(parse_args(['--verbose=2', '--quiet']).flags.verbosity, -1) - }) - - await runner.step("UsageError", () => { - assertThrows(() => parse_args(['--syncs'])) - assertThrows(() => parse_args(['+'])) - assertThrows(() => parse_args(['-'])) - }) - - await runner.step("--internal.use", () => { - const args = faker_args() - const rv = parse_args(['--internal.use', ...args.map(x => `+${x}`)]) - if (rv.mode !== 'internal.use') fail() - assertEquals(rv.pkgs.plus, args) - }) - - await runner.step("integrate", async runner => { - await runner.step("--dry-run", () => { - const args = faker_args() - const rv = parse_args(['integrate', '--dry-run', ...args]) - if (rv.mode !== 'integrate') fail() - assertEquals(rv.dryrun, true) - }) - await runner.step("w/o --dry-run", () => { - const args = faker_args() - const rv = parse_args(['integrate', ...args]) - if (rv.mode !== 'integrate') fail() - assertEquals(rv.dryrun, false) - }) - await runner.step("--dry-run with other modes throws", () => { - const args = faker_args() - assertThrows(() => parse_args(['--dry-run', '--help', ...args])) - }) - }) - - await runner.step("provider", () => { - const args = faker_args() - const rv = parse_args(['--provider', ...args]) - if (rv.mode != 'provider') fail() - assertEquals(rv.args, args) - }) - - await runner.step("install", () => { - const args = faker_args() - const rv = parse_args(['install', ...args]) - if (rv.mode != 'install') fail() - assertEquals(rv.args, args) - }) - - await runner.step("--internal.activate", () => { - const args = ['/foo/bar', ...faker_args()] - const rv = parse_args(['--internal.activate', ...args]) - if (rv.mode != 'internal.activate') fail() - assertEquals(rv.dir.string, args[0]) - }) - - await runner.step("multiple modes", () => { - assertThrows(() => parse_args(['--shellcode', '--internal.use']), UsageError, 'multiple modes specified') - assertThrows(() => parse_args(['--internal.use', '--shellcode']), UsageError, 'multiple modes specified') - assertThrows(() => parse_args(['--internal.use', '--provider']), UsageError, 'multiple modes specified') - assertThrows(() => parse_args(['--shellcode', '--help']), UsageError, 'multiple modes specified') - }) - - await runner.step("deintegrate", () => { - const args = faker_args() - const rv = parse_args(['deintegrate', ...args]) - assertEquals(rv.mode, 'deintegrate') - }) -}) diff --git a/src/parse-args.ts b/src/parse-args.ts deleted file mode 100644 index 6677f4b4..00000000 --- a/src/parse-args.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { UsageError } from "./utils/error.ts" -import { Path, utils } from "pkgx" -const { flatmap } = utils - -type Pkgs = { - plus: string[] - minus: string[] -} - -export type Args = { - flags: Flags -} & ( - { - mode: 'x' | 'run' - args: string[] - unknown: string[] - pkgs: Pkgs - } | { - mode: 'internal.use' - pkgs: Pkgs - } | { - mode: 'env' - pkgs: { - plus: string[] - minus: string[] - } - } | { - mode: 'shellcode' | 'version' | 'help' - } | { - mode: 'integrate' | 'deintegrate' - dryrun: boolean - } | { - mode: 'provider' | 'shell-completion' - args: string[] - } | { - mode: 'internal.activate' - dir: Path - } | { - mode: 'install' | 'uninstall' - args: string[] - } -) - -interface Flags { - sync: boolean - update: boolean - verbosity?: number - keepGoing: boolean, - unsafe: boolean -} - -export default function(input: string[]): Args { - const it = input[Symbol.iterator]() - const pkgs: Pkgs = { plus: [], minus: [] } - const args: string[] = [] - const unknown: string[] = [] - const flags: Flags = { - sync: false, - update: false, - keepGoing: false, - unsafe: false - } - let mode: string | undefined - let dryrun: boolean | undefined - - switch (input[0]) { - case 'deintegrate': - case 'integrate': - case 'uninstall': - case 'install': - case 'run': - mode = input[0] - it.next() // consume the subcommand - } - - for (const arg of it) { - const [type, content] = parse(arg) - - switch (type) { - case '+': - pkgs.plus.push(content) - break - case '-': - pkgs.minus.push(content) - break - case '--': - switch (content) { - case 'sync': - flags.sync = true - break - case 'update': - flags.update = true - break - case 'unsafe': - flags.unsafe = true - break - case 'provides': - if (mode) throw new UsageError({msg: 'multiple modes specified'}) - console.error("%cdeprecated: %cuse pkgx --provider instead", 'color: red', 'color: initial') - mode = 'provider' - break - case 'shellcode': - case 'integrate': - case 'internal.activate': - case 'help': - case 'version': - case 'provider': - case 'shell-completion': - case 'internal.use': - if (mode) throw new UsageError({msg: 'multiple modes specified'}) - mode = content - break - case 'silent': - flags.verbosity = -2 - break - case 'quiet': - flags.verbosity = -1 - break - case 'dry-run': - dryrun = true - break - case 'keep-going': - flags.keepGoing = true - break - case '': - // empty the main loop iterator - for (const arg of it) unknown.push(arg) - break - default: { - const match = content.match(/^verbose(=-?\d+)?$/) - if (match) { - flags.verbosity = flatmap(match[1], n => parseInt(n.slice(1))!) ?? 1 - } else { - throw new UsageError(arg) - } - }} - break - default: - /// unrecognized args means the start of the runner args - args.push(arg) - for (const arg of it) args.push(arg) - } - } - - if (dryrun !== undefined && !(mode == 'integrate' || mode == 'deintegrate')) { - throw new UsageError({msg: '--dry-run cannot be specified with this mode'}) - } - - switch (mode) { - case undefined: - if (args.length + unknown.length) { - return { mode: 'x', flags, pkgs, args, unknown } - } else { - return { mode: 'env', flags, pkgs } - } - case 'internal.use': - return { mode, flags, pkgs } - case 'provider': - case 'shell-completion': - return { mode, flags, args } - case 'internal.activate': - return { mode, flags, dir: new Path(args[0]) } - case 'install': - case 'uninstall': - return { mode, flags, args } - case 'integrate': - case 'deintegrate': - dryrun ??= false - return { mode, dryrun, flags } - default: - // deno-lint-ignore no-explicit-any - return { mode: mode as any, flags } - } -} - - -//////////////////////// utils - -function parse(arg: string): ['+', string] | ['--', string] | ['-', string] | [undefined, string] { - switch (arg[0]) { - case '+': { - const content = arg.slice(1) - if (!content) throw new UsageError(arg) - return ['+', content] - } - case '-': - if (arg[1] == '-') { - return ['--', arg.slice(2)] - } else { - const content = arg.slice(1) - if (!content) throw new UsageError(arg) - return ['-', content] - } - default: - return [undefined, arg] - } -} diff --git a/src/prefab/construct-env.test.ts b/src/prefab/construct-env.test.ts deleted file mode 100644 index 09b43a90..00000000 --- a/src/prefab/construct-env.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { assertEquals, assertThrows } from "@std/assert" -import specimen, { _internals } from "./construct-env.ts" -import { Path, SemVer, semver, hooks } from "pkgx" -import * as mock from "@std/testing/mock" - -Deno.test({ - name: "construct_env.ts", - // libpkgx loads sqlite3 dynamically during this test - // libpkgx considers this a global resource that is acceptable to leak - // we would prefer it didn't, certainly this test doesn't need to by using a real pantry or pantry-cache - sanitizeResources: false, -async fn(runner) { - const pkg1 = { project: 'foo', constraint: new semver.Range("^2")} - const pkg2 = { project: 'bar', constraint: new semver.Range("~3.1")} - - await runner.step("std", async () => { - const stub2 = mock.stub(_internals, "runtime_env", async () => ({ - FOO_FLAGS: "bar $FOO_FLAGS baz", - PATH: "/opt/bin:$PATH", - MANPATH: "/man", - LIBRARY_PATH: "/lib", - })) - - const tmp = Path.mktemp() - - const installations = [ - { pkg: {project: pkg1.project, version: new SemVer("2.3.4")}, path: tmp.join("foo/v2.3.4/bin").mkdir('p').parent() }, - { pkg: {project: pkg2.project, version: new SemVer("3.1.4")}, path: tmp.join("bar/v3.1.4/include").mkdir('p').parent() }, - { pkg: {project: "cmake.org", version: new SemVer("3.1.4")}, path: tmp.join("cmake.org/v3.1.4/lib").mkdir('p').parent() }, - { pkg: {project: "gnu.org/autoconf", version: new SemVer("4.5.6")}, path: tmp.join("gnu.org/autoconf/v4.5.6/share/aclocal").mkdir('p').parent().parent() }, - ] - - try { - const rv = await specimen({ installations }) - assertEquals(rv['FOO_FLAGS'], "bar bar bar bar ${FOO_FLAGS} baz${FOO_FLAGS} baz${FOO_FLAGS} baz${FOO_FLAGS} baz") //FIXME lol - assertEquals(rv['PATH'], `/opt/bin:${tmp.join('foo/v2.3.4/bin')}\${PATH:+:$PATH}`) - } finally { - stub2.restore() - tmp.rm({recursive: true}) - } - }) - - await runner.step("coverage++", async () => { - const pkg = { project: "bytereef.org/mpdecimal", version: new SemVer("2.1.2") } - if (hooks.usePantry().missing()) { - assertThrows(() => _internals.runtime_env(pkg, [])) - } else { - await _internals.runtime_env(pkg, []) - } - }) - - await runner.step("multiple versions", async () => { - const tmp = Path.mktemp() - - const installations = [ - { pkg: {project: "unicode.org", version: new SemVer("71.0.0")}, path: tmp.join("unicode.org/v71.0.0").mkdir('p') }, - { pkg: {project: "unicode.org", version: new SemVer("73.0.0")}, path: tmp.join("unicode.org/v73.0.0").mkdir('p') }, - ] - const stub3 = mock.stub(_internals, "runtime_env", async () => ({})) - try { - await _internals.mkenv({ installations }) - } finally { - stub3.restore() - tmp.rm({recursive: true}) - } - }) -}}) diff --git a/src/prefab/construct-env.ts b/src/prefab/construct-env.ts deleted file mode 100644 index c6b2dd2c..00000000 --- a/src/prefab/construct-env.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Installation, Package, hooks, utils } from "pkgx" -const { usePantry } = hooks -const { host } = utils - -export default async function(pkgenv: { installations: Installation[] }) { - const rv = await mkenv({...pkgenv}) - - // don’t break `man` lol - //TODO don’t add if already there obv - if (rv["MANPATH"]) { - rv["MANPATH"] = `${rv["MANPATH"]}:/usr/share/man` - } - - // makes libraries precise rather than having them use their rpaths - //FIXME SIP on macOS prevents DYLD_FALLBACK_LIBRARY_PATH from propagating to grandchild processes - if (rv.LIBRARY_PATH) { - rv.LD_LIBRARY_PATH = rv.LIBRARY_PATH.replace('$LIBRARY_PATH', '${LD_LIBRARY_PATH}') - if (host().platform == 'darwin') { - // non FALLBACK variety causes strange issues in edge cases - // where our symbols somehow override symbols from the macOS system - rv.DYLD_FALLBACK_LIBRARY_PATH = rv.LIBRARY_PATH.replace('$LIBRARY_PATH', '${DYLD_FALLBACK_LIBRARY_PATH}') - } - } - - for (const key in rv) { - rv[key] = rv[key].replaceAll(new RegExp(`\\$${key}\\b`, 'g'), `\${${key}}`) - // don’t end with a trailing `:` since that is sometimes interpreted as CWD and can break things - // instead of `foo:${PATH}` we end up with `foo${PATH:+:PATH}` which is not POSIX but works - // with all the shells that we support shellcode for and avoids a trailing `:` - // NOTE this may not work with FISH though. - rv[key] = rv[key].replaceAll(new RegExp(`:+\\$\{${key}}$`, 'g'), `\${${key}:+:$${key}}`) - } - - return rv -} - -///////////////////////// reworked from useShellEnv needs porting back to libpkgx -async function mkenv({installations}: {installations: Installation[]}) { - const projects = new Set(installations.map(x => `${x.pkg.project}@${x.pkg.version}`)) - - // we need to do this as well, since we allow multiple versions of e.g. unicode.org. - // however, if we *just* use this as `projects`, then tests below like `projects.has('cmake.org')` will fail - const projects_with_versions = new Set(installations.map(x => `${x.pkg.project}@${x.pkg.version}`)) - console.assert(projects_with_versions.size == installations.length, "pkgx: env is being duped") - - const common_vars: Record> = {} - const common_keys = new Set() - - for (const { path } of installations) { - - const test = (part: string, key: string) => { - const d = path.join(part).isDirectory() - if (!d) return - if (!common_vars[key]) common_vars[key] = new OrderedSet() - common_vars[key].add(d.string) - common_keys.add(key) - } - - test("bin", 'PATH') - test("include", 'CPATH') - test("lib", 'LIBRARY_PATH') - test('lib/pkgconfig', 'PKG_CONFIG_PATH') - test("man", "MANPATH") - test("sbin", 'PATH') - test('share', 'XDG_DATA_DIRS') - test("share/man", "MANPATH") - test('share/pkgconfig', 'PKG_CONFIG_PATH') - - if (projects.has('cmake.org')) { - test('', 'CMAKE_PREFIX_PATH') - } - - if (projects.has('gnu.org/autoconf')) { - test("share/aclocal", 'ACLOCAL_PATH') - } - } - - const rv: Record = {} - - for (const { pkg } of installations) { - const runtime_env = await _internals.runtime_env(pkg, installations) - for (const key in runtime_env) { - const value = runtime_env[key] - - if (common_keys.has(key)) { - // if the package has specific env for a key we handle ourselves we treat it differently - - const new_set = new OrderedSet() - let superkey_present = false - for (const part of value.split(":")) { - if (part == `$${key}`) { - common_vars[key].toArray().forEach(x => new_set.add(x)) - superkey_present = true - } else { - new_set.add(part) - } - } - // we don’t care if the package author didn’t include the superkey - // we are not going to throw away all the other env lol! - if (!superkey_present) { - new_set.add_all(common_vars[key]) - } - - common_vars[key] = new_set - - } else { - const rx = new RegExp(`(\\$${key})(\\b)`, 'g') - if (rx.test(value)) { - if (rv[key]) { - // replace eg. `foo:$FOO:bar` with `foo:${existing}:$FOO:bar` - rv[key] = value.replaceAll(rx, (_, a, b) => `${b}${rv[key]}${b}${a}`) - } else { - rv[key] = value - } - } else { - //NOTE this means we may replace user-specified env - //TODO show warning! - rv[key] = value - } - } - } - } - - for (const key of common_keys) { - if (!common_vars[key].isEmpty()) { - rv[key] = [...common_vars[key].toArray(), `$${key}`].join(':') - } - } - - return rv -} - -////////////////////////////////////////////////////////////////////////// utils -class OrderedSet { - private items: T[]; - private set: Set; - - constructor(items: T[] = []) { - this.items = items; - this.set = new Set(); - } - - add(item: T): void { - if (!this.set.has(item)) { - this.items.push(item); - this.set.add(item); - } - } - - add_all(items: OrderedSet) { - for (const item of items.items) { - this.add(item) - } - } - - toArray(): T[] { - return [...this.items]; - } - - isEmpty(): boolean { - return this.items.length == 0 - } -} - -////////////////////////////////////////////////////////////////////// internals -export const _internals = { - runtime_env: (pkg: Package, installations: Installation[]) => usePantry().project(pkg.project).runtime.env(pkg.version, installations), - mkenv -} diff --git a/src/prefab/failsafe.test.ts b/src/prefab/failsafe.test.ts deleted file mode 100644 index a7d52d78..00000000 --- a/src/prefab/failsafe.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// deno-lint-ignore-file require-await -import { assertEquals } from "@std/assert" -import specimen, { _internals } from "./failsafe.ts" -import { assertRejects } from "@std/assert" -import * as mock from "@std/testing/mock" -import { PackageNotFoundError } from "pkgx" - -Deno.test("failsafe.ts", async runner => { - const stub = mock.stub(_internals, "useSync", () => Promise.resolve()) - - try { - await runner.step("retries", async () => { - let i = 0 - const rv = await specimen(async () => { - if (i++ === 0) { - throw new PackageNotFoundError("foo.com") - } else { - return "ok" - } - }) - assertEquals(rv, "ok") - }) - - await runner.step("throws", async () => { - let i = 0 - await assertRejects(() => specimen(async () => { - throw new Error() - })) - }) - - } finally { - stub.restore() - } -}) diff --git a/src/prefab/failsafe.ts b/src/prefab/failsafe.ts deleted file mode 100644 index c72cc967..00000000 --- a/src/prefab/failsafe.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { PackageNotFoundError, hooks } from "pkgx" -import Logger from "../utils/Logger.ts" -const { useSync } = hooks - -export default async function failsafe(body: () => Promise): Promise { - try { - return await body() - } catch (err) { - if (err instanceof PackageNotFoundError) { - const logger: Logger = new Logger('sync') - const printer = { - syncing() { - logger.replace('fetching') - }, - caching() { - logger.replace('caching') - }, - syncd() { - logger.clear() - } - } - await _internals.useSync(printer) - return body() - } else { - throw err - } - } -} - -export const _internals = { - useSync -} diff --git a/src/prefab/install.test.ts b/src/prefab/install.test.ts deleted file mode 100644 index b24710f3..00000000 --- a/src/prefab/install.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -// deno-lint-ignore-file no-explicit-any require-await -import { assert, assertEquals, assertFalse } from "@std/assert" -import { null_logger as logger } from "../utils/test-utils.ts" -import specimen, { _internals } from "./install.ts" -import hydrate from "pkgx/plumbing/hydrate.ts" -import * as mock from "@std/testing/mock" -import { Path, SemVer, semver } from "pkgx" - -Deno.test("install.ts", () => { - const gas = [ - { project: "bar.org", version: new SemVer("1.2.3") }, - { project: "foo.com", version: new SemVer("2.3.4") }, - { project: "baz.net", version: new SemVer("3.4.5") } - ] - - const stub1 = mock.stub(_internals, "getproj", ({ project }: any) => ({ - companions: async () => { - assertEquals(project, "bar.org") - return [{project: "foo.com", constraint: new semver.Range("~2.1") }] - } - }) as any) - const stub2 = mock.stub(_internals, "hydrate", async input => { - return hydrate(input, async ({project}: any) => { - switch (project) { - case "foo.com": - return [{ - project: "baz.net", constraint: new semver.Range("^3") - }] - case "bar.org": - return [] - case "baz.net": - return [] - default: - throw new Error() - } - }) - }) - const stub3 = mock.stub(_internals, "resolve", async (wet, opts) => { - assertFalse(opts!.update) - const projects = new Set(wet.map(({project}) => project)) - assertEquals(projects, new Set(gas.map(({project}) => project))) - return { - pkgs: gas, - pending: gas.slice(0, 2), - installed: [{ - pkg: gas[2], - path: Path.root - }] - } - }) - let i = 0 - const set = new Set() - const stub4 = mock.stub(_internals, "install", async pkg => { - assertEquals(pkg, gas[i++]) - set.insert(pkg.project) - return { pkg, path: Path.root } - }) - - const stub5 = mock.stub(_internals, "link", async ({pkg}: any) => { - assert(set.delete(pkg.project)) - }) - - try { - const opts = { update: false, logger } - specimen([{ project: "bar.org", constraint: new semver.Range("^1") }], opts) - } finally { - stub1.restore() - stub2.restore() - stub3.restore() - stub4.restore() - stub5.restore() - } -}) diff --git a/src/prefab/install.ts b/src/prefab/install.ts deleted file mode 100644 index 0c76168e..00000000 --- a/src/prefab/install.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { PackageSpecification, hooks, plumbing, Package } from "pkgx" -import { Logger as BaseLogger } from "pkgx/plumbing/install.ts" -import failsafe from "./failsafe.ts"; - -const { resolve, hydrate, install, link } = plumbing -const { usePantry } = hooks - -export type Logger = { - replace(string: string): void - clear(): void - upgrade(dry: PackageSpecification[], pending: Package[]): BaseLogger | undefined -} - -interface Options { - update: boolean | Set, - logger: Logger -} - -export default async function(dry: PackageSpecification[], { logger, ...opts }: Options) { - logger.replace("resolving graph…") - - try { - const { resolve, install, link, getproj, hydrate } = _internals - - const companions = (await Promise.all(dry.map(pkg => getproj(pkg).companions()))).flatMap(x => x) - - const { pkgs: wet, dry: pkgenv_ } = await failsafe(() => hydrate(dry.concat(companions))) - const { pending, installed: installations } = await resolve(wet, opts) - - const superlogger = logger.upgrade(dry, pending) - - const installers = pending.map(pkg => install(pkg, superlogger).then(i => link(i).then(() => i))) - installations.push(...await Promise.all(installers)) - - const pkgenv = pkgenv_.filter(({project}) => companions.some(x => x.project == project) == false) - - return { - installations, - pkgenv - } - - } finally { - logger.clear() - } -} - -export const _internals = { - hydrate, resolve, install, link, - getproj: usePantry().project -} diff --git a/src/prefab/parse-pkg-str.test.ts b/src/prefab/parse-pkg-str.test.ts deleted file mode 100644 index ca2fa5fa..00000000 --- a/src/prefab/parse-pkg-str.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -import { ProvidesError, AmbiguityError } from "../utils/error.ts" -import { assertRejects, assertEquals } from "@std/assert" -import specimen, { _internals } from "./parse-pkg-str.ts" -import * as mock from "@std/testing/mock" - -Deno.test("parse-pkg-str.ts", async runner => { - - await runner.step("happy", async () => { - const stub = mock.stub(_internals, "find", () => Promise.resolve([{ project: "foo.com" } as any])) - try { - const rv = await specimen("foo") - assertEquals(rv.project, "foo.com") - assertEquals(rv.constraint.toString(), "*") - } finally { - stub.restore() - } - }) - - await runner.step("@latest", async () => { - const stub = mock.stub(_internals, "find", () => Promise.resolve([{ project: "foo.com" } as any])) - try { - const rv = await specimen("foo@latest", { latest: 'ok' }) - assertEquals(rv.project, "foo.com") - assertEquals(rv.constraint.toString(), "*") - assertEquals(rv.update, true) - } finally { - stub.restore() - } - }) - - await runner.step("ambiguous", async () => { - const stub = mock.stub(_internals, "find", () => Promise.resolve([1, 2] as any)) - try { - await assertRejects(() => specimen("foo"), AmbiguityError) - } finally { - stub.restore() - } - }) - - await runner.step("no provides", async () => { - const stub = mock.stub(_internals, "find", () => Promise.resolve([])) - try { - await assertRejects(() => specimen("foo"), ProvidesError) - } finally { - stub.restore() - } - }) -}) diff --git a/src/prefab/parse-pkg-str.ts b/src/prefab/parse-pkg-str.ts deleted file mode 100644 index 5be24ef1..00000000 --- a/src/prefab/parse-pkg-str.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ProvidesError, AmbiguityError } from "../utils/error.ts" -import { utils, hooks, PackageRequirement } from "pkgx" -import failsafe from "./failsafe.ts" - -export default async function(input: string, opts?: { latest: 'ok' }): Promise { - const { find } = _internals - - let update = false - - if (opts?.latest && input.endsWith("@latest")) { - input = input.slice(0, -7) - update = true - } - - const rawpkg = utils.pkg.parse(input) - - const projects = await failsafe(() => find(rawpkg.project)) - if (projects.length <= 0) throw new ProvidesError(input) - if (projects.length > 1) throw new AmbiguityError(input, projects) - - const project = projects[0].project //FIXME libpkgx forgets to correctly assign type - const constraint = rawpkg.constraint - - return { project, constraint, update } -} - -export const _internals = { - find: hooks.usePantry().find -} diff --git a/src/prefab/resolve-arg0.test.ts b/src/prefab/resolve-arg0.test.ts deleted file mode 100644 index cc49a5ac..00000000 --- a/src/prefab/resolve-arg0.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -import { assertRejects, assertEquals } from "@std/assert" -import specimen, { _internals } from "./resolve-arg0.ts" -import { AmbiguityError } from "../utils/error.ts" -import { faker_args } from "../utils/test-utils.ts" -import * as mock from "@std/testing/mock" -import { Path, semver, SemVer } from "pkgx" - -Deno.test("resolve-arg0.ts", async runner => { - await runner.step("ambiguous but only one installed", async () => { - //TODO really we should stub usePantry to return multiple pkgs with a provides set - - const pkg1 = { project: "foo.com", constraint: new semver.Range("^2") } - const pkg2 = { project: "bar.com", constraint: new semver.Range("^3") } - const args = faker_args() - const version = new SemVer("2.3.4") - - const stub1 = mock.stub(_internals, "which", () => Promise.resolve([{...pkg1, shebang: args.slice(0, 1) }, {...pkg2, shebang: [""] }])) - // deno-lint-ignore require-await - const stub2 = mock.stub(_internals, "has", async pkg => { - if ((pkg as any).project == pkg1.project) { - return {pkg: { project: pkg1.project, version }, path: new Path("/opt/foo")} - } - }) - - try { - const rv = await specimen(args[0], []) - assertEquals(rv!.project, pkg1.project) - } finally { - stub1.restore() - stub2.restore() - } - }) - - await runner.step("ambiguous but dry powder contains one", async () => { - const pkg1 = { project: "foo.com", constraint: new semver.Range("^2") } - const pkg2 = { project: "bar.com", constraint: new semver.Range("^3") } - const args = faker_args() - - const stub = mock.stub(_internals, "which", () => Promise.resolve([{...pkg1, shebang: args.slice(0, 1) }, {...pkg2, shebang: [""] }])) - - try { - const rv = await specimen(args[0], [pkg1]) - assertEquals(rv!.project, pkg1.project) - } finally { - stub.restore() - } - }) - - await runner.step("ambiguous and dry powder contains both", async () => { - const pkg1 = { project: "foo.com", constraint: new semver.Range("^2") } - const pkg2 = { project: "bar.com", constraint: new semver.Range("^3") } - const args = faker_args() - - const stub = mock.stub(_internals, "which", () => Promise.resolve([{...pkg1, shebang: args.slice(0, 1) }, {...pkg2, shebang: [""] }])) - - try { - await assertRejects(() => specimen(args[0], [pkg1, pkg2]), AmbiguityError) - } finally { - stub.restore() - } - }) - - await runner.step("AmbiguityError", async () => { - //TODO really we should stub usePantry to return multiple pkgs with a provides set - - const pkg1 = { project: "foo.com", constraint: new semver.Range("^2") } - const pkg2 = { project: "bar.com", constraint: new semver.Range("^3") } - - const stub1 = mock.stub(_internals, "which", () => Promise.resolve([{...pkg1, shebang: [""] }, {...pkg2, shebang: [""] }])) - - try { - const args = faker_args() - await assertRejects(() => specimen(args[0], []), AmbiguityError) - } finally { - stub1.restore() - } - }) -}) diff --git a/src/prefab/resolve-arg0.ts b/src/prefab/resolve-arg0.ts deleted file mode 100644 index 31708780..00000000 --- a/src/prefab/resolve-arg0.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { plumbing, hooks, semver, PackageRequirement } from "pkgx" -import { AmbiguityError } from "../utils/error.ts" -const { useCellar } = hooks -const { which } = plumbing - -export default async function(arg0: string, dry: PackageRequirement[]): Promise<{ project: string, constraint: semver.Range, shebang: string[] } | undefined> { - const { has, which } = _internals - - let pkgs = await which(arg0, { providers: true, all: true }) - if (pkgs.length > 1) { - // there are multiple results for this “provides” - // if the user specified one of them then use it - const set = new Set(dry.map(pkg => pkg.project)) - const powder = pkgs.filter(x => set.has(x.project)) - if (powder.length == 1) { - return powder[0] - } else if (powder.length > 1) { - throw new AmbiguityError(arg0, powder) - } - - // if only one of them is installed then pick that - // ∵ the user already made a choice - const installs = (await Promise.all(pkgs.map(has))).compact() - if (installs.length !== 1) { - throw new AmbiguityError(arg0, pkgs) - } else for (const pkg of pkgs) { - if (installs[0]!.pkg.project == pkg.project) { - pkgs = [pkg] - break - }} - } - return pkgs[0] -} - -export const _internals = { - which, - has: useCellar().has -} diff --git a/src/utils/InstallLogger.ts b/src/utils/InstallLogger.ts deleted file mode 100644 index 524201b1..00000000 --- a/src/utils/InstallLogger.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { PackageSpecification, Package, utils, Path, Installation } from "pkgx" -import { Logger as BaseInstallLogger } from "pkgx/plumbing/install.ts" -import { blurple, inverse_blurple, dim } from "./color.ts" -import useConfig from "pkgx/hooks/useConfig.ts" -import Logger from "./Logger.ts" - -export default class implements BaseInstallLogger { - projects: string[] - rcvd: Record = {} - totals: Record = {} - progress: Record = {} - logger: Logger - start = Date.now() - installed_count: number - context: string - - constructor(dry: PackageSpecification[], pkgs: Package[], logger: Logger) { - this.projects = pkgs.map(x => x.project) - this.installed_count = 0 - this.context = dry.map(utils.pkg.str).join(', ') - for (const project in this.projects) { - this.rcvd[project] = 0 - this.totals[project] = 0 - } - this.logger = logger - } - - downloading({ pkg: { project }, rcvd, total }: { pkg: Package; src?: URL|undefined; dst?: Path|undefined; rcvd?: number|undefined; total?: number|undefined; }): void { - if (!rcvd || !total) return - this.rcvd[project] = rcvd - this.totals[project] = total - this.update() - } - - installing({ pkg: { project }, progress }: { pkg: Package; progress: number|undefined; }): void { - if (!progress) return - this.progress[project] = progress - this.update() - } - - locking(pkg: Package): void { - if (!this.total_progress()) { - this.logger.replace(`${blurple("locking")} ${utils.pkg.str(pkg)}`) //TODO - } - } - unlocking(_pkg: Package): void {} - - installed(installation: Installation): void { - this.installed_count++ - log_installed_msg(installation.pkg, inverse_blurple(' ✓ '), this.logger) - this.logger.newln() - this.update() - } - - private total_rcvd(): number { - return Object.values(this.rcvd).reduce((a, b) => a + b, 0) - } - - private total_progress(): number { - let total_untard_bytes = 0 - for (const project of this.projects) { - const bytes = this.progress[project] * this.totals[project] - total_untard_bytes += bytes - } - return total_untard_bytes / Object.values(this.totals).reduce((a, b) => a + b, 0) - } - - private update() { - let str = '' - - const pc = this.total_progress() - let prefix: string - if (!isNaN(pc)) { - str = `${(pc * 100).toFixed()}% ` - prefix = blurple('installing') - } else { - prefix = blurple('downloading') - } - - prefix += ` ${this.context} (${this.installed_count}⁄${this.projects.length})` - - const rcvd = this.total_rcvd() - const total = Object.values(this.totals).reduce((a, b) => a + b, 0) - - const speed = this.total_rcvd() / (Date.now() - this.start) * 1000 - str += dim(`${pretty_size(speed)[0]}/s`) - - if (rcvd && total) { - const [pretty_total, divisor] = pretty_size(total, 0) - const n = rcvd / divisor - const pretty_rcvd = n.toFixed(precision(n)) - str += dim(` ${pretty_rcvd}/${pretty_total}`) - } - - this.logger.replace(`${prefix} ${str}`) - } -} - -////////////////// utils ////////////////// - -function pretty_size(n: number, fixed?: number): [string, number] { - const units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] - let i = 0 - let divisor = 1 - while (n > 1024) { - n /= 1024 - i++ - divisor *= 1024 - } - return [`${n.toFixed(fixed ?? precision(n))} ${units[i]}`, divisor] -} - -function precision(n: number) { - return n < 10 ? 2 : n < 100 ? 1 : 0 -} - -const log_installed_msg = (pkg: Package, title: string, logger: Logger) => { - const { prefix } = useConfig() - const pkg_prefix_str = (pkg: Package) => [ - dim(prefix.prettyString()), - pkg.project, - `${dim('v')}${pkg.version}` - ].join(dim('/')) - - const str = pkg_prefix_str(pkg) - logger!.replace(`${title} ${str}`, { prefix: false }) -} diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts deleted file mode 100644 index 3daac5b9..00000000 --- a/src/utils/Logger.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { stripAnsiCode } from "@std/fmt/colors" -import { ansi } from "@cliffy/ansi" -import { dim } from "./color.ts" - -export default class Logger { - lines = 0 - prefix?: string - - constructor(prefix?: string) { - this.prefix = prefix ? `${prefix}: ` : undefined - } - - //TODO don’t erase whole lines, just erase the part that is different - replace(line: string, opts: { prefix: boolean } = {prefix: true}) { - if (opts.prefix && this.prefix) { - line = `${dim(this.prefix)}${line}` - } - - Deno.stderr.writeSync(this._clear().text(line).bytes()) - this.lines += ln(line) - } - - private _clear() { - try { - let rv = ansi.eraseLineStart.cursorTo(0) - if (this.lines > 0) { - rv = rv.cursorUp(this.lines).eraseDown(this.lines) - } - return rv - } finally { - this.lines = 0 - } - } - - clear() { - Deno.stderr.writeSync(this._clear().bytes()) - this.lines = 0 - } - - newln() { - console.error() - this.lines = 0 - } -} - -function ln(s: string) { - try { - const { columns } = Deno.consoleSize() - if (columns == 0) return 0 - // remove ansi escapes to get actual length - const n = stripAnsiCode(s).length - return Math.floor(n / columns) - } catch { - // consoleSize() throws if not a tty - // eg. in GitHub Actions - return 1 - } -} diff --git a/src/utils/announce.ts b/src/utils/announce.ts deleted file mode 100644 index 689fb9a6..00000000 --- a/src/utils/announce.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { dim, blurple } from './color.ts' - -export default function({ title, subtitle, body, help, color }: {title: string, subtitle?: string | undefined, body: (string[])[], help?: string | undefined, color?: (x: string) => string}) { - color ??= blurple - help ??= 'unknown-error' - - if (subtitle) { - console.error(`${color(`× %s`)} %s`, title, subtitle) - } else { - console.error(color('× %s'), title) - } - for (const [s1, ...ss] of body) { - console.error(`${color('│')} ${s1 ?? ''}`, ...ss) - } - - const url = help.startsWith('http') ? help : `https://docs.pkgx.sh/help/${help}` - console.error(`${color('╰─➤')} %s`, dim(url)) -} diff --git a/src/utils/clicolor.ts b/src/utils/clicolor.ts deleted file mode 100644 index 5de986e0..00000000 --- a/src/utils/clicolor.ts +++ /dev/null @@ -1,33 +0,0 @@ - -export default function(dst = Deno.stdout, env = Deno.env.toObject()) { - // interprets https://no-color.org - // see: https://deno.land/api@v1.37.1?s=Deno.noColor - if (Deno.noColor) { - return false - } - - //https://bixense.com/clicolors/ - - //NOTE we (mostly) only output colors to stderr hence the isTerminal() check for that - //FIXME not true for --help tho - if (env.CLICOLOR !== '0' && dst.isTerminal()) { - return true - } - if ((env.CLICOLOR_FORCE ?? '0') != '0') { - //https://bixense.com/clicolors/ - return true - } - - if (env.CLICOLOR == '0' || env.CLICOLOR_FORCE == '0') { - return false - } - if (env.CI) { - // this is what charm’s lipgloss does, we copy their lead - // however surely nobody wants `pkgx foo > bar` to contain color codes? - // the thing is otherwise we have no color in CI since it is not a TTY - //TODO probs should check the value of some of the TERM vars - return true - } - - return false -} diff --git a/src/utils/color.test.ts b/src/utils/color.test.ts deleted file mode 100644 index 60ede233..00000000 --- a/src/utils/color.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { blurple, dim, inverse_blurple } from "./color.ts"; - -Deno.test("color", async runner => { - - await runner.step("blurple", () => { - assertEquals(blurple("Hello"), "\x1b[38;5;63mHello\x1b[39m") - }) - - await runner.step("dim", () => { - const input = "Hello"; - const expected = "\x1b[2mHello\x1b[22m"; - - assertEquals(dim(input), expected); - }) - - await runner.step("inverse_blurple", () => { - assertEquals(inverse_blurple("Hello"), "\x1b[48;5;63mHello\x1b[49m"); - }) -}) diff --git a/src/utils/color.ts b/src/utils/color.ts deleted file mode 100644 index 55691b0a..00000000 --- a/src/utils/color.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { dim, rgb8, bgRgb8, red } from "@std/fmt/colors" - -export const blurple = (x: string) => rgb8(x, 63) -export { dim, red } -export const inverse_blurple = (x: string) => bgRgb8(x, 63) diff --git a/src/utils/devenv.test.ts b/src/utils/devenv.test.ts deleted file mode 100644 index 06df67c3..00000000 --- a/src/utils/devenv.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -// deno-lint-ignore-file require-await -import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert" -import specimen, { _internals } from "./devenv.ts" -import * as mock from "@std/testing/mock" -import { fixturesd } from "./test-utils.ts" -import { Path, utils } from "pkgx" - -Deno.test("devenv.ts", async runner => { - - const stub = mock.stub(_internals, "find", async pkg => utils.pkg.parse(pkg)) - - try { - await runner.step("supplementable fixtures", async test => { - // each of the files in this list must have a zlib.net^1.2 dependency and a FOO=BAR env - const keyfiles = [ - ["pkgx.yml"], - ["deno.json/std/deno.json", "deno.land"], - ["deno.json/arr/deno.json", "deno.land"], - ["deno.jsonc", "deno.land"], - ["package.json/std/package.json", "nodejs.org"], - ["package.json/str/package.json", "nodejs.org"], - ["package.json/arr/package.json", "nodejs.org"], - ["Cargo.toml", "rust-lang.org"], - ["Gemfile", "ruby-lang.org"], - ["pyproject.toml/std/pyproject.toml", "pip.pypa.io"], - ["pyproject.toml/poetry/pyproject.toml", "python-poetry.org"], - ["go.mod", "go.dev"], - ["requirements.txt", "pip.pypa.io"], - [".yarnrc", "classic.yarnpkg.com"], - ["pixi.toml", "prefix.dev"], - ["action.yml/std/action.yml", "nodejs.org^16"], - [".yarnrc.yml", "yarnpkg.com"], - ] - - for (const [keyfile, dep] of keyfiles) { - await test.step(`${keyfile}`, async () => { - const go = async (file: Path) => { - const { env, pkgs } = await specimen(file.parent()) - assert(pkgs.find(pkg => utils.pkg.str(pkg) == "zlib.net^1.2"), "should dep zlib^1.2") - if (dep) { - assert(pkgs.find(pkg => utils.pkg.str(pkg) == dep), `should dep ${dep}`) - } - - switch (keyfile) { - case 'package.json/str/package.json': - case 'package.json/arr/package.json': - case 'deno.json/arr/deno.json': - break // testing the short form for deps with these files - default: - assertEquals(env.FOO, "BAR") - } - } - - const target = fixturesd.join(keyfile).cp({ into: Path.mktemp() }) - await go(target) - await go(Path.mktemp().join(target.basename()).ln('s', { target })) - }) - } - }) - - await runner.step("fixed fixtures", async test => { - const keyfiles = [ - [ - 'package.json/engines/package.json', - 'nodejs.org~16.16.1', - 'npmjs.com~9.7.1', - 'yarnpkg.com~1.22.10', - 'pnpm.io~7.33.7', - ], - [ - 'package.json/packageManager/package.json', - 'pnpm.io@7.33.7', - 'nodejs.org' - ], - [ - 'package.json/volta/package.json', - 'nodejs.org@16.16.1', - 'npmjs.com@9.7.1', - 'yarnpkg.com@1.22.10', - 'pnpm.io@7.33.7', - ], - [".node-version", "nodejs.org@16.16.0"], - ["python-version/std/.python-version", "python.org~3.10"], - ["python-version/commented/.python-version", "python.org~3.11"], - [".ruby-version", "ruby-lang.org@3.2.1"], - [".terraform-version", "terraform.io@1.6.1"], - ["yarn.lock", "yarnpkg.com"], - ["bun.lockb", "bun.sh>=1"], - ["pyproject.toml/poetry-yaml-fm/pyproject.toml", "pip.pypa.io", "python~3.10"], - ["cdk.json", "aws.amazon.com/cdk"], - ] - - for (const [keyfile, ...deps] of keyfiles) { - await test.step(keyfile, async () => { - const file = fixturesd.join(keyfile).cp({ into: Path.mktemp() }) - const { env, pkgs } = await specimen(file.parent()) - - assertEquals(pkgs.length, deps.length) - - pkgs.forEach((pkg, i) => { - assertEquals(Object.keys(env).length, 0); - assertEquals(utils.pkg.str(pkg), deps[i]); - }); - }) - } - }) - - await runner.step("broken .python-version", async () => { - const file = fixturesd.join("python-version/broken/.python-version").cp({ into: Path.mktemp() }) - const { pkgs } = await specimen(file.parent()) - assertEquals(pkgs.length, 0) //NOTE this seems like dumb behavior - }) - - await runner.step("vcs", async test => { - const vcss = [ - ["git", "git-scm.org"], - ["hg", "mercurial-scm.org"], - ["svn", "apache.org/subversion"] - ] - - for (const [vcs, dep] of vcss) { - await test.step(vcs, async () => { - const d = Path.mktemp().join(`.${vcs}`).mkdir() - const { env, pkgs } = await specimen(d.parent()) - assertEquals(Object.keys(env).length, 0) - assertEquals(utils.pkg.str(pkgs[0]), dep) - }) - } - }) - - await runner.step("empty action.yml has no deps", async () => { - const { pkgs } = await specimen(fixturesd.join("action.yml/empty")) - assertEquals(pkgs.length, 0) - - const { pkgs: pkgs2 } = await specimen(fixturesd.join("action.yml/not-node")) - assertEquals(pkgs2.length, 0) - }) - - await runner.step("no dir error", async () => { - await assertRejects(() => specimen(new Path("/a/b/c/pkgx"))) - }) - - await runner.step("not error if no yaml fm", async () => { - const f = Path.mktemp().join('pyproject.toml').touch() - await specimen(f.parent()) - - f.rm().write({ text: "#---\n#---" }) - await specimen(f.parent()) - - // we don’t support invalid json just like npm won’t - f.parent().join("package.json").touch() - await assertRejects(() => specimen(f.parent())) - - f.parent().join("package.json").rm().write({text: "{}"}) - await specimen(f.parent()) - }) - - await runner.step("skips invalid deps node", async () => { - const f = Path.mktemp().join("package.json").rm().write({text: '{"pkgx": {"dependencies": true}}'}) - const { pkgs } = await specimen(f.parent()) - assertEquals(pkgs.length, 1) - assertEquals(pkgs[0].project, "nodejs.org") - }) - - await runner.step("skffold.yaml", async () => { - // test invalid skffold.yaml - const f = Path.mktemp().join('skaffold.yaml').touch() - f.parent().join("skaffold.yaml").rm().write({text: ""}) - const {env, pkgs} = await specimen(f.parent()) - // only skafflold.dev, no other dep expected - assert(pkgs.length === 1, "invalid skaffold.yaml should not return any dep") - - const keyfiles = [ - [ - 'skaffold.yaml/std/skaffold.yaml', - 'skaffold.dev', - 'kubernetes.io/kubectl', - 'helm.sh', - 'kpt.dev', - 'kubernetes.io/minikube', - 'docker.com/cli', - 'kubernetes.io/kustomize' - ], - [ - 'skaffold.yaml/empty/skaffold.yaml', - 'skaffold.dev' - ], - [ - 'skaffold.yaml/manifests/skaffold.yaml', - 'skaffold.dev', - 'helm.sh', - 'kpt.dev', - 'kubernetes.io/kustomize' - ], - ] - - for (const [keyfile, ...deps] of keyfiles) { - const file = fixturesd.join(keyfile).cp({into: Path.mktemp()}) - const {env, pkgs} = await specimen(file.parent()) - assert(pkgs.length === deps.length, `dependencies length differ, required: ${deps.length}, actual: ${pkgs.length}`) - deps.every(dep => { - assert(pkgs.find(pkg => utils.pkg.str(pkg) == dep), "should dep " + dep) - }) - } - }) - - } finally { - stub.restore() - } - - await runner.step("validateDollarSignUsage", () => { - assertThrows(() => _internals.validateDollarSignUsage("foo $(bar) baz")) - assertThrows(() => _internals.validateDollarSignUsage("foo $123 baz")) - - _internals.validateDollarSignUsage("foo $bar baz") - _internals.validateDollarSignUsage("foo $BAR baz") - _internals.validateDollarSignUsage("foo $B0AR baz") - _internals.validateDollarSignUsage("foo z${FOO}s baz") - }) -}) diff --git a/src/utils/devenv.ts b/src/utils/devenv.ts deleted file mode 100644 index 825eb4c5..00000000 --- a/src/utils/devenv.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { PlainObject, isArray, isNumber, isPlainObject, isString } from "is-what" -import { PackageRequirement, Path, semver, hooks, utils } from "pkgx" -import { validatePackageRequirement } from "pkgx/hooks/usePantry.ts" -import parse_pkg_str from "../prefab/parse-pkg-str.ts" -import readLines from "pkgx/utils/read-lines.ts" -import { parse as parseYaml } from "@std/yaml" -import { ProgrammerError } from "./error.ts" -import * as JSONC from "@std/jsonc" -const { useMoustaches } = hooks - -export default async function(dir: Path) { - if (!dir.isDirectory()) { - throw new ProgrammerError(`not a directory: ${dir}`) - } - - const constraint = new semver.Range('*') - let has_package_json = false - - const pkgs: PackageRequirement[] = [] - const env: Record = {} - - for await (const [path, {name, isFile, isSymlink, isDirectory}] of dir.ls()) { - if (isFile || isSymlink) { - switch (name) { - case "deno.json": - case "deno.jsonc": - await deno(path) - break - case ".nvmrc": - case ".node-version": - await version_file(path, 'nodejs.org') - break - case ".ruby-version": - await version_file(path, 'ruby-lang.org') - break - case ".python-version": - await python_version(path) - break - case ".terraform-version": - await terraform_version(path) - break - case "package.json": - await package_json(path) - break - case "action.yml": - case "action.yaml": - await github_actions(path) - break - case "Cargo.toml": - pkgs.push({project: "rust-lang.org", constraint}) - await read_YAML_FM(path) //TODO use dedicated TOML section in preference - break - case "skaffold.yaml": - pkgs.push({project: "skaffold.dev", constraint}) - await skaffold_yaml(path) - break - case "go.mod": - case "go.sum": - pkgs.push({project: "go.dev", constraint}) - await read_YAML_FM(path) - break - case "requirements.txt": - case "pipfile": - case "pipfile.lock": - case "setup.py": - pkgs.push({project: "pip.pypa.io", constraint}) - await read_YAML_FM(path) - break - case "pyproject.toml": - await pyproject(path) - break - case "Gemfile": - pkgs.push({project: "ruby-lang.org", constraint}) - await read_YAML_FM(path) - break - case ".yarnrc": - pkgs.push({ project: "classic.yarnpkg.com", constraint }) - await read_YAML_FM(path) - break - case "yarn.lock": - pkgs.push({ project: "yarnpkg.com", constraint }) - break - case ".yarnrc.yml": - pkgs.push({ project: "yarnpkg.com", constraint }) - await read_YAML_FM(path) - break - case "bun.lockb": - pkgs.push({ project: "bun.sh", constraint: new semver.Range(">=1") }) - break - case "pnpm-lock.yaml": - pkgs.push({ project: "pnpm.io", constraint }) - break - case "pixi.toml": - pkgs.push({ project: 'prefix.dev', constraint }) - await read_YAML_FM(path) - break - case "pkgx.yml": - case "pkgx.yaml": - case ".pkgx.yml": - case ".pkgx.yaml": - await parse_well_formatted_node(await path.readYAML()) - break - case "cdk.json": - pkgs.push({ project: 'aws.amazon.com/cdk', constraint }) - break - case "justfile": - case "Justfile": - pkgs.push({ project: 'just.systems', constraint }) - break - case "Taskfile.yml": - pkgs.push({ project: 'taskfile.dev', constraint }) - break - } - } else if (isDirectory) { - switch (name) { - case ".git": - pkgs.push({project: "git-scm.org", constraint}) - break - case ".hg": - pkgs.push({project: "mercurial-scm.org", constraint}) - break - case ".svn": - pkgs.push({project: "apache.org/subversion", constraint}) - break - } - } - } - - if (has_package_json && !pkgs.some(pkg => pkg.project === 'bun.sh') && !pkgs.some(pkg => pkg.project === 'nodejs.org')) { - pkgs.push({project: "nodejs.org", constraint}) - } - - return { pkgs, env } - - //---------------------------------------------- parsers - async function deno(path: Path) { - pkgs.push({project: "deno.land", constraint}) - const json = JSONC.parse(await path.read()) - // deno-lint-ignore no-explicit-any - if (isPlainObject(json) && (json as any).pkgx) { - // deno-lint-ignore no-explicit-any - let node = (json as any).pkgx - if (isString(node) || isArray(node)) node = { dependencies: node } - await parse_well_formatted_node(node) - } - } - - async function version_file(path: Path, project: string) { - let s = (await path.read()).trim() - if (s.startsWith('v')) s = s.slice(1) // v prefix has no effect but is allowed - s = `${project}@${s}` - pkgs.push(utils.pkg.parse(s)) - } - - async function python_version(path: Path) { - const s = (await path.read()).trim() - const lines = s.split("\n") - for (let l of lines) { - l = l.trim() - if (!l) continue // skip empty lines - if (l.startsWith('#')) continue // skip commented lines - // TODO: How to handle 'system'? - // TODO: How to handle non-bare versions like pypy3.9-7.3.11, stackless-3.7.5, etc. in pyenv install --list? - l = `python.org@${l}` - try { - pkgs.push(utils.pkg.parse(l)) - break // only one thanks - } catch { - //noop pyenv sticks random shit in here - } - } - } - - async function terraform_version(path: Path) { - const terraform_version = (await path.read()).trim() - const package_descriptor = `terraform.io@${terraform_version}` - pkgs.push(utils.pkg.parse(package_descriptor)) - } - - async function package_json(path: Path) { - const json = JSON.parse(await path.read()); - let node = json?.pkgx; - if (isString(node) || isArray(node)) node = { dependencies: node } - if (!node) { - if (json?.engines) { - node = { - dependencies: { - ...(json.engines.node && { 'nodejs.org': json.engines.node }), - ...(json.engines.npm && { 'npmjs.com': json.engines.npm }), - ...(json.engines.yarn && { 'yarnpkg.com': json.engines.yarn }), - ...(json.engines.pnpm && { 'pnpm.io': json.engines.pnpm }), - }, - }; - } - if (json?.packageManager) { // corepack - // example: "pnpm@7.33.7+sha256.d1581d46ed10f54ff0cbdd94a2373b1f070202b0fbff29f27c2ce01460427043" - const match = json.packageManager.match(/^(?[^@]+)@(?[^+]+)/); - - if (match) { - const { pkg, version } = match.groups as { pkg: string, version: string }; - - switch (pkg) { - case 'npm': - node = { - dependencies: { - 'npmjs.com': version, - }, - }; - break; - case 'yarn': - node = { - dependencies: { - 'yarnpkg.com': version, - }, - }; - break; - case 'pnpm': - node = { - dependencies: { - 'pnpm.io': version, - }, - }; - break; - }; - } - } - if (json?.volta) { - node = { - dependencies: { - ...(json.volta.node && { 'nodejs.org': json.volta.node }), - ...(json.volta.npm && { 'npmjs.com': json.volta.npm }), - ...(json.volta.yarn && { 'yarnpkg.com': json.volta.yarn }), - ...(json.volta.pnpm && { 'pnpm.io': json.volta.pnpm }), - } - } - } - } - await parse_well_formatted_node(node) - has_package_json = true - } - - async function skaffold_yaml(path: Path){ - const yaml = await path.readYAML() - if (!isPlainObject(yaml)) return - if (yaml.build?.local?.useDockerCLI?.toString() === true || yaml.deploy?.docker){ - pkgs.push({ - project: "docker.com/cli", - constraint: new semver.Range(`*`) - }) - } - if (yaml.deploy?.kubectl){ - pkgs.push({ - project: "kubernetes.io/kubectl", - constraint: new semver.Range(`*`) - }) - } - if (yaml.deploy?.kubeContext?.match("minikube")){ - pkgs.push({ - project: "kubernetes.io/minikube", - constraint: new semver.Range(`*`) - }) - } - if (yaml.deploy?.helm || yaml.manifests?.helm){ - pkgs.push({ - project: "helm.sh", - constraint: new semver.Range(`*`) - }) - } - if (yaml.deploy?.kpt || yaml.manifests?.kpt){ - pkgs.push({ - project: "kpt.dev", - constraint: new semver.Range(`*`) - }) - } - if (yaml.manifests?.kustomize){ - pkgs.push({ - project: "kubernetes.io/kustomize", - constraint: new semver.Range(`*`) - }) - } - } - - async function github_actions(path: Path) { - const yaml = await path.readYAML() - if (!isPlainObject(yaml)) return - const rv = yaml.runs?.using?.match(/node(\d+)/) - if (rv?.[1]) { - pkgs.push({ - project: "nodejs.org", - constraint: new semver.Range(`^${rv?.[1]}`) - }) - } - await parse_well_formatted_node(yaml.pkgx) - } - - async function pyproject(path: Path) { - //TODO parse the TOML lol! - - const content = await path.read() - if (content.includes("poetry.core.masonry.api")) { - pkgs.push({project: "python-poetry.org", constraint}) - } else { - //TODO other pkging systems…? - pkgs.push({project: "pip.pypa.io", constraint}) - } - await read_YAML_FM(path) - } - - //---------------------------------------------- YAML FM utils - - async function read_YAML_FM(path: Path) { - //TODO be smart with knowing the comment types - // this parsing logic should be in the pantry ofc - - //TODO should only parse blank lines and comments before bailing - // at the first non-comment line - - //TODO should be savvy to what comment type is acceptable! - - let yaml: string | undefined - const fd = await Deno.open(path.string, { read: true }) - try { - for await (const line of readLines(fd)) { - if (yaml !== undefined) { - if (/^((#|\/\/)\s*)?---(\s*\*\/)?$/.test(line.trim())) { - let node = parseYaml(yaml) - /// using a `pkgx` node is safer (YAML-FM is a free-for-all) but is not required - if (isPlainObject(node) && node.pkgx) { - node = isString(node.pkgx) || isArray(node.pkgx) ? { dependencies: node.pkgx } : node.pkgx - } - return await parse_well_formatted_node(node) - } - yaml += line?.replace(/^(#|\/\/)/, '') - yaml += "\n" - } else if (/^((\/\*|#|\/\/)\s*)?---/.test(line.trim())) { - yaml = '' - } - } - } finally { - fd.close() - } - } - - async function parse_well_formatted_node(obj: unknown) { - if (!isPlainObject(obj)) { - return //TODO diagnostics in verbose mode, error if `pkgx` node - } - - const yaml = await extract_well_formatted_entries(obj) - - for (let [k, v] of Object.entries(yaml.env)) { - if (isNumber(v)) v = v.toString() - if (isString(v)) { - //TODO provide diagnostics if verbose, throw if part of a `pkgx` node - env[k] = fix(v) - } - } - - pkgs.push(...yaml.deps) - - function fix(input: string): string { - const moustaches = useMoustaches() - - //TODO deprecate moustaches and instead use env vars - - const foo = [ - //FIXME ...moustaches.tokenize.host(), - { from: "home", to: Path.home().string }, //TODO deprecate and use $HOME once pantry is migrated - { from: "srcroot", to: dir.string } //TODO deprecate and use $PWD once pantry is migrated - ] - - const out = moustaches.apply(input, foo) - _internals.validateDollarSignUsage(out) - return out - } - } -} - -function validateDollarSignUsage(str: string): void { - let currentIndex = 0; - - while ((currentIndex = str.indexOf('$', currentIndex)) !== -1) { - const substring = str.substring(currentIndex); - - // Check for ${FOO} format - const isValidCurlyFormat = /^\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(substring); - // Check for $FOO format - const isValidDirectFormat = /^\$[A-Za-z_][A-Za-z0-9_]*/.test(substring); - - if (!isValidCurlyFormat && !isValidDirectFormat) { - throw new Error("Invalid dollar sign usage detected."); - } - - // Move past this $ instance - currentIndex++; - } -} - - -/// YAML-FM must be explicitly marked with a `dependencies` node -async function extract_well_formatted_entries(yaml: PlainObject): Promise<{ deps: PackageRequirement[], env: Record }> { - const deps = await parse_deps(yaml.dependencies) - const env = isPlainObject(yaml.env) ? yaml.env : {} //TODO provide diagnostics if verbose, throw if part of a `pkgx` node - return { deps, env } -} - -async function parse_deps(node: unknown) { - if (isString(node)) node = node.split(/\s+/).filter(x => x) - - function parse(input: string) { - // @latest means '*' here, we refuse to always check for newer versions - // that is up to the user to initiate, however we should allow the spec since - // users expect it. Maybe we should console.warn? - // discussion: https://github.com/pkgxdev/pkgx/issues/797 - if (input.endsWith('@latest')) input = input.slice(0, -6) - - return utils.pkg.parse(input) - } - - if (isArray(node)) node = node.map(parse).reduce((acc, curr) => { - acc[curr.project] = curr.constraint.toString() - return acc - }, {} as Record) - - if (!isPlainObject(node)) { - return [] //TODO provide diagnostics if verbose, throw if part of a `pkgx` node - } - - const pkgs = Object.entries(node) - .compact(([project, constraint]) => { - // see comment above in parse() about @latest - if (/^@?latest$/.test(constraint)) constraint = '*' - return validatePackageRequirement(project, constraint) - }) - .map(utils.pkg.str) //FIXME lol inefficient - .map(x => _internals.find(x)) - - return await Promise.all(pkgs) -} - -export const _internals = { - find: parse_pkg_str, - validateDollarSignUsage -} diff --git a/src/utils/error.ts b/src/utils/error.ts deleted file mode 100644 index 0ecb5cd5..00000000 --- a/src/utils/error.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { isString } from "is-what" -import { PkgxError } from "pkgx" -import undent from "outdent" - -export class AmbiguityError extends PkgxError { - projects: string[] - arg0: string - - constructor(arg0: string, pkgs: { project: string }[]) { - const projects = pkgs.map(x => x.project) - super(undent` - multiple projects provide \`${arg0}\`. please be more specific: - - ${projects.map(p => ` pkgx +${p} ${Deno.args.join(' ')}`).join('\n')} - `) - this.projects = projects - this.arg0 = arg0 - } -} - -export class ProvidesError extends PkgxError { - constructor(arg0: string) { - //TODO if arg0 is in the parent PATH then scan it and find it - //TODO render errors with markdown there must be a pkg for that - - super(undent` - # nothing provides \`${arg0}\` - - > we haven’t pkgd this yet, can you add it to the pantry? [docs.pkgx.sh/pantry] - `) - - this.arg0 = arg0 - } - - arg0: string -} - -export class UsageError extends PkgxError { - constructor(arg: string | {msg: string}) { - let msg: string - if (isString(arg)) { - msg = `no such arg: ${arg}` - } else { - msg = arg.msg - } - super(`usage error: ${msg}`) - } -} - -export class ProgrammerError extends Error -{} diff --git a/src/utils/execve.test.ts b/src/utils/execve.test.ts deleted file mode 100644 index b1b6e38c..00000000 --- a/src/utils/execve.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { assertThrows } from "@std/assert" -import execve, { _internals } from "./execve.ts" -import faker, { faker_args } from "./test-utils.ts" -import * as mock from "@std/testing/mock" -import { Path } from "pkgx" - -Deno.test("execve.ts", async runner => { - - await runner.step("happy path", () => { - const stub = mock.stub(_internals, "execve", () => { return 0 }) - try { - assertThrows( - () => execve({ cmd: faker_args(), env: {} }), - "execve (0)" - ) - } finally { - stub.restore() - } - }) - - await runner.step("relative path", () => { - const stub = mock.stub(_internals, "execve", () => { return 0 }) - try { - assertThrows( - () => execve({ cmd: ["./ls"], env: {} }), - "execve (0)" - ) - } finally { - stub.restore() - } - }) - - await runner.step("PATH parser", async () => { - const stub = mock.stub(_internals, "execve", () => { return 0 }) - const file = new Path(await Deno.makeTempFile()).chmod(0o700) - try { - assertThrows( - () => execve({ cmd: [file.basename()], env: {PATH: `.:~:~/foo:${file.parent()}`, "HOME": "/home/test"} }), - "execve (0)" - ) - } finally { - file.rm() - stub.restore() - } - }) - - - await runner.step("file doesn’t exist", () => { - assertThrows( - () => execve({ cmd: faker_args(), env: {} }), - Deno.errors.NotFound - ) - }) - - await runner.step("file is not executable", () => { - const args = faker_args() - const f = Path.mktemp().join(args[0]).touch() - args[0] = f.string - - assertThrows( - () => execve({ cmd: args, env: {} }), - Deno.errors.PermissionDenied - ) - }) - - await runner.step("no working directory", () => { - const stub = mock.stub(_internals, "getcwd", () => { throw new Error() }) - try { - assertThrows(() => execve({ cmd: ["foo", ...faker_args()], env: {} })) - } finally { - stub.restore() - } - }) - - const thisfile = new Path(new URL(import.meta.url).pathname) - - await runner.step("file exists but is not executable", () => { - const cmd = [thisfile.string, ...faker_args()] - assertThrows( - () => execve({ cmd, env: {} }), - Deno.errors.PermissionDenied - ) - }) - - await runner.step("file exists but is a directory", () => { - const cmd = [thisfile.parent().string, ...faker_args()] - assertThrows( - () => execve({ cmd, env: {} }), - Deno.errors.PermissionDenied - ) - }) - - await runner.step("ENAMETOOLONG", () => { - const cmd = [faker.string.alphanumeric({length: 1024*5})] - assertThrows( - () => execve({ cmd, env: {} }), - "execve (63)" - ) - }) -}) diff --git a/src/utils/execve.ts b/src/utils/execve.ts deleted file mode 100644 index 517ac148..00000000 --- a/src/utils/execve.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { ProgrammerError } from "./error.ts" -import { utils, Path, PkgxError } from "pkgx" -const { host } = utils - -export default function({cmd: args, env}: {cmd: string[], env: Record}): never { - /// we need to forward any other env, some vars like HOME are super important - /// but otherwise the user has set stuff so we should use it - for (const [key, value] of Object.entries(Deno.env.toObject())) { - env[key] ??= value - } - - find_in_PATH(args, env.PATH, Path.abs(env.HOME)) - - const path = cstr(args[0]) - const argv = new CStringArray(args) - const envp = new CStringArray(Object.entries(env).map(([key, value]) => `${key}=${value}`)) - - const errno = (() => { - let tries = 10; - while (true) { - const errno = _internals.execve( - Deno.UnsafePointer.of(path), - Deno.UnsafePointer.of(argv), - Deno.UnsafePointer.of(envp) - ); - if (!tries--) return errno; - // 11 = EAGAIN = Try again - if (errno == 11) continue; - return errno; - } - })(); - - switch (errno) { - case 2: //ENOENT: - case 22: //EINVAL: (on Linux we're getting this for file is not executable since Deno 2 :/) - case 316: //FIXME ALERT! ALERT! BUG! SOMETHING IS WRONG WITH OUR USE OR ERRNO AND THIS SOMETIMES RESULTS, USUALLY ON MACOS :/ - // yes: strange behavior from execve here indeed - if (parse_Path(args[0])?.exists()) { - throw new Deno.errors.PermissionDenied() - } else { - throw new Deno.errors.NotFound() - } - case 13: - throw new Deno.errors.PermissionDenied() - case 63: //ENAMETOOLONG: - case 7: //E2BIG: - case 14: //EFAULT: - case 5: //EIO: - case 62: //ELOOP: - case 8: //ENOEXEC: - case 12: //ENOMEM: - case 20: //ENOTDIR: - case 26: //ETXTBSY: - throw new PkgxError(`execve (${errno})`) - } - - throw new ProgrammerError(`execve (${errno})`) -} - -export function parse_Path(input: string) { - const p = Path.abs(input) - if (p) return p - try { - return _internals.getcwd().join(input) - } catch { - return - } -} - -function find_in_PATH(cmd: string[], PATH: string | undefined, HOME: Path | undefined) { - if (cmd[0].startsWith("/")) { - return - } - if (cmd[0].includes("/")) { - cmd[0] = _internals.getcwd().join(cmd[0]).string - return - } - - PATH ??= "/usr/bin:/bin" // see manpage for execvp(3) - - for (const part of PATH.split(':')) { - const path = (() => { - if (part == '.') return _internals.getcwd() - if (part == '~') return HOME - if (part.startsWith('~/')) return HOME?.join(part.slice(2)) - //FIXME: not handled: ~user/... - return new Path(part) - })()?.join(cmd[0]) - if (path?.isExecutableFile()) { - cmd[0] = path.string - return - } - } -} - - -////// COPY PASTA -// actually minor mods to add trailing null -// https://github.com/aapoalas/libclang_deno/blob/main/lib/utils.ts - -const ENCODER = new TextEncoder(); - -export class CStringArray extends Uint8Array { - constructor(strings: string[]) { - let stringsLength = 0; - for (const string of strings) { - // maximum bytes for a utf8 string is 4×length - stringsLength += string.length * 4 + 1; - } - super(8 * (strings.length + 1) + stringsLength); - const pointerBuffer = new BigUint64Array(this.buffer, 0, strings.length + 1); - const stringsBuffer = new Uint8Array(this.buffer).subarray( - (strings.length + 1) * 8, - ); - const basePointer = BigInt( - Deno.UnsafePointer.value(Deno.UnsafePointer.of(stringsBuffer)), - ); - let index = 0; - let offset = 0; - for (const string of strings) { - const start = offset; - const result = ENCODER.encodeInto( - string, - stringsBuffer.subarray(start), - ); - offset = start + result.written + 1; // Leave null byte - pointerBuffer[index++] = basePointer + BigInt(start); - } - } -} - -export const cstr = (string: string): Uint8Array => - ENCODER.encode(`${string}\0`); - -function execve(arg0: Deno.PointerValue, args: Deno.PointerValue, env: Deno.PointerValue) { - const filename = host().platform == 'darwin' ? '/usr/lib/libSystem.dylib' : 'libc.so.6' - - const libc = Deno.dlopen( - filename, { - execve: { - parameters: ["pointer", "pointer", "pointer"], - result: "i32" - }, - errno: { - type: "i32" - } - } - ) - - try { - libc.symbols.execve(arg0, args, env) - return libc.symbols.errno - } finally { - libc.close() - } -} - -export const _internals = { - execve, - getcwd: Path.cwd -} diff --git a/src/utils/get-shebang.test.ts b/src/utils/get-shebang.test.ts deleted file mode 100644 index efdce6fb..00000000 --- a/src/utils/get-shebang.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { assertEquals, assertRejects } from "@std/assert" -import specimen from "./get-shebang.ts" -import undent from "outdent" -import { Path } from "pkgx" - -Deno.test("get-shebang.ts", async runner => { - await runner.step("/usr/bin/python", async () => { - const f = Path.mktemp().join("script").write({text: undent` - #!/usr/bin/python - `}) - const shebang = await specimen(f) - assertEquals(shebang,["python"]) - }) - - await runner.step("/usr/bin/env python", async () => { - const f = Path.mktemp().join("script").write({text: undent` - #!/usr/bin/env python - `}) - const shebang = await specimen(f) - assertEquals(shebang, ["python"]) - }) - - await runner.step("/usr/bin/env -S python", async () => { - const f = Path.mktemp().join("script").write({text: undent` - #!/usr/bin/env -S python - `}) - const shebang = await specimen(f) - assertEquals(shebang, ["python"]) - }) - - await runner.step("bin/foo", async () => { - const f = Path.mktemp().join("script").write({text: undent` - #!bin/foo - `}) - await assertRejects(() => specimen(f)) - }) - - await runner.step("no shebang but not empty", async () => { - const f = Path.mktemp().join("script").write({text: undent` - print(1) - print(2) - `}) - const shebang = await specimen(f) - assertEquals(shebang, undefined) - }) - - await runner.step("empty file", async () => { - const f = Path.mktemp().join("script").touch() - const shebang = await specimen(f) - assertEquals(shebang, undefined) - }) - - await runner.step("unreadable files are ignored", async () => { - const f = Path.mktemp().join("script").write({ text: "#!/bin/sh" }).chmod(0o000) - const shebang = await specimen(f) - assertEquals(shebang, undefined) - }) -}) diff --git a/src/utils/get-shebang.ts b/src/utils/get-shebang.ts deleted file mode 100644 index 8ac0fc71..00000000 --- a/src/utils/get-shebang.ts +++ /dev/null @@ -1,48 +0,0 @@ -import readLines from "pkgx/utils/read-lines.ts" -import { Path, PkgxError } from "pkgx" - -export default async function(path: Path) { - try { - const fd = await is_shebang(path) - if (!fd) return - try { - const shebang = (await readLines(fd).next()).value as string - const parts = shebang.split(' ') - - const cmd = Path.abs(parts[0])?.basename() - if (!cmd) throw new PkgxError("invalid shebang") - - if (cmd == 'env') { - return parts.slice(parts[1] == '-S' ? 2 : 1) - } else { - return [cmd, ...parts.slice(1)] - } - } finally { - fd.close() - } - } catch (err) { - if (err instanceof Deno.errors.PermissionDenied) { - // eg `pkgx sudo` will throw since it is not a readable file unless you are already root - return - } else { - throw err - } - } -} - -export async function is_shebang(path: Path): Promise { - const fd = await Deno.open(path.string) - const buf = new Uint8Array(2) - - if (await fd.read(buf) !== 2) { - fd.close() - return // empty file - } - - if (buf[0] != 35 || buf[1] != 33) { - fd.close() - return // not an executable script - } - - return fd -} \ No newline at end of file diff --git a/src/utils/sh-escape.test.ts b/src/utils/sh-escape.test.ts deleted file mode 100644 index f48e8dd5..00000000 --- a/src/utils/sh-escape.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { assertEquals } from "@std/assert" -import shEscape from "./sh-escape.ts" - -Deno.test("shEscape", () => { - assertEquals(shEscape("foo"), "foo") - assertEquals(shEscape("foo bar"), '"foo bar"') - assertEquals(shEscape("foo'bar"), '"foo\'bar"') - assertEquals(shEscape('foo"bar'), "'foo\"bar'") - assertEquals(shEscape('foo\'bar"baz'), '"foo\'bar\\"baz"') - assertEquals(shEscape("foo\nbar"), '"foo\nbar"') -}); diff --git a/src/utils/sh-escape.ts b/src/utils/sh-escape.ts deleted file mode 100644 index 658840e8..00000000 --- a/src/utils/sh-escape.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function(x: string) { - /// `$` because we add some env vars recursively - if (!/\s/.test(x) && !/['"$><]/.test(x)) return x - if (!x.includes('"')) return `"${x}"` - if (!x.includes("'")) return `'${x}'` - x = x.replaceAll('"', '\\"') - return `"${x}"` -} diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts deleted file mode 100644 index 7ab11065..00000000 --- a/src/utils/test-utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Logger } from "../prefab/install.ts" -import { faker } from "npm:@faker-js/faker" -import { Path } from "pkgx" - -export default faker - -export function faker_args() { - let arg0 = faker.system.fileName({ extensionCount: 0 }) - arg0 = `_${arg0}` // ensure we don’t accidentally call a real utility lol - const args = faker.word.words({count: { min: 2, max: 10 }}).split(" ") - return [arg0, ...args] -} - -// putting here as putting it in devenv.test.ts caused those tests to once per import -export const fixturesd = new Path(new URL(import.meta.url).pathname).parent().parent().parent().join('fixtures') - -export const null_logger: Logger = { - replace: () => {}, - clear: () => {}, - upgrade: () => undefined -}