From 46fa1895dff4e4e2bb18ed92b1c6bb68c3e9dbf9 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:42:14 +0300 Subject: [PATCH] feat: mvp (#1) * wip: docker based suite * wip: worker based plugins * wip: installation * wip: shimmy shim shim * feat: ambient access plugs * feat: plugin deps * feat: wasmedge, lib/include shims support and globs * feat(plug): pnpm * feat(plug): cargo-instal and wasm-tools * feat(plug): wasm-opt & cargo-insta * feat(ci): e2e tests and some new plugs * wip: asdf plug support * fix: missing changes * feat(core): asdf support * fix: formatting * fix(tests): disable asdf-python * feat(plug): protoc * feat(tests): `protoc` * refactor(hooks): move to bash-preexec * fix(hooks): `init_ghjk` directly in hooks.sh * feat(plug): earthly * fix: change installId format to something less problematic * feat(plug): ruff * feat(plug): whiz * refactor: native js `unarchive` * fix: use zipjs * fix: better `unarchive` * fix: get tests working * fix: address feedback * fix: remove dead code * refactor: `install.ts` -> `setup.ts` * refactor: `setup.ts` -> `init.ts` * wip: `modules` * wip: `plugs` -> `ports` * refactor: big un * fix: apply feedback * fix: pin debian pkg versions for test dockerfile * fix: back to aliases and zsh support * refactor(hooks): explicit bash command * feat(ci): ghjk action * fix(ci): missing shell prop * fix(action): improve outputs * refactor: make hooks more efficient --- .dockerignore | 4 + .github/pull_request_template.md | 25 + .github/workflows/autoupdate.yml | 19 + .github/workflows/pr-title-check.yml | 15 + .github/workflows/tests.yml | 59 +++ .gitignore | 1 + .pre-commit-config.yaml | 113 +---- .vscode/settings.json | 9 +- LICENSE.md => LICENSE | 2 +- README.md | 58 ++- action.yml | 31 ++ cli/cleanup.ts | 12 - cli/core/hooks.ts | 123 ----- cli/core/tools.ts | 55 --- cli/deps.ts | 15 - cli/list.ts | 12 - cli/mod.ts | 27 -- cli/outdated.ts | 12 - cli/sync.ts | 85 ---- cli/utils.ts | 62 --- deno.jsonc | 9 + deno.lock | 685 +++++++++++++++++++++++++++ deps/cli.ts | 10 + deps/common.ts | 10 + deps/dev.ts | 4 + deps/ports.ts | 8 + examples/protoc/ghjk.ts | 4 + ghjk.ts | 49 +- host/deno.ts | 114 +++++ host/mod.ts | 80 ++++ host/types.ts | 14 + install.ts | 12 +- install/hooks/bash.sh | 67 +++ install/hooks/fish.fish | 46 ++ install/hooks/zsh.zsh | 3 + install/mod.ts | 151 ++++++ main.ts | 11 + mod.ts | 62 ++- modules/mod.ts | 6 + modules/ports/ambient.ts | 85 ++++ modules/ports/asdf.ts | 159 +++++++ modules/ports/mod.ts | 144 ++++++ modules/ports/std.ts | 62 +++ modules/ports/sync.ts | 439 +++++++++++++++++ modules/ports/types.ts | 289 +++++++++++ modules/ports/worker.ts | 294 ++++++++++++ modules/std.ts | 25 + modules/tasks/mod.ts | 21 + modules/types.ts | 21 + port.ts | 132 ++++++ ports/act.ts | 122 +++++ ports/asdf.ts | 14 + ports/cargo-binstall.ts | 101 ++++ ports/cargo-insta.ts | 92 ++++ ports/curl.ts | 22 + ports/earthly.ts | 109 +++++ ports/git.ts | 22 + ports/jco.ts | 110 +++++ ports/mold.ts | 118 +++++ ports/node.ts | 127 +++++ ports/pnpm.ts | 113 +++++ ports/protoc.ts | 113 +++++ ports/ruff.ts | 118 +++++ ports/tar.ts | 22 + ports/unzip.ts | 22 + ports/wasm-opt.ts | 92 ++++ ports/wasm-tools.ts | 92 ++++ ports/wasmedge.ts | 176 +++++++ ports/whiz.ts | 114 +++++ setup_logger.ts | 3 + tests/ambient.ts | 32 ++ tests/e2e.ts | 155 ++++++ tests/test.Dockerfile | 61 +++ tests/utils.ts | 73 +++ tools/jco.ts | 70 --- tools/node.ts | 62 --- tools/rust.ts | 38 -- utils/logger.ts | 79 +++ utils/mod.ts | 226 +++++++++ utils/unarchive.ts | 127 +++++ 80 files changed, 5655 insertions(+), 725 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/autoupdate.yml create mode 100644 .github/workflows/pr-title-check.yml create mode 100644 .github/workflows/tests.yml rename LICENSE.md => LICENSE (99%) create mode 100644 action.yml delete mode 100644 cli/cleanup.ts delete mode 100644 cli/core/hooks.ts delete mode 100644 cli/core/tools.ts delete mode 100644 cli/deps.ts delete mode 100644 cli/list.ts delete mode 100644 cli/mod.ts delete mode 100644 cli/outdated.ts delete mode 100644 cli/sync.ts delete mode 100644 cli/utils.ts create mode 100644 deno.jsonc create mode 100644 deno.lock create mode 100644 deps/cli.ts create mode 100644 deps/common.ts create mode 100644 deps/dev.ts create mode 100644 deps/ports.ts create mode 100644 examples/protoc/ghjk.ts create mode 100644 host/deno.ts create mode 100644 host/mod.ts create mode 100644 host/types.ts create mode 100644 install/hooks/bash.sh create mode 100644 install/hooks/fish.fish create mode 100644 install/hooks/zsh.zsh create mode 100644 install/mod.ts create mode 100755 main.ts create mode 100644 modules/mod.ts create mode 100644 modules/ports/ambient.ts create mode 100644 modules/ports/asdf.ts create mode 100644 modules/ports/mod.ts create mode 100644 modules/ports/std.ts create mode 100644 modules/ports/sync.ts create mode 100644 modules/ports/types.ts create mode 100644 modules/ports/worker.ts create mode 100644 modules/std.ts create mode 100644 modules/tasks/mod.ts create mode 100644 modules/types.ts create mode 100644 port.ts create mode 100644 ports/act.ts create mode 100644 ports/asdf.ts create mode 100644 ports/cargo-binstall.ts create mode 100644 ports/cargo-insta.ts create mode 100644 ports/curl.ts create mode 100644 ports/earthly.ts create mode 100644 ports/git.ts create mode 100644 ports/jco.ts create mode 100644 ports/mold.ts create mode 100644 ports/node.ts create mode 100644 ports/pnpm.ts create mode 100644 ports/protoc.ts create mode 100644 ports/ruff.ts create mode 100644 ports/tar.ts create mode 100644 ports/unzip.ts create mode 100644 ports/wasm-opt.ts create mode 100644 ports/wasm-tools.ts create mode 100644 ports/wasmedge.ts create mode 100644 ports/whiz.ts create mode 100644 setup_logger.ts create mode 100644 tests/ambient.ts create mode 100644 tests/e2e.ts create mode 100644 tests/test.Dockerfile create mode 100644 tests/utils.ts delete mode 100644 tools/jco.ts delete mode 100644 tools/node.ts delete mode 100644 tools/rust.ts create mode 100644 utils/logger.ts create mode 100644 utils/mod.ts create mode 100644 utils/unarchive.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..1eb1bed0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.vscode +tests/ +*.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..78c73a8c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ + + +### Describe your change + + + +### Motivation and context + + + +### Migration notes + + + +### Checklist + +- [ ] The change come with new or modified tests +- [ ] Hard-to-understand functions have explanatory comments +- [ ] End-user documentation is updated to reflect the change diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 00000000..aa9982f5 --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,19 @@ +on: + schedule: + - cron: "0 2 1 * *" + workflow_dispatch: + +jobs: + auto-update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + - uses: browniebroke/pre-commit-autoupdate-action@main + - uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: update/pre-commit-hooks + title: Update pre-commit hooks + commit-message: "chore: update pre-commit hooks" + body: Update versions of pre-commit hooks to latest version. diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 00000000..148cdd28 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,15 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - ready_for_review + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..5c09652d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,59 @@ +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize + - ready_for_review + +env: + DENO_VERSION: "1.38.2" + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: actions/checkout@v4 + + test-pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: ${{ env.DENO_VERSION }} + - uses: pre-commit/action@v3.0.0 + + test-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: ${{ env.DENO_VERSION }} + - uses: docker/setup-buildx-action@v3 + - uses: actions-hub/docker/cli@master + env: + SKIP_LOGIN: true + - run: deno task test + + test-action: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: ${{ env.DENO_VERSION }} + - uses: ./ + id: ghjk-action + env: + GHJK_CONFIG: ./examples/protoc/ghjk.ts + - shell: bash + run: | + cd examples/protoc + . $BASH_ENV + protoc --version diff --git a/.gitignore b/.gitignore index e43b0f98..049aae8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +play.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f15f5696..0eb641d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,133 +13,38 @@ repos: - id: check-merge-conflict - id: end-of-file-fixer # exclude all generated files - exclude: (typegate/deno.lock|.*\.snap$|typegate/src/typegraphs/.*\.json|website/docs/reference/) + exclude: (deno.lock) - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.27.0 hooks: - id: check-dependabot - id: check-github-workflows - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.0.292" - hooks: - - id: ruff - - repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - repo: https://github.com/commitizen-tools/commitizen rev: 3.10.0 hooks: - id: commitizen stages: - commit-msg - - repo: https://github.com/doublify/pre-commit-rust - rev: v1.0 - hooks: - - id: fmt - - id: cargo-check - args: - - "--locked" - - id: clippy - args: - - "--locked" - - "--" - - "--deny" - - "warnings" - repo: local hooks: - id: deno-fmt name: Deno format language: system - entry: bash -c 'cd typegate && deno fmt --ignore=native,src/typegraphs,tmp && cd ../dev && deno fmt' + entry: bash -c 'deno fmt' pass_filenames: false types: - ts - files: ^(typegate|dev)/ - - id: deno-lint - name: Deno lint + - id: deno-check + name: Deno check language: system - entry: bash -c 'cd typegate && deno lint --rules-exclude=no-explicit-any --ignore=native,tmp && cd ../dev && deno lint' + entry: bash -c 'deno task check' pass_filenames: false types: - ts - files: ^(typegate|dev)/ - - id: es-lint - name: Eslint - language: system - entry: bash -c 'cd website && [ -f node_modules/.bin/eslint ] && pnpm lint' - pass_filenames: false - types_or: - - ts - - tsx - files: ^website/ - - id: version - name: "Lock versions" - always_run: true + - id: deno-lint + name: Deno lint language: system - entry: bash -c 'deno run -A dev/lock.ts --check' + entry: bash -c 'deno task lint' pass_filenames: false - - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 - hooks: - - id: insert-license - name: "License MPL-2.0 python" - args: - #- --remove-header - - --license-filepath=dev/license-header-MPL-2.0.txt - - "--comment-style=#" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: - - python - files: ^typegraph/ - - id: insert-license - name: "License Elastic-2.0 rust" - args: - #- --remove-header - - --license-filepath=dev/license-header-Elastic-2.0.txt - - "--comment-style=//" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: - - rust - files: ^(typegate|libs)/ - - id: insert-license - name: "License MPL-2.0 rust" - args: - #- --remove-header - - --license-filepath=dev/license-header-MPL-2.0.txt - - "--comment-style=//" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: - - rust - files: ^(meta-cli|typegraph)/ - - id: insert-license - name: "License Elastic-2.0 deno" - args: - #- --remove-header - - --license-filepath=dev/license-header-Elastic-2.0.txt - - "--comment-style=//" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: - - ts - files: ^(typegate|dev)/ - - id: insert-license - name: "License MPL-2.0 deno" - args: - #- --remove-header - - --license-filepath=dev/license-header-MPL-2.0.txt - - "--comment-style=//" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: - - ts - files: ^typegraph/ - - id: insert-license - name: "License Elastic-2.0 typescript" - args: - #- --remove-header - - --license-filepath=dev/license-header-Elastic-2.0.txt - - "--comment-style=//" - - "--skip-license-insertion-comment=no-auto-license-header" - types_or: + types: - ts - - tsx - files: ^website/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 47187f82..10360c41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "deno.enablePaths": [ - ".", + "." ], "deno.suggest.completeFunctionCalls": true, "deno.inlayHints.variableTypes.enabled": true, @@ -16,5 +16,8 @@ "deno.codeLens.referencesAllFunctions": true, "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" - } -} \ No newline at end of file + }, + "cSpell.words": [ + "ghjk" + ] +} diff --git a/LICENSE.md b/LICENSE similarity index 99% rename from LICENSE.md rename to LICENSE index a612ad98..d0a1fa14 100644 --- a/LICENSE.md +++ b/LICENSE @@ -357,7 +357,7 @@ Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. + file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE diff --git a/README.md b/README.md index d4ebceec..69b998c0 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,23 @@ ghjk (jk) is a programmable runtime manager. ## Features - install and manage tools (e.g. rustup, deno, node, etc.) - - fuzzy match the version + - [ ] fuzzy match the version - support dependencies between tools -- setup runtime helpers (e.g. pre-commit, linting, ignore, etc.) - - provide a general regex based lockfile +- [ ] setup runtime helpers (e.g. pre-commit, linting, ignore, etc.) + - [ ] provide a general regex based lockfile - enforce custom rules -- create aliases and shortcuts +- [ ] create aliases and shortcuts - `meta` -> `cargo run -p meta` - `x meta` -> `cargo run -p meta` (avoid conflicts and provide autocompletion) -- load environment variables and prompt for missing ones -- define build tasks with dependencies +- [ ] load environment variables and prompt for missing ones +- [ ] define build tasks with dependencies - `task("build", {depends_on: [rust], if: Deno.build.os === "Macos" })` - `task.bash("ls")` -- compatible with continuous integration (e.g. github actions, gitlab) +- [x] compatible with continuous integration (e.g. github actions, gitlab) ## Getting started -Install the hooks: +Install ghjk: ```bash deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/install.ts @@ -31,6 +31,7 @@ In your project, create a configuration file `ghjk.ts`: ```ts export { ghjk } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; +import node from "https://raw.githubusercontent.com/metatypedev/ghjk/ports/node.ts"; node({ version: "14.17.0" }); ``` @@ -46,18 +47,19 @@ and looks as follows (abstracting away some implementation details): - `.config/fish/config.fish` - for every visited directory, the hook looks for `$PWD/ghjk.ts` in the directory or its parents, and - - adds the `$HOME/.local/share/ghjk/shims/$PWD` to your `$PATH` - - sources environment variables in `$HOME/.local/share/ghjk/shims/$PWD/loader` - and clear previously loaded ones (if any) - - defines an alias `ghjk` running `deno run -A $PWD/ghjk.ts` + - adds the `$HOME/.local/share/ghjk/envs/$PWD/shims/{bin,lib,include}` to your + paths + - sources environment variables in + `$HOME/.local/share/ghjk/envs/$PWD/loader.{sh,fish}` and clear previously + loaded ones (if any) - you can then - - sync your runtime with `ghjk sync` which - - installs the missing tools at `$HOME/.local/share/ghjk/installs` + - sync your runtime with `ghjk ports sync` which + - installs the missing tools at `$HOME/.local/share/ghjk/envs/$PWD/installs` - regenerates the shims with symlinks and environment variables - detects any violation of the enforced rules - - `ghjk list`: list installed tools and versions - - `ghjk outdated`: list outdated tools - - `ghjk cleanup`: remove unused tools and versions + - [ ] `ghjk list`: list installed tools and versions + - [ ] `ghjk outdated`: list outdated tools + - [ ]`ghjk cleanup`: remove unused tools and versions ## Extending `ghjk` @@ -66,10 +68,20 @@ and looks as follows (abstracting away some implementation details): ## todo -- multiple version of the same package (e.g. rust stable and rust nightly) -- wasmedge -- python with virtual env dir -- poetry -- pnpm -- mold({ if: Deno.build.os === "Macos" }) +- multiple version of the same package (e.g. rust stable and rust nighted) +- [x] wasmedge +- [x] jco +- [ ] python with virtual env dir + - poetry + - pre-commit +- [x] pnpm +- [x] mold +- [x] wasm-tools +- [x] cargo-insta - hash verifiable dependencies (timestamp) +- hide the `Deno` object in an abstraction +- support windows +- [ ] installation tools + - [ ] untar + - [ ] xz + - [ ] git diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..8a047137 --- /dev/null +++ b/action.yml @@ -0,0 +1,31 @@ +name: 'Setup Ghjk' +description: 'Installs ghjk and optionally syncs according to the config' +inputs: + installer-uri: + description: 'Alternative installer script to use' + required: true + # FIXME: find a way to get commit sha of current executing action version + # default: $GITHUB_SERVER_URL/$GITHUB_ACTION_REPOSITORY/raw/feat/ + default: './install.ts' + sync: + description: 'Disable to skip syncing ports' + required: true + default: true +runs: + using: "composite" + steps: + - id: install-ghjk + shell: bash + run: | + deno run -A ${{ inputs.installer-uri }} + echo "$HOME/.local/bin" >> $GITHUB_PATH + - id: sync-ghjk + shell: bash + if: ${{ inputs.sync }} + run: | + ghjk ports sync + - id: ghjk-outputs + shell: bash + run: | + echo "GHJK_DIR=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT + echo "BASH_ENV=$HOME/.local/share/ghjk/env.sh" >> $GITHUB_ENV diff --git a/cli/cleanup.ts b/cli/cleanup.ts deleted file mode 100644 index e4a7f335..00000000 --- a/cli/cleanup.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from "./deps.ts"; - -export class CleanupCommand extends Command { - constructor() { - super(); - this - .description("") - .action(async () => { - console.log("cleanup"); - }); - } -} diff --git a/cli/core/hooks.ts b/cli/core/hooks.ts deleted file mode 100644 index 0ef38c3e..00000000 --- a/cli/core/hooks.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { basename, dirname, resolve } from "../deps.ts"; -import { dirs, runAndReturn } from "../utils.ts"; - -// null means it should be removed (for cleaning up old versions) -const vfs = { - "hooks/entrypoint.ts": ` -const log = console.log; -console.log = (...args) => { - log("[ghjk.ts]", ...args); -}; -const module = await import(Deno.args[0]); -console.log = log; -module.ghjk.runCli(Deno.args.slice(1)); - `, - "hooks/hook.fish": ` -function ghjk_hook --on-variable PWD - if set --query GHJK_CLEANUP - eval $GHJK_CLEANUP - set --erase GHJK_CLEANUP - end - set --local current_dir $PWD - while test $current_dir != "/" - if test -e $current_dir/ghjk.ts - set --local shim $HOME/.local/share/ghjk/shims/$(string replace --all / . $current_dir) - if test -d $shim - set --global PATH $shim $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/shim" $PATH) - source $shim/loader.fish - if test $shim/loader.fish -ot $current_dir/ghjk.ts - set_color FF4500 - echo "[ghjk] Detected changes, please sync..." - set_color normal - end - else - set_color FF4500 - echo "[ghjk] Uninstalled runtime found, please sync..." - echo $shim - set_color normal - end - alias ghjk "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $current_dir/ghjk.ts" - return - end - set current_dir (dirname $current_dir) - end - alias ghjk "echo 'No ghjk.ts config found.'" -end -ghjk_hook -`, -}; - -async function detectShell(): Promise { - const parent = await runAndReturn([ - "ps", - "-p", - String(Deno.ppid), - "-o", - "comm=", - ]); - const path = parent.unwrapOrElse((e) => { - throw new Error(`cannot get parent process name: ${e}`); - }).trimEnd(); - - return basename(path, ".exe").toLowerCase(); -} - -async function unpackVFS(baseDir: string): Promise { - await Deno.mkdir(baseDir, { recursive: true }); - - for (const [subpath, content] of Object.entries(vfs)) { - const path = resolve(baseDir, subpath); - if (content === null) { - await Deno.remove(path); - } else { - await Deno.mkdir(dirname(path), { recursive: true }); - await Deno.writeTextFile(path, content.trim()); - } - } -} - -async function filterAddFile( - path: string, - marker: RegExp, - content: string | null, -) { - const file = await Deno.readTextFile(path).catch(async (err) => { - if (err instanceof Deno.errors.NotFound) { - await Deno.mkdir(dirname(path), { recursive: true }); - return ""; - } - throw err; - }); - const lines = file.split("\n"); - - let i = 0; - while (i < lines.length) { - if (marker.test(lines[i])) { - lines.splice(i, 1); - } else { - i += 1; - } - } - - if (content !== null) { - lines.push(content); - } - - await Deno.writeTextFile(path, lines.join("\n")); -} - -export async function install() { - const { homeDir, shareDir } = dirs(); - await unpackVFS(shareDir); - const shell = await detectShell(); - - if (shell === "fish") { - await filterAddFile( - resolve(homeDir, ".config/fish/config.fish"), - /\.local\/share\/ghjk\/hooks\/hook.fish/, - "source $HOME/.local/share/ghjk/hooks/hook.fish", - ); - } else { - throw new Error(`unsupported shell: ${shell}`); - } -} diff --git a/cli/core/tools.ts b/cli/core/tools.ts deleted file mode 100644 index 0bee9b6b..00000000 --- a/cli/core/tools.ts +++ /dev/null @@ -1,55 +0,0 @@ -interface ASDF_CONFIG_EXAMPLE { - ASDF_INSTALL_TYPE: "version" | "ref"; - ASDF_INSTALL_VERSION: string; // full version number or Git Ref depending on ASDF_INSTALL_TYPE - ASDF_INSTALL_PATH: string; // the path to where the tool should, or has been installed - ASDF_CONCURRENCY: number; // the number of cores to use when compiling the source code. Useful for setting make -j - ASDF_DOWNLOAD_PATH: string; // the path to where the source code or binary was downloaded to by bin/download - ASDF_PLUGIN_PATH: string; // the path the plugin was installed - ASDF_PLUGIN_SOURCE_URL: string; // the source URL of the plugin - ASDF_PLUGIN_PREV_REF: string; // prevous git-ref of the plugin repo - ASDF_PLUGIN_POST_REF: string; // updated git-ref of the plugin repo - ASDF_CMD_FILE: string; // resolves to the full path of the file being sourced -} - -export interface BinDefaultEnv { - ASDF_INSTALL_TYPE: "version" | "ref"; - ASDF_INSTALL_VERSION: string; - ASDF_INSTALL_PATH: string; -} - -export interface ListAllEnv { -} - -export interface ListBinPathsEnv extends BinDefaultEnv { -} - -export interface ExecPathEnv extends BinDefaultEnv { -} - -export interface DownloadEnv extends BinDefaultEnv { - ASDF_DOWNLOAD_PATH: string; -} - -export interface InstallEnv extends BinDefaultEnv { - ASDF_CONCURRENCY: number; - ASDF_DOWNLOAD_PATH: string; -} - -export abstract class Tool { - abstract name: string; - abstract dependencies: string[]; - - abstract execEnv( - env: ExecPathEnv, - ): Promise> | Record; - - abstract listBinPaths( - env: ListBinPathsEnv, - ): Promise> | Record; - - abstract listAll(env: ListAllEnv): Promise | string[]; - - abstract download(env: DownloadEnv): Promise | void; - - abstract install(env: InstallEnv): Promise | void; -} diff --git a/cli/deps.ts b/cli/deps.ts deleted file mode 100644 index 25f39072..00000000 --- a/cli/deps.ts +++ /dev/null @@ -1,15 +0,0 @@ -export { Err, Ok } from "https://deno.land/x/monads@v0.5.10/mod.ts"; -export type { Result } from "https://deno.land/x/monads@v0.5.10/mod.ts"; -export { - basename, - dirname, - resolve, -} from "https://deno.land/std@0.205.0/path/mod.ts"; -export { exists } from "https://deno.land/std@0.205.0/fs/mod.ts"; -export { - Command, - CompletionsCommand, -} from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; -export type { - CommandResult, -} from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; diff --git a/cli/list.ts b/cli/list.ts deleted file mode 100644 index ab950a0d..00000000 --- a/cli/list.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from "./deps.ts"; - -export class ListCommand extends Command { - constructor() { - super(); - this - .description("") - .action(async () => { - console.log("list"); - }); - } -} diff --git a/cli/mod.ts b/cli/mod.ts deleted file mode 100644 index 372fb6c2..00000000 --- a/cli/mod.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SyncCommand } from "./sync.ts"; -import { Command, CommandResult, CompletionsCommand } from "./deps.ts"; -import { ListCommand } from "./list.ts"; -import { OutdatedCommand } from "./outdated.ts"; -import { CleanupCommand } from "./cleanup.ts"; - -function runCli(args: string[]): Promise { - return new Command() - .name("ghjk") - .version("0.1.0") - .description("Programmable runtime manager.") - .action(function () { - this.showHelp(); - }) - .command("sync", new SyncCommand()) - .command("list", new ListCommand()) - .command("outdated", new OutdatedCommand()) - .command("cleanup", new CleanupCommand()) - .command("completions", new CompletionsCommand()) - .parse(args); -} - -export const ghjk = { - runCli, - tools: [], - tasks: [], -}; diff --git a/cli/outdated.ts b/cli/outdated.ts deleted file mode 100644 index fb6619fa..00000000 --- a/cli/outdated.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from "./deps.ts"; - -export class OutdatedCommand extends Command { - constructor() { - super(); - this - .description("") - .action(async () => { - console.log("outdated"); - }); - } -} diff --git a/cli/sync.ts b/cli/sync.ts deleted file mode 100644 index 73d4cda8..00000000 --- a/cli/sync.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { node } from "../tools/node.ts"; -import { Command, dirname, exists, resolve } from "./deps.ts"; -import { dirs } from "./utils.ts"; - -async function findConfig(path: string): Promise { - let current = path; - while (current !== "/") { - const location = `${path}/ghjk.ts`; - if (await exists(location)) { - return location; - } - current = dirname(current); - } - return null; -} - -function shimFromConfig(config: string): string { - const { shareDir } = dirs(); - return resolve(shareDir, "shims", dirname(config).replaceAll("/", ".")); -} - -async function writeLoader(shim: string, env: Record) { - await Deno.mkdir(shim, { recursive: true }); - await Deno.writeTextFile( - `${shim}/loader.fish`, - Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP "set --global --export ${k} '$k';"; set --global --export ${k} '${v}'` - ).join("\n"), - ); -} - -export class SyncCommand extends Command { - constructor() { - super(); - this - .description("Syncs the runtime.") - .action(async () => { - const config = await findConfig(Deno.cwd()); - console.log(config); - if (!config) { - console.log("ghjk did not find any `ghjk.ts` config."); - return; - } - - const shim = shimFromConfig(config); - console.log(shim); - - // in the ghjk.ts the user will have declared some tools and tasks - // we need to collect them through the `ghjk` object from main - // (beware of multiple versions of tools libs) - // here, only showing what should happen after as an example - - const nodeTool = node({ version: "v21.1.0" }); - - // build dag - - // link shims - const ASDF_INSTALL_VERSION = "v21.1.0"; - const ASDF_INSTALL_PATH = resolve(shim, "node", ASDF_INSTALL_VERSION); - await Deno.mkdir(ASDF_INSTALL_PATH, { recursive: true }); - - await nodeTool.install( - { ASDF_INSTALL_VERSION, ASDF_INSTALL_PATH } as any, - ); - - for ( - const [bin, link] of Object.entries( - await nodeTool.listBinPaths({} as any), - ) - ) { - const linkPath = `${shim}/${link}`; - await Deno.remove(linkPath, { recursive: true }); - await Deno.symlink( - `${ASDF_INSTALL_PATH}/${bin}`, - linkPath, - { type: "file" }, - ); - } - - // write shim if config changes or does not exists - const env = await nodeTool.execEnv({ ASDF_INSTALL_PATH } as any); - await writeLoader(shim, env); - }); - } -} diff --git a/cli/utils.ts b/cli/utils.ts deleted file mode 100644 index ee740a03..00000000 --- a/cli/utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Err, Ok, Result } from "./deps.ts"; - -export async function runAndReturn( - cmd: string[], - cwd?: string, - env: Record = Deno.env.toObject(), -): Promise> { - const output = await new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - env, - }).output(); - - return output.success - ? Ok(new TextDecoder().decode(output.stdout)) - : Err(new TextDecoder().decode(output.stderr)); -} - -export async function runOrExit( - cmd: string[], - cwd?: string, - env: Record = Deno.env.toObject(), -) { - const p = new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - env, - }).spawn(); - - // keep pipe asynchronous till the command exists - void p.stdout.pipeTo(Deno.stdout.writable, { preventClose: true }); - void p.stderr.pipeTo(Deno.stderr.writable, { preventClose: true }); - - const { code, success } = await p.status; - if (!success) { - Deno.exit(code); - } -} - -function home_dir(): string | null { - switch (Deno.build.os) { - case "linux": - case "darwin": - return Deno.env.get("HOME") ?? null; - case "windows": - return Deno.env.get("USERPROFILE") ?? null; - default: - return null; - } -} - -export function dirs() { - const home = home_dir(); - if (!home) { - throw new Error("cannot find home dir"); - } - return { homeDir: home, shareDir: `${home}/.local/share/ghjk` }; -} diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 00000000..a883c5e4 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,9 @@ +{ + "tasks": { + // "test": "DOCKER_SOCK=/var/run/docker.sock deno test --unstable --allow-env=DOCKER_SOCK --allow-read=/var/run/docker.sock --allow-write=/var/run/docker.sock tests/*" + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker,sudo,which,ls,tar,git,curl,unzip --allow-read --allow-env tests/*", + "cache": "deno cache deps/*", + "check": "deno check *.ts **/*.ts", + "lint": "deno lint --ignore=ghjk.ts,play.ts --rules-exclude=no-explicit-any" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 00000000..c255f3d3 --- /dev/null +++ b/deno.lock @@ -0,0 +1,685 @@ +{ + "version": "3", + "redirects": { + "https://cdn.esm.sh/extract-files@12.0.0/extractFiles.mjs": "https://esm.sh/extract-files@12.0.0/extractFiles.mjs", + "https://cdn.esm.sh/extract-files@12.0.0/isExtractableFile.mjs": "https://esm.sh/extract-files@12.0.0/isExtractableFile.mjs", + "https://deno.land/x/graphql_request/mod.ts": "https://deno.land/x/graphql_request@v4.1.0/mod.ts" + }, + "remote": { + "https://deno.land/std@0.129.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.129.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", + "https://deno.land/std@0.129.0/archive/tar.ts": "35ea1baddec7988cc4034765a2cee7613bc8074bd40940d3f5e98f63070a716a", + "https://deno.land/std@0.129.0/async/abortable.ts": "a896ac6b0d4237bd2d2d248217cfa1f0d85ccda93cb25ebda55e33850e526be6", + "https://deno.land/std@0.129.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.129.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620", + "https://deno.land/std@0.129.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a", + "https://deno.land/std@0.129.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c", + "https://deno.land/std@0.129.0/async/mod.ts": "2240c6841157738414331f47dee09bb8c0482c5b1980b6e3234dd03515c8132f", + "https://deno.land/std@0.129.0/async/mux_async_iterator.ts": "f4d1d259b0c694d381770ddaaa4b799a94843eba80c17f4a2ec2949168e52d1e", + "https://deno.land/std@0.129.0/async/pool.ts": "97b0dd27c69544e374df857a40902e74e39532f226005543eabacb551e277082", + "https://deno.land/std@0.129.0/async/tee.ts": "1341feb1f5b1a96f8628d0f8fc07d8c43d3813423f18a63bf1b4785568d21b1f", + "https://deno.land/std@0.129.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", + "https://deno.land/std@0.129.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", + "https://deno.land/std@0.129.0/bytes/mod.ts": "d3b455c0dbd4804644159d1e25946ade5ee385d2359894de49e2c6101b18b7a9", + "https://deno.land/std@0.129.0/encoding/base64.ts": "c8c16b4adaa60d7a8eee047c73ece26844435e8f7f1328d74593dbb2dd58ea4f", + "https://deno.land/std@0.129.0/encoding/base64url.ts": "55f9d13df02efac10c6f96169daa3e702606a64e8aa27c0295f645f198c27130", + "https://deno.land/std@0.129.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.129.0/fmt/printf.ts": "e2c0f72146aed1efecf0c39ab928b26ae493a2278f670a871a0fbdcf36ff3379", + "https://deno.land/std@0.129.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.129.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.129.0/fs/ensure_file.ts": "7d353e64fee3d4d1e7c6b6726a2a5e987ba402c15fb49566309042887349c545", + "https://deno.land/std@0.129.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.129.0/io/files.ts": "d199ef64e918a256320ba8d8d44ae91de87c9077df8f8d6cca013f1b9fbbe285", + "https://deno.land/std@0.129.0/io/mod.ts": "1a4e8d19d42745fb2ff68d6ffa801657a4a15713bf7e7173df2da4737f5c5450", + "https://deno.land/std@0.129.0/io/readers.ts": "679471f3b9929b54393c9cd75b6bd178b4bc6d9aab5c0f1f9538f862cf4746fe", + "https://deno.land/std@0.129.0/io/streams.ts": "988a19155b52161f0035ce539e2f1d12edbc4c389fa7633da832a64e6edbe1a0", + "https://deno.land/std@0.129.0/io/util.ts": "078da53bba767bec0d45f7da44411f6dbf269e51ef7fcfea5e3714e04681c674", + "https://deno.land/std@0.129.0/io/writers.ts": "5db9995d2afc7ed391c88c6b441457df6fad6a0b09653e54c1dcd0387ab947fd", + "https://deno.land/std@0.129.0/node/_buffer.mjs": "f4a7df481d4eed06dc0151b833177d8ef74fc3a96dd4d2b073e690b6ced9474d", + "https://deno.land/std@0.129.0/node/_core.ts": "568d277be2e086af996cbdd599fec569f5280e9a494335ca23ad392b130d7bb9", + "https://deno.land/std@0.129.0/node/_events.mjs": "c0e3e0e290a8b81fee9d2973a529c8dcd5ebb4406782d1f91085274e2cb8490f", + "https://deno.land/std@0.129.0/node/_fixed_queue.ts": "455b3c484de48e810b13bdf95cd1658ecb1ba6bcb8b9315ffe994efcde3ba5f5", + "https://deno.land/std@0.129.0/node/_next_tick.ts": "64c361f6bca21df2a72dd77b84bd49d80d97a694dd3080703bc78f52146351d1", + "https://deno.land/std@0.129.0/node/_process/exiting.ts": "bc9694769139ffc596f962087155a8bfef10101d03423b9dcbc51ce6e1f88fce", + "https://deno.land/std@0.129.0/node/_util/_util_callbackify.ts": "79928ad80df3e469f7dcdb198118a7436d18a9f6c08bd7a4382332ad25a718cf", + "https://deno.land/std@0.129.0/node/_utils.ts": "c2c352e83c4c96f5ff994b1c8246bff2abcb21bfc3f1c06162cb3af1d201e615", + "https://deno.land/std@0.129.0/node/buffer.ts": "fbecbf3f237fa49bec96e97ecf56a7b92d48037b3d11219288e68943cc921600", + "https://deno.land/std@0.129.0/node/events.ts": "a1d40fc0dbccc944379ef968b80ea08f9fce579e88b5057fdb64e4f0812476dd", + "https://deno.land/std@0.129.0/node/internal/buffer.mjs": "6662fe7fe517329453545be34cea27a24f8ccd6d09afd4f609f11ade2b6dfca7", + "https://deno.land/std@0.129.0/node/internal/crypto/keys.ts": "16ce7b15a9fc5e4e3dee8fde75dae12f3d722558d5a1a6e65a9b4f86d64a21e9", + "https://deno.land/std@0.129.0/node/internal/crypto/util.mjs": "1de55a47fdbed6721b467a77ba48fdd1550c10b5eee77bbdb602eaffee365a5e", + "https://deno.land/std@0.129.0/node/internal/error_codes.ts": "ac03c4eae33de3a69d6c98e8678003207eecf75a6900eb847e3fea3c8c9e6d8f", + "https://deno.land/std@0.129.0/node/internal/errors.ts": "0d3a1eb03b654beb29b8354759a6902f45a840d4f957e9a3c632a24ce4c32632", + "https://deno.land/std@0.129.0/node/internal/hide_stack_frames.ts": "a91962ec84610bc7ec86022c4593cdf688156a5910c07b5bcd71994225c13a03", + "https://deno.land/std@0.129.0/node/internal/normalize_encoding.mjs": "3779ec8a7adf5d963b0224f9b85d1bc974a2ec2db0e858396b5d3c2c92138a0a", + "https://deno.land/std@0.129.0/node/internal/util.mjs": "684653b962fae84fd2bc08997291b1a50bed09b95dcfa7d35e3c4143163e879a", + "https://deno.land/std@0.129.0/node/internal/util/comparisons.ts": "680b55fe8bdf1613633bc469fa0440f43162c76dbe36af9aa2966310e1bb9f6e", + "https://deno.land/std@0.129.0/node/internal/util/debuglog.ts": "99e91bdf26f6c67861031f684817e1705a5bc300e81346585b396f413387edfb", + "https://deno.land/std@0.129.0/node/internal/util/inspect.mjs": "d1c2569c66a3dab45eec03208f22ad4351482527859c0011a28a6c797288a0aa", + "https://deno.land/std@0.129.0/node/internal/util/types.ts": "b2dacb8f1f5d28a51c4da5c5b75172b7fcf694073ce95ca141323657e18b0c60", + "https://deno.land/std@0.129.0/node/internal/validators.mjs": "a7e82eafb7deb85c332d5f8d9ffef052f46a42d4a121eada4a54232451acc49a", + "https://deno.land/std@0.129.0/node/internal_binding/_libuv_winerror.ts": "801e05c2742ae6cd42a5f0fd555a255a7308a65732551e962e5345f55eedc519", + "https://deno.land/std@0.129.0/node/internal_binding/_node.ts": "e4075ba8a37aef4eb5b592c8e3807c39cb49ca8653faf8e01a43421938076c1b", + "https://deno.land/std@0.129.0/node/internal_binding/_utils.ts": "1c50883b5751a9ea1b38951e62ed63bacfdc9d69ea665292edfa28e1b1c5bd94", + "https://deno.land/std@0.129.0/node/internal_binding/_winerror.ts": "8811d4be66f918c165370b619259c1f35e8c3e458b8539db64c704fbde0a7cd2", + "https://deno.land/std@0.129.0/node/internal_binding/buffer.ts": "722c62b85f966e0777b2d98c021b60e75d7f2c2dabc43413ef37d60dbd13a5d9", + "https://deno.land/std@0.129.0/node/internal_binding/constants.ts": "aff06aac49eda4234bd3a2b0b8e1fbfc67824e281c532ff9960831ab503014cc", + "https://deno.land/std@0.129.0/node/internal_binding/string_decoder.ts": "5cb1863763d1e9b458bc21d6f976f16d9c18b3b3f57eaf0ade120aee38fba227", + "https://deno.land/std@0.129.0/node/internal_binding/types.ts": "4c26fb74ba2e45de553c15014c916df6789529a93171e450d5afb016b4c765e7", + "https://deno.land/std@0.129.0/node/internal_binding/util.ts": "90364292e2bd598ab5d105b48ca49817b6708f2d1d9cbaf08b2b3ab5ca4c90a7", + "https://deno.land/std@0.129.0/node/internal_binding/uv.ts": "3821bc5e676d6955d68f581988c961d77dd28190aba5a9c59f16001a4deb34ba", + "https://deno.land/std@0.129.0/node/util.ts": "7fd6933b37af89a8e64d73dc6ee1732455a59e7e6d0965311fbd73cd634ea630", + "https://deno.land/std@0.129.0/node/util/types.mjs": "f9288198cacd374b41bae7e92a23179d3160f4c0eaf14e19be3a4e7057219a60", + "https://deno.land/std@0.129.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.129.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.129.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.129.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.129.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.129.0/path/mod.ts": "4275129bb766f0e475ecc5246aa35689eeade419d72a48355203f31802640be7", + "https://deno.land/std@0.129.0/path/posix.ts": "663e4a6fe30a145f56aa41a22d95114c4c5582d8b57d2d7c9ed27ad2c47636bb", + "https://deno.land/std@0.129.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.129.0/path/win32.ts": "e7bdf63e8d9982b4d8a01ef5689425c93310ece950e517476e22af10f41a136e", + "https://deno.land/std@0.129.0/streams/buffer.ts": "ee47194022d47fa23d4749b8afbadc83c237c4595467a9bddb322af0dd205815", + "https://deno.land/std@0.129.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "https://deno.land/std@0.129.0/streams/delimiter.ts": "75595345f14eb268d2f5edfd20600c9956f61499a36697baabef8043897bc50b", + "https://deno.land/std@0.129.0/streams/merge.ts": "89faf7dcda7e010f1e01dfc555d609c66d9fb2c834b7aa457a63cc70a25c3817", + "https://deno.land/std@0.129.0/streams/mod.ts": "5f47811c2e983518cc0c82f323924b6a9bb1f5bf948cd6d498ff6aed77ada51c", + "https://deno.land/std@0.129.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", + "https://deno.land/std@0.129.0/testing/asserts.ts": "0a95d9e8076dd3e7f0eeb605a67c148078b4b11f4abcd5eef115b0361b0736a2", + "https://deno.land/std@0.133.0/_deno_unstable.ts": "23a1a36928f1b6d3b0170aaa67de09af12aa998525f608ff7331b9fb364cbde6", + "https://deno.land/std@0.133.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.133.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", + "https://deno.land/std@0.133.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.133.0/fs/copy.ts": "9248d1492599957af8c693ceb10a432b09f0b0b61c60a4d6aff29b0c7d3a17b3", + "https://deno.land/std@0.133.0/fs/empty_dir.ts": "7274d87160de34cbed0531e284df383045cf43543bbeadeb97feac598bd8f3c5", + "https://deno.land/std@0.133.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.133.0/fs/ensure_file.ts": "7d353e64fee3d4d1e7c6b6726a2a5e987ba402c15fb49566309042887349c545", + "https://deno.land/std@0.133.0/fs/ensure_link.ts": "489e23df9fe3e6636048b5830ddf0f111eb29621eb85719255ad9bd645f3471b", + "https://deno.land/std@0.133.0/fs/ensure_symlink.ts": "88dc83de1bc90ed883dd458c2d2eae3d5834a4617d12925734836e1f0803b274", + "https://deno.land/std@0.133.0/fs/eol.ts": "b92f0b88036de507e7e6fbedbe8f666835ea9dcbf5ac85917fa1fadc919f83a5", + "https://deno.land/std@0.133.0/fs/exists.ts": "cb734d872f8554ea40b8bff77ad33d4143c1187eac621a55bf37781a43c56f6d", + "https://deno.land/std@0.133.0/fs/expand_glob.ts": "0c10130d67c9b02164b03df8e43c6d6defbf8e395cb69d09e84a8586e6d72ac3", + "https://deno.land/std@0.133.0/fs/mod.ts": "4dc052c461c171abb5c25f6e0f218ab838a716230930b534ba351745864b7d6d", + "https://deno.land/std@0.133.0/fs/move.ts": "0573cedcf583f09a9494f2dfccbf67de68a93629942d6b5e6e74a9e45d4e8a2e", + "https://deno.land/std@0.133.0/fs/walk.ts": "117403ccd21fd322febe56ba06053b1ad5064c802170f19b1ea43214088fe95f", + "https://deno.land/std@0.133.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.133.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.133.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.133.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.133.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.133.0/path/mod.ts": "4275129bb766f0e475ecc5246aa35689eeade419d72a48355203f31802640be7", + "https://deno.land/std@0.133.0/path/posix.ts": "663e4a6fe30a145f56aa41a22d95114c4c5582d8b57d2d7c9ed27ad2c47636bb", + "https://deno.land/std@0.133.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.133.0/path/win32.ts": "e7bdf63e8d9982b4d8a01ef5689425c93310ece950e517476e22af10f41a136e", + "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", + "https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7", + "https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9", + "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", + "https://deno.land/std@0.201.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.201.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.201.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", + "https://deno.land/std@0.201.0/fmt/colors.ts": "87544aa2bc91087bb37f9c077970c85bfb041b48e4c37356129d7b450a415b6f", + "https://deno.land/std@0.201.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.201.0/fs/copy.ts": "23cc1c465babe5ca4d69778821e2f8addc44593e30a5ca0b902b3784eed75bb6", + "https://deno.land/std@0.201.0/fs/empty_dir.ts": "2e52cd4674d18e2e007175c80449fc3d263786a1361e858d9dfa9360a6581b47", + "https://deno.land/std@0.201.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.201.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", + "https://deno.land/std@0.201.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", + "https://deno.land/std@0.201.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", + "https://deno.land/std@0.201.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", + "https://deno.land/std@0.201.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", + "https://deno.land/std@0.201.0/fs/expand_glob.ts": "52b8b6f5b1fa585c348250da1c80ce5d820746cb4a75d874b3599646f677d3a7", + "https://deno.land/std@0.201.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.201.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", + "https://deno.land/std@0.201.0/fs/walk.ts": "a16146724a6aaf9efdb92023a74e9805195c3469900744ce5de4113b07b29779", + "https://deno.land/std@0.201.0/io/buf_reader.ts": "0bd8ad26255945b5f418940db23db03bee0c160dbb5ae4627e2c0be3b361df6a", + "https://deno.land/std@0.201.0/io/buffer.ts": "4d6883daeb2e698579c4064170515683d69f40f3de019bfe46c5cf31e74ae793", + "https://deno.land/std@0.201.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", + "https://deno.land/std@0.201.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.201.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", + "https://deno.land/std@0.201.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", + "https://deno.land/std@0.201.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", + "https://deno.land/std@0.201.0/path/_from_file_url.ts": "6eadfae2e6f63ad9ee46b26db4a1b16583055c0392acedfb50ed2fc694b6f581", + "https://deno.land/std@0.201.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.201.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", + "https://deno.land/std@0.201.0/path/_join.ts": "815f5e85b042285175b1492dd5781240ce126c23bd97bad6b8211fe7129c538e", + "https://deno.land/std@0.201.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", + "https://deno.land/std@0.201.0/path/_os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.201.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", + "https://deno.land/std@0.201.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", + "https://deno.land/std@0.201.0/path/_resolve.ts": "7a3616f1093735ed327e758313b79c3c04ea921808ca5f19ddf240cb68d0adf6", + "https://deno.land/std@0.201.0/path/_to_file_url.ts": "a141e4a525303e1a3a0c0571fd024552b5f3553a2af7d75d1ff3a503dcbb66d8", + "https://deno.land/std@0.201.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", + "https://deno.land/std@0.201.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", + "https://deno.land/std@0.201.0/path/basename.ts": "bdfa5a624c6a45564dc6758ef2077f2822978a6dbe77b0a3514f7d1f81362930", + "https://deno.land/std@0.201.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.201.0/path/dirname.ts": "b6533f4ee4174a526dec50c279534df5345836dfdc15318400b08c62a62a39dd", + "https://deno.land/std@0.201.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", + "https://deno.land/std@0.201.0/path/format.ts": "110270b238514dd68455a4c54956215a1aff7e37e22e4427b7771cefe1920aa5", + "https://deno.land/std@0.201.0/path/from_file_url.ts": "9f5cb58d58be14c775ec2e57fc70029ac8b17ed3bd7fe93e475b07280adde0ac", + "https://deno.land/std@0.201.0/path/glob.ts": "593e2c3573883225c25c5a21aaa8e9382a696b8e175ea20a3b6a1471ad17aaed", + "https://deno.land/std@0.201.0/path/is_absolute.ts": "0b92eb35a0a8780e9f16f16bb23655b67dace6a8e0d92d42039e518ee38103c1", + "https://deno.land/std@0.201.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", + "https://deno.land/std@0.201.0/path/mod.ts": "6e1efb0b13121463aedb53ea51dabf5639a3172ab58c89900bbb72b486872532", + "https://deno.land/std@0.201.0/path/normalize.ts": "6ea523e0040979dd7ae2f1be5bf2083941881a252554c0f32566a18b03021955", + "https://deno.land/std@0.201.0/path/parse.ts": "be8de342bb9e1924d78dc4d93c45215c152db7bf738ec32475560424b119b394", + "https://deno.land/std@0.201.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", + "https://deno.land/std@0.201.0/path/relative.ts": "8bedac226afd360afc45d451a6c29fabceaf32978526bcb38e0c852661f66c61", + "https://deno.land/std@0.201.0/path/resolve.ts": "133161e4949fc97f9ca67988d51376b0f5eef8968a6372325ab84d39d30b80dc", + "https://deno.land/std@0.201.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.201.0/path/to_file_url.ts": "00e6322373dd51ad109956b775e4e72e5f9fa68ce2c6b04e4af2a6eed3825d31", + "https://deno.land/std@0.201.0/path/to_namespaced_path.ts": "1b1db3055c343ab389901adfbda34e82b7386bcd1c744d54f9c1496ee0fd0c3d", + "https://deno.land/std@0.201.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", + "https://deno.land/std@0.201.0/streams/read_all.ts": "ee319772fb0fd28302f97343cc48dfcf948f154fd0d755d8efe65814b70533be", + "https://deno.land/std@0.201.0/streams/reader_from_stream_reader.ts": "fa4971e5615a010e49492c5d1688ca1a4d17472a41e98b498ab89a64ebd7ac73", + "https://deno.land/std@0.201.0/streams/write_all.ts": "aec90152978581ea62d56bb53a5cbf487e6a89c902f87c5969681ffbdf32b998", + "https://deno.land/std@0.205.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.205.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", + "https://deno.land/std@0.205.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.205.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.205.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.205.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.205.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.205.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.205.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.205.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.205.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.205.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.205.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.205.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.205.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.205.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.205.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.205.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.205.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.205.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", + "https://deno.land/std@0.205.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.205.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.205.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.205.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.205.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.205.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.205.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.205.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.205.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.205.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.205.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.205.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", + "https://deno.land/std@0.205.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", + "https://deno.land/std@0.205.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.205.0/fs/copy.ts": "ca19e4837965914471df38fbd61e16f9e8adfe89f9cffb0c83615c83ea3fc2bf", + "https://deno.land/std@0.205.0/fs/empty_dir.ts": "0b4a2508232446eed232ad1243dd4b0f07ac503a281633ae1324d1528df70964", + "https://deno.land/std@0.205.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.205.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", + "https://deno.land/std@0.205.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", + "https://deno.land/std@0.205.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", + "https://deno.land/std@0.205.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", + "https://deno.land/std@0.205.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", + "https://deno.land/std@0.205.0/fs/expand_glob.ts": "4f98c508fc9e40d6311d2f7fd88aaad05235cc506388c22dda315e095305811d", + "https://deno.land/std@0.205.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.205.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", + "https://deno.land/std@0.205.0/fs/walk.ts": "c1e6b43f72a46e89b630140308bd51a4795d416a416b4cfb7cd4bd1e25946723", + "https://deno.land/std@0.205.0/io/buf_writer.ts": "c49d1a3114ad936690847abd0dd2e321e96188546d6e8ae9d22b292b8b59f9f8", + "https://deno.land/std@0.205.0/log/handlers.ts": "3a0883f65567f59a9a88e44c972b24b924621bc28ead91af11d7a6da93c4a64c", + "https://deno.land/std@0.205.0/log/levels.ts": "6309147664e9e008cd6671610f2505c4c95f181f6bae4816a84b33e0aec66859", + "https://deno.land/std@0.205.0/log/logger.ts": "180c50a07c43a556dc5794e913c82946399e89d683201d01c8f0091e1e4ae3fc", + "https://deno.land/std@0.205.0/log/mod.ts": "a274d2129c8d08d4c96e0fb165a595e6c730b5130b437a9ce04364156bfe955a", + "https://deno.land/std@0.205.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", + "https://deno.land/std@0.205.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", + "https://deno.land/std@0.205.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", + "https://deno.land/std@0.205.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.205.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", + "https://deno.land/std@0.205.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", + "https://deno.land/std@0.205.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", + "https://deno.land/std@0.205.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", + "https://deno.land/std@0.205.0/path/_common/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", + "https://deno.land/std@0.205.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", + "https://deno.land/std@0.205.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", + "https://deno.land/std@0.205.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", + "https://deno.land/std@0.205.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", + "https://deno.land/std@0.205.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", + "https://deno.land/std@0.205.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.205.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", + "https://deno.land/std@0.205.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", + "https://deno.land/std@0.205.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", + "https://deno.land/std@0.205.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", + "https://deno.land/std@0.205.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", + "https://deno.land/std@0.205.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", + "https://deno.land/std@0.205.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", + "https://deno.land/std@0.205.0/path/glob.ts": "9c77cf47db1d786e2ebf66670824d03fd84ecc7c807cac24441eb9d5cb6a2986", + "https://deno.land/std@0.205.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", + "https://deno.land/std@0.205.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", + "https://deno.land/std@0.205.0/path/mod.ts": "2d62a0a8b78a60e8e6f485d881bac6b61d58573b11cf585fb7c8fc50d9b20d80", + "https://deno.land/std@0.205.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", + "https://deno.land/std@0.205.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", + "https://deno.land/std@0.205.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", + "https://deno.land/std@0.205.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", + "https://deno.land/std@0.205.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", + "https://deno.land/std@0.205.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", + "https://deno.land/std@0.205.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", + "https://deno.land/std@0.205.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", + "https://deno.land/std@0.205.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", + "https://deno.land/std@0.205.0/path/posix/glob.ts": "86c3f06d1c98303613c74650961c3e24bdb871cde2a97c3ae7f0f6d4abbef445", + "https://deno.land/std@0.205.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", + "https://deno.land/std@0.205.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", + "https://deno.land/std@0.205.0/path/posix/mod.ts": "6bfa8a42d85345b12dbe8571028ca2c62d460b6ef968125e498602b43b6cf6b6", + "https://deno.land/std@0.205.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", + "https://deno.land/std@0.205.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", + "https://deno.land/std@0.205.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", + "https://deno.land/std@0.205.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", + "https://deno.land/std@0.205.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", + "https://deno.land/std@0.205.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", + "https://deno.land/std@0.205.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", + "https://deno.land/std@0.205.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", + "https://deno.land/std@0.205.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", + "https://deno.land/std@0.205.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.205.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", + "https://deno.land/std@0.205.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", + "https://deno.land/std@0.205.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", + "https://deno.land/std@0.205.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", + "https://deno.land/std@0.205.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", + "https://deno.land/std@0.205.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", + "https://deno.land/std@0.205.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", + "https://deno.land/std@0.205.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", + "https://deno.land/std@0.205.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", + "https://deno.land/std@0.205.0/path/windows/glob.ts": "0286fb89ecd21db5cbf3b6c79e2b87c889b03f1311e66fb769e6b905d4142332", + "https://deno.land/std@0.205.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", + "https://deno.land/std@0.205.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", + "https://deno.land/std@0.205.0/path/windows/mod.ts": "c3d1a36fbf9f6db1320bcb4fbda8de011d25461be3497105e15cbea1e3726198", + "https://deno.land/std@0.205.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", + "https://deno.land/std@0.205.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", + "https://deno.land/std@0.205.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", + "https://deno.land/std@0.205.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", + "https://deno.land/std@0.205.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", + "https://deno.land/std@0.205.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", + "https://deno.land/std@0.205.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", + "https://deno.land/std@0.205.0/semver/_shared.ts": "8547ccf91b36c30fb2a8a17d7081df13f4ae694c4aa44c39799eba69ad0dcb23", + "https://deno.land/std@0.205.0/semver/cmp.ts": "12c30b5888afd9e414defef64f881a478ff9ab11bd329ed6c5844b74eea5c971", + "https://deno.land/std@0.205.0/semver/comparator_format.ts": "329e05d914c064590ded4801fc601bf1c5d0f461c5524b1578e10f180551ef6f", + "https://deno.land/std@0.205.0/semver/comparator_intersects.ts": "61920121a6c1600306dbcf8944c4cc55e45c3a1bdbefe41b79a0884bf02d9e1b", + "https://deno.land/std@0.205.0/semver/comparator_max.ts": "f4cc5f528abd8aab68c66bbead732e3c59102b13a318cd8e4f8a47aa3debec76", + "https://deno.land/std@0.205.0/semver/comparator_min.ts": "eea382428ebf0c50168f780898df8519c88da5a10d1f8babbfebdc89fb75942e", + "https://deno.land/std@0.205.0/semver/compare.ts": "782e03b5107648bebaaebf0e33a9a7d6a0481eb88d2f7be8e857e4abbfdf42c0", + "https://deno.land/std@0.205.0/semver/compare_build.ts": "5d6ebc0106f1ed46e391d6c234e071934ba30938fa818c9cc3da67c7c7494c02", + "https://deno.land/std@0.205.0/semver/constants.ts": "bb0c7652c433c7ec1dad5bf18c7e7e1557efe9ddfd5e70aa6305153e76dc318c", + "https://deno.land/std@0.205.0/semver/difference.ts": "966ef286f0bfde53ebfb74a727c607b05a7fdce623a678794d088166b9b9afdf", + "https://deno.land/std@0.205.0/semver/eq.ts": "6ddb84ce8c95f18e9b7a46d8a63b1e6ca5f0c0f651f1f46f20db6543b390c3f3", + "https://deno.land/std@0.205.0/semver/format.ts": "236cc8b5d2e8031258dcff3ca89e14ba926434d5b789730e2c781db172e76bd9", + "https://deno.land/std@0.205.0/semver/gt.ts": "8529cf2ae1bca95c22801cf38f93620dc802c5dcbc02f863437571b970de3705", + "https://deno.land/std@0.205.0/semver/gte.ts": "b54f7855ac37ff076d6df9a294e944356754171f94f5cb974af782480a9f1fd0", + "https://deno.land/std@0.205.0/semver/gtr.ts": "d2ec1f02ce6a566b7df76a188af7315d802c6069892d460d631a3b0d9e2b1a45", + "https://deno.land/std@0.205.0/semver/increment.ts": "a6e5ac018887244731a4b936743ae14476cc432ac874f1c9848711b4000c5991", + "https://deno.land/std@0.205.0/semver/is_semver.ts": "666f4e1d8e41994150d4326d515046bc5fc72e59cbbd6e756a0b60548dcd00b5", + "https://deno.land/std@0.205.0/semver/is_semver_comparator.ts": "035aa894415ad1c8f50a6b6f52ea49c62d6f3af62b5d6fca9c1f4cb84f1896fd", + "https://deno.land/std@0.205.0/semver/is_semver_range.ts": "6f9b4f1c937a202750cae9444900d8abe4a68cc3bf5bb90f0d49c08cf85308cb", + "https://deno.land/std@0.205.0/semver/lt.ts": "081614b5adbc5bc944649e09af946a90a4b4bdb3d65a67c005183994504f04c2", + "https://deno.land/std@0.205.0/semver/lte.ts": "f8605c17d620bfb3aa57775643e3c560c04f7c20f2e431f64ca5b2ea39e36217", + "https://deno.land/std@0.205.0/semver/ltr.ts": "975e672b5ca8aa67336660653f8c76e1db829c628fb08ea3e815a9a12fa7eb9c", + "https://deno.land/std@0.205.0/semver/max_satisfying.ts": "75406901818cd1127a6332e007e96285474e833d0e40dbbfddc01b08ee6e51f2", + "https://deno.land/std@0.205.0/semver/min_satisfying.ts": "58bd48033a00e63bea0709f78c33c66ea58bce2dbebda0d54d3fdc6db7d0d298", + "https://deno.land/std@0.205.0/semver/mod.ts": "442702e8a57cbf02e68868c46ffe66ecf6efbde58d72cfdfbdaa51ad0c4af513", + "https://deno.land/std@0.205.0/semver/neq.ts": "e91b699681c3b406fc3d661d4eac7aa36cd1cc8bf188f8e3c7b53cc340775b87", + "https://deno.land/std@0.205.0/semver/outside.ts": "1d225fdb42172d946c382e144ce97c402812741741bbe299561aa164cc956ec4", + "https://deno.land/std@0.205.0/semver/parse.ts": "5d24ec0c5f681db1742c31332f6007395c84696c88ff4b58287485ed3f6d8c84", + "https://deno.land/std@0.205.0/semver/parse_comparator.ts": "f07f9be8322b1f61a36b94c3c65a0dc4124958ee54cf744c92ca4028bf156d5e", + "https://deno.land/std@0.205.0/semver/parse_range.ts": "39a18608a8026004b218ef383e7ae624a9e663b82327948c1810f16d875113c2", + "https://deno.land/std@0.205.0/semver/range_format.ts": "3de31fd0b74dd565e052840e73a8e9ee1d9d289ca60b85749167710b978cc078", + "https://deno.land/std@0.205.0/semver/range_intersects.ts": "8672e603df1bb68a02452b634021c4913395f4d16d75c21b578d6f4175a2b2c1", + "https://deno.land/std@0.205.0/semver/range_max.ts": "9c10c65bbc7796347ce6f765a77865cead88870d17481ac78259400a2378af2e", + "https://deno.land/std@0.205.0/semver/range_min.ts": "b7849e70e0b0677b382eddaa822b6690521449a659c5b8ec84cbd438f6e6ca59", + "https://deno.land/std@0.205.0/semver/rcompare.ts": "b8b9f5108d40c64cf50ffe455199aba7ad64995829a17110301ae3f8290374ee", + "https://deno.land/std@0.205.0/semver/rsort.ts": "a9139a1fc37570f9d8b6517032d152cf69143cec89d4342f19174e48f06d8543", + "https://deno.land/std@0.205.0/semver/sort.ts": "c058a5b2c8e866fa8e6ef25c9d228133357caf4c140f129bfc368334fcd0813b", + "https://deno.land/std@0.205.0/semver/test_comparator.ts": "eff5394cb82d133ed18f96fe547de7e7264bf0d25d16cbc6126664aa06ef8f37", + "https://deno.land/std@0.205.0/semver/test_range.ts": "b236c276268e92bbbc65e7c4b4b6b685ea6b4534a71b2525b53093d094f631c6", + "https://deno.land/std@0.205.0/semver/types.ts": "d44f442c2f27dd89bd6695b369e310b80549746f03c38f241fe28a83b33dd429", + "https://deno.land/std@0.205.0/url/_strip.ts": "86f852d266b86e5867f50ac5d453bedea7b7e7a1919669df93d66a0b59b00e5b", + "https://deno.land/std@0.205.0/url/basename.ts": "1257643f9934b65696d8af3ad993b3269d55231e6258ac13fba3d4fe193f30be", + "https://deno.land/std@0.205.0/url/dirname.ts": "65a0c5d4a62a6505404ea992fb73a2201c66e208aa7dfeb76d34f275432eddd0", + "https://deno.land/std@0.205.0/url/extname.ts": "d16f2a3bdccd1ef389a0a066a8275fa59089a04ae98cb69d753e228845d6256f", + "https://deno.land/std@0.205.0/url/join.ts": "fbc3488c641c38832f0c900fcf99cb970164d8e32b84f1427581bb83cf35efeb", + "https://deno.land/std@0.205.0/url/mod.ts": "d4e4db2f85a4a1613d824367b750f36bbd1c0ff791daae2eb74795d292c722bb", + "https://deno.land/std@0.205.0/url/normalize.ts": "5c5803452521a36faec1a91bdb665e1cbdf7ce22bc0482388ad79f229b74cd45", + "https://deno.land/x/base64@v0.2.1/base.ts": "47dc8d68f07dc91524bdd6db36eccbe59cf4d935b5fc09f27357a3944bb3ff7b", + "https://deno.land/x/base64@v0.2.1/mod.ts": "1cbdc4ba7229d3c6d1763fecdb9d46844777c7e153abb6dabea8b0dd01448db4", + "https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_errors.ts": "12d513ff401020287a344e0830e1297ce1c80c077ecb91e0ac5db44d04a6019c", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_utils.ts": "3c88ff4f36eba298beb07de08068fdce5e5cb7b9d82c8a319f09596d8279be64", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts": "ae690745759524082776b7f271f66d5b93933170b1b132f888bd4ac12e9fdd7d", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_zsh_completions_generator.ts": "c74525feaf570fe8c14433c30d192622c25603f1fc64694ef69f2a218b41f230", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deps.ts": "7473ebd5625bf901becd7ff80afdde3b8a50ae5d1bbfa2f43805cfacf4559d5a", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/_help_generator.ts": "532dd4a928baab8b45ce46bb6d20e2ebacfdf3da141ce9d12da796652b1de478", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", + "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", + "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_utils.ts": "fd48d1a524a42e72aa3ad2eec858a92f5a00728d306c7e8436fba6c34314fee6", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/cell.ts": "1ffabd43b6b7fddfac9625cb0d015532e144702a9bfed03b358b79375115d06b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/consume_words.ts": "456e75755fdf6966abdefb8b783df2855e2a8bad6ddbdf21bd748547c5fc1d4b", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://deno.land/x/compress@v0.4.5/deps.ts": "096395daebc7ed8a18f0484e4ffcc3a7f70e50946735f7df9611a7fcfd8272cc", + "https://deno.land/x/compress@v0.4.5/gzip/gzip.ts": "4bf22e9cd3368332928324dd9443ef72cabd05e9234e5a37dd7b3517d50e945e", + "https://deno.land/x/compress@v0.4.5/gzip/gzip_file.ts": "b044ec0df4266c084baa033a4ab5394882e44a86d09d5616636467dcb39c671d", + "https://deno.land/x/compress@v0.4.5/gzip/gzip_stream.ts": "6781cf0e47648e3e5631cba4cc2cd018a24935ce09fdaa86e0cabcf78b5012df", + "https://deno.land/x/compress@v0.4.5/gzip/mod.ts": "4ade8edbe01b54a84f289351e137ebdfc040a74cd616636770cf1724fbf522d1", + "https://deno.land/x/compress@v0.4.5/gzip/writer_gunzip.ts": "5aba34394820b835c414048ac2e15f52d443f1f773ebe61fd2517c938572d616", + "https://deno.land/x/compress@v0.4.5/gzip/writer_gzip.ts": "c7aad0c51ab4f5952c068088186339cfc79a2ee1e057d6e16731b1175f342645", + "https://deno.land/x/compress@v0.4.5/interface.ts": "fc5f87bd208ab8a03a1f65972b11781967c3d21c3d756fe9ae99ca98e10e5780", + "https://deno.land/x/compress@v0.4.5/mod.ts": "ae8b15826334021583a5bd1978c63840f85156ea3635f5941bfc6733aad247e5", + "https://deno.land/x/compress@v0.4.5/tar/mod.ts": "6d9073005e678479908047cbe9e4716e484f80d1f2a1e15d3d6ac92213ffaeba", + "https://deno.land/x/compress@v0.4.5/tgz/mod.ts": "2fd4e99f26b57b0055d4d2f87721682304541ed1ca41bbb49c034d121f936f00", + "https://deno.land/x/compress@v0.4.5/utils/uint8.ts": "9c82e09c065f1f4bc648e3b14df441b43a7960fc7bdb29e9fb8d3a69c7e9d425", + "https://deno.land/x/compress@v0.4.5/zlib/deflate.ts": "e1e3b406dcc3e20021e53cde427b4b9ced752b72df820de73fec17c6e5ba999e", + "https://deno.land/x/compress@v0.4.5/zlib/inflate.ts": "618cc3dd25d202bf6b89d92f3ab2865e7495884cafce950638c77cbc1537aeb1", + "https://deno.land/x/compress@v0.4.5/zlib/mod.ts": "4dca9c1e934b7ab27f31c318abd7bfd39b09be96fd76ba27bd46f3a4e73b4ad0", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/adler32.ts": "e34c7596d63a655755c4b0a44a40d4f9c1d1c4d3b891e5c1f3f840f8939e1940", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/crc32.ts": "b9bc4adaf327d32585205d1176bd52f6453c06dd1040544611d4c869e638119c", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/deflate.ts": "8d1dd88630279313e50deed4fe5feefe8128307cc48fa560e659b5234ab09d83", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/gzheader.ts": "11e6da7383447aae9791308dc2350a809fa341a876a2da396b03a2a31408c20c", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/inffast.ts": "282daf5ea16bb876d26e342f3c24fe1a8ec84640e713a970b02232955a853f86", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/inflate.ts": "76751c1a5b18d70a929fa31ce4959db0bde1b9097bfa1b5ea3b4d1fba2ab92fa", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/inftrees.ts": "8a6d765a5c42bf3b6990060cabbe52e88493f8ce6d082e6e35d97756914cfb8e", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/messages.ts": "c82229bd67ccc3b6162f3aca1c5e7f936e546aa91ac9a9ac4fcfefc3a9dc5ac8", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/status.ts": "5987864d2d43d59bbbfa2e6ef4d5a07284c1d10489cc5843ddf41ac547957ac3", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/trees.ts": "6b65a767646e031e87e7b725ffad0c511fe701f393a01652e1e7ee8884f60fee", + "https://deno.land/x/compress@v0.4.5/zlib/zlib/zstream.ts": "c110fd5919235e317d64933852e24a1bba0126202be592e90e58f7b19315ad93", + "https://deno.land/x/crc32@v0.2.0/mod.ts": "de7a3fa2d4ef24b96fc21e1cc4d2d65d1d2b1dcea92f63960e3e11bfa82df0fa", + "https://deno.land/x/dax@0.35.0/mod.ts": "3fc382546bf3c7b90aa458aa144be7c6e8aed3e8c2680289f9c8694d986b7247", + "https://deno.land/x/dax@0.35.0/src/command.ts": "6e7db06015b4ad6decbf59cc5fcb6bd4b03a46276f7e3f3472204c11b2109e0e", + "https://deno.land/x/dax@0.35.0/src/command_handler.ts": "841cee0ce12b19eea6c7fcaeaa40a9e3ef4bf50c36cf02afbe3ab7b41f8571eb", + "https://deno.land/x/dax@0.35.0/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", + "https://deno.land/x/dax@0.35.0/src/commands/cat.ts": "229dc854f80ea8f1ebd811190fc31e5cf0fe39f76c2de1c27e256cb831237cb0", + "https://deno.land/x/dax@0.35.0/src/commands/cd.ts": "239fee1606881dbc3f778a761d1d4557c21a63063c15ab58883a32e7466b7177", + "https://deno.land/x/dax@0.35.0/src/commands/cp_mv.ts": "58205a82a9404e444c7c5caf98b5dd2b350c668c0b421546a038b76ea8b6a53d", + "https://deno.land/x/dax@0.35.0/src/commands/echo.ts": "247909de5b8ea20218daab419f3aad37b69763052272aca3633fe8e7f83148cd", + "https://deno.land/x/dax@0.35.0/src/commands/exit.ts": "c619e52d744dfa3e8fa954026f1c5302d8be991c775553efc85a0f224b77b6ff", + "https://deno.land/x/dax@0.35.0/src/commands/export.ts": "b6ecad1203cfe606d69da6c16736f31acf211e864e6822484d85cea1cb7d5528", + "https://deno.land/x/dax@0.35.0/src/commands/mkdir.ts": "9381ecdc0e0203d941f89027b6ef2865393bf0a66670bf5f5aaa6a49669244c7", + "https://deno.land/x/dax@0.35.0/src/commands/printenv.ts": "473c39b457cae91e9ca029ad420642b9a410257fb699674660c886c6ebe72ebc", + "https://deno.land/x/dax@0.35.0/src/commands/pwd.ts": "5438aea979027bfa5c64c2a7f1073389735ea986f6abe2174ec21bcb70a2156f", + "https://deno.land/x/dax@0.35.0/src/commands/rm.ts": "d911ff4e2e0b3d3c5d426c7b735313741ad762d9e25a743f101a1b05447eecf8", + "https://deno.land/x/dax@0.35.0/src/commands/sleep.ts": "d1183fa8e31ba85a7b88666e854c7aa6e53e1d4c65e39f20a05d8ea4b82efca3", + "https://deno.land/x/dax@0.35.0/src/commands/test.ts": "a221f82c209fd53756e9c02c475b9d5833284513853e90fdaaf0c1e1d9cfbf30", + "https://deno.land/x/dax@0.35.0/src/commands/touch.ts": "5953dbde8732da47ade9b7554a638ea06a8b67a59842e638fb79f7aebe392650", + "https://deno.land/x/dax@0.35.0/src/commands/unset.ts": "8d4abb29f53c3de0c10ba6d51e3d55bce745160f7430396ede58156e8f2b747c", + "https://deno.land/x/dax@0.35.0/src/common.ts": "c0e809c591400dbadb25197f2819c59fec6b897c94c1aba6a026d5d1eee9cb53", + "https://deno.land/x/dax@0.35.0/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", + "https://deno.land/x/dax@0.35.0/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", + "https://deno.land/x/dax@0.35.0/src/console/mod.ts": "29ae1f8250b74a477e26a3b6ccf647badf5d8f8e2a9e6c4aa0d5df9e3bbbb273", + "https://deno.land/x/dax@0.35.0/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", + "https://deno.land/x/dax@0.35.0/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", + "https://deno.land/x/dax@0.35.0/src/console/progress/interval.ts": "80188d980a27c2eb07c31324365118af549641442f0752fe7c3b0c91832e5046", + "https://deno.land/x/dax@0.35.0/src/console/progress/mod.ts": "70080a5d06ab2c58e948225e1e5144458fbc36fbfa61672ac82bb2f6c6991bad", + "https://deno.land/x/dax@0.35.0/src/console/prompt.ts": "78c645b41a7562133d05a10901ae4d682cb22bfaf0b5a21cc8475ca2a946aee1", + "https://deno.land/x/dax@0.35.0/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", + "https://deno.land/x/dax@0.35.0/src/console/utils.ts": "954c99397dcd2cb3f1ccf50055085f17c9ffb31b25b3c5719776de81e23935f4", + "https://deno.land/x/dax@0.35.0/src/deps.ts": "709fcfef942331cbc97c1faf37dbff8b97c411fac1d142106027ca5bbe64df59", + "https://deno.land/x/dax@0.35.0/src/lib/mod.ts": "c992db99c8259ae3bf2d35666585dfefda84cf7cf4e624e42ea2ac7367900fe0", + "https://deno.land/x/dax@0.35.0/src/lib/rs_lib.generated.js": "381f2f60b458bcb0a6fec1310c2c3b6447339f6995df206b9a4d0c3747ee8c36", + "https://deno.land/x/dax@0.35.0/src/path.ts": "5e1ea6139a975d31d6a5ca62c96c095ff7ddcf5c34ef8b75ab0ea04f87ac579b", + "https://deno.land/x/dax@0.35.0/src/pipes.ts": "3aa984c0d031f4221953e228ba89452a86068a80d2811fddb9c60737cd4ab174", + "https://deno.land/x/dax@0.35.0/src/request.ts": "a2b20859de7a0fbe10584a41de435942ee4726f0b637b1cb55d7f632f4efc74f", + "https://deno.land/x/dax@0.35.0/src/result.ts": "0908b69c16b25c3b258f6b2ada12e124686df5f7ea2b98daa27a83973c7b118c", + "https://deno.land/x/dax@0.35.0/src/shell.ts": "9475a015d5493197f9611b1259c5dd6d27c7c2ab9c3711606cd4b47412568ee1", + "https://deno.land/x/denocker@v0.2.1/container.ts": "b13512efec6ec20655ddd263732f1386ac05b72926f9a45e9142b2200ff71275", + "https://deno.land/x/denocker@v0.2.1/index.ts": "c9f67e6ce29d7d6cc0516c1d0aedc4b927d66338a75226020e9a5670b8167452", + "https://deno.land/x/denocker@v0.2.1/lib/client/auth.ts": "dc672975a3356bc9e95e29106e6526cb27791daf7be340a1af55a85f4fd44897", + "https://deno.land/x/denocker@v0.2.1/lib/client/client.ts": "d612ec1f2c3dd50a0b21a21a4aabafa470e41bce6c9bda83390bf8e3b058f713", + "https://deno.land/x/denocker@v0.2.1/lib/client/httpClient.ts": "1e2e93b1b98c91f353371b14ec878d3eb646cd8ea1da1407a77cf9445ac0837e", + "https://deno.land/x/denocker@v0.2.1/lib/types/container/container.ts": "496a0aaca6e892cb1c6563334ad10f3fba0d72f26a7fbc0608ce74822009b7d2", + "https://deno.land/x/denocker@v0.2.1/lib/types/container/create.ts": "6fba0a043a4c7d21a1757ac845af1afc90612a4a08c62df383c6db919c7e739b", + "https://deno.land/x/denocker@v0.2.1/lib/types/container/inspect.ts": "83c70a637962c420fd19eb477e4357dfcb909ed698c735ae5bec4c424418edfa", + "https://deno.land/x/denocker@v0.2.1/lib/types/container/list.ts": "0593156829cc2cab0a7ed52c1e2b66c6a3a082bdc6de5011db3f5a3c0d717369", + "https://deno.land/x/denocker@v0.2.1/lib/types/container/mod.ts": "06b040fa7315aca141e0eb36a9993e1ca9d165694a3d46abac4dc0ddfde9e574", + "https://deno.land/x/docker@v0.0.1/deps.ts": "3fcd7d37f373abaab7e46d865b232ffc3061bd98310d00db6822a9066c05ac67", + "https://deno.land/x/docker@v0.0.1/mod.ts": "930d1a8890b6eb4a9b6334d245d536150079773f5d8df1fa624fe530a7988a80", + "https://deno.land/x/docker@v0.0.1/src/client.ts": "3b6abd8ac73b2364222acd43dc0a4cea8531278005b3047e2d7b1b9e2bf54916", + "https://deno.land/x/docker@v0.0.1/src/system.ts": "567c9b48da5ac913b63a12f4a52bee461ff9b2f5260e459457bfbf5c2b524020", + "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts": "c350ea5f32938e6dcb694df3761615f316d730dafc57440e9afd5f36f8e309fd", + "https://deno.land/x/foras@v2.1.4/src/deno/mods/mod.ts": "cc099bbce378f3cdaa94303e8aff2611e207442e5ac2d5161aba636bb4a95b46", + "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.js": "06f8875b456918b9671d52133f64f3047f1c95540feda87fdd4a55ba3d30091d", + "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.wasm.js": "2df8522df7243b0f05b1d188e220629cd5d2c92080a5f1407e15396fc35bebb3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/GraphQLError.js": "18adbba7aa651770e0876d0c7df4e6e2ab647a9f09d4b5c107c57d6fa157be9d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/formatError.js": "aec87433c501df6d6272b64974e8edf53b2ed192e66782b827328d635ed55df8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/index.js": "7557dcea8830550f82dd7b1984fdc216e14327d094f501bd2a03f80bf609a768", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/locatedError.js": "65ad5e9246747d2b63cd2ea48fa22db617e1c7c2b796a27b8ce32bfb0f2a401c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/syntaxError.js": "9c53411030cf5f4e874e9d1c1926f242e4acc4b469197b395ae1a9a7a9092055", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/execute.js": "2d3114e49268a50195195039cc14629b0fe3dff7afeafed9876f1532a95ed4e7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/index.js": "064615af63e0da1584c557ce130b3ca42acab750289a085ebcc3107aa024ce52", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/values.js": "e9a42ff1593db210a47504eebcc145f70f8d97cfe53c4cce345116bb420fbf95", + "https://deno.land/x/graphql_deno@v15.0.0/lib/graphql.js": "c34b19e78a57ad0a0ac9b39ec2766ab755b23e61ea08edf124dda1d60104ae54", + "https://deno.land/x/graphql_deno@v15.0.0/lib/index.js": "ab428112340017d5b1f74e62f958cf0c50c17b664a33be53974ab8738a531c1c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/Path.js": "28322158c8208f92d376969de58c30ea393aada1beb44223d40584d8d89285c0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/defineToJSON.js": "5423bbfe56faf7c3b0cda65b515c7fcc0e111bef46da9f76fed93eaba1859149", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/devAssert.js": "7493987e163c294b31f354091b6ca5e87849a132aa3aad8e3c173ac008c8e970", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/didYouMean.js": "9b83a8fe7bdd5b02012c097de14a0fe08a5e33662624ce55eb0019f46067b974", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/identityFunc.js": "204f973b6a5fc6f60ea62641e58eed254f0bb7d76b2b05e9c0bae54dc7cd324a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/inspect.js": "bb756ad067f23b137b63bb2dabc4e50b070255da2ff10068d9f0a174dd5f3aae", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/instanceOf.js": "c2940e2f0c71ed73390ab03ceb04484c40a4f5f28973709702c8279b1b6f96cd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/invariant.js": "b8d3e8438abe0ec591fdc3a3f0f9c2d7750925738ee146ba7a18173a8f2dc1cb", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isCollection.js": "42ffc930d5982ec031b4a7f29db9fd7f3a9e488ca0ceb73183a51a67ad146e2b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isObjectLike.js": "96403248d91ae55fa67ce81d339bec098aa51e5fc7132eae3f8dcce91ac340ce", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isPromise.js": "dbe9979b7d4ffd920d8a608a9ce7127b943b130822cd907d78b9e56e66843509", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/keyMap.js": "4e54790d45a13998d198b0b7b1bc20ae17259e8131c29821d67e64b96dcb249e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/keyValMap.js": "515ebb0c4be9c26f8fa38b7e0db4f2ebf96057196a8fcd52078046f05bf4e4e0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/mapValue.js": "5df0be208d71e372a693ef305287a8f041b061a293cff030aa10bef4c2735981", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/memoize3.js": "4212fbf3f75fc8625a8312b18e0b2907efaf9cf6b8131c78a731a707ba638c74", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/nodejsCustomInspectSymbol.js": "eb85744be5bab6c4511556b095198b45259b2c020ff2f88f3b04197acc9feb01", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/printPathArray.js": "15110572999ddb82bcde2e42a35092d18fa601f5b8312e5823bd43d48f9b773f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/promiseForObject.js": "b82720027d9bf3b81f302baa8d72f80b5d04f2069ebb538875f22993be83ee11", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/promiseReduce.js": "178a7c704a3c124a68a60aa71f29fb078268e403dab310b41937d10f5bb55e01", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/suggestionList.js": "4504e10e0e9b230cd697ebcf1ffae9b2ae420db2927a44dfbab452558e2f4b4a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/toObjMap.js": "482c18508c808bca6a8e657ab7e3dfacadbf9f78603ce452700199d02f1b9d32", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/ast.js": "faa944fc543e4997c82302a6230c190eaf72bfcca1b8db3b29502826bdf9e9bb", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/blockString.js": "413ad9886dee36a96875cea8bbb51763b1ab8f1c3e82b8468330e8a5ca877852", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/directiveLocation.js": "8500c3878eafe0142f1b1255a998e76b20d3f859d9016ef9f7252878926dfdd8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/index.js": "9289b05fbe173c54e727465996239a3bce6057fb727677ca44af37d60409480d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/kinds.js": "7bdebb8110e7345fe6c14f255225d7c9b5448fb2dd53ea91d035a4b2e1e8f72b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/lexer.js": "68bf4d832a8d1b4af3a0432ad308bc6f3ffbf60eb1c4e786503b30aff7e17564", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/location.js": "9994a20bee8d97ff69781b35477f596a3e6bee9ac1d914340e435bc0298922ee", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/parser.js": "53af901714fda7e62137fe2fe9eccecb4945a8648cffbb93ace439a0e445f05e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/predicates.js": "cfd7e10b724590b618f67845ce1d1ca712b7da5943622b40881e7653f5557ab7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printLocation.js": "26b21c32abaee20e11a072984c381384dd5645be50bcbbdb59bac435490a60e9", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printer.js": "a2da61f578725d78182fadfdc0ce809c30ed7d6b8369b98b1606b68b0e949b39", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/source.js": "e2a870f2446abf092996453d87f98214f12898e83980b76706cc8160993f16a8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/tokenKind.js": "ad57e9dddaca5336e49a715b710c8142d9ff963270f3d4d6b63348a482922a74", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/visitor.js": "47b0a0f0d2f1e1edbf18a4b26861d371c41c0bfebf7c3f9ccb2e423663184dcd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/arrayFrom.js": "b57194d34b98ac5926ae83e0a83e884d63ee9ec2d2ae3e846f1783cb26f6dbf8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/find.js": "30ebdb4e2cd0ed5b0e3e4a00877f2bd2ac62de6d70a4c63152021fe724e41911", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/flatMap.js": "29d62564cd9745536e366e09b7f6cbe8860d8423c00ca253ae6cbfba839f2fc6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/isFinite.js": "231e2d149aa58e8288b6cf86f588a1597f3f8d432928a17af9c348b48bd8ddde", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/isInteger.js": "0b68164ae12c46b16e439a5384af0c8c395f5c941e0f03fb71ee2ee9cb45fae6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/objectEntries.js": "c261873f15f88ecb1497427844a9afc541168a92b7d75d69446732a0d381c819", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/objectValues.js": "7c3ed07793a1685cf018fa028275ff556213ba953c43dd2410bb549a86ee801b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/symbols.js": "bbe907378637a4264484590815d80563cfbd8a8a5f396ac9d2be4107658e5bf7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/index.js": "3c2e03b8225f66a541d870fb6729ad2e3afbd923a0d07713a087c22d134097b3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/mapAsyncIterator.js": "ff581dcfccf8aeedfefe1c42a8f01c7898c1a2b093e5f6fb4d2e62b2f83ab8c8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/subscribe.js": "f4d316d4f7e3e60cc3f9f8371e6122396653ff1836a27d44a3e8bf1cfa3d5def", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/definition.js": "94ef1069f5e034f9dc34ca7f25370fe81a58365acbb6e932ab9e0db92efe8287", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/directives.js": "d4614736de1997413a4970b73865c99d3b01922eda3de1e587c27c25c24e8892", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/index.js": "4a7d837f30c56cf3074ee527f4b9388d160ad20cd545d4fdebac500acdf102b0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/introspection.js": "c63b4c5a578d76f8d159837ce531e1193dd3a2629148aedb895b511141303afd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/scalars.js": "d447913dbdcaaa32f2eaa5d86bd39b5342bfcf78d9ddd6b2173ad717ae7fa734", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/schema.js": "1c92f80cc2c1f0a14a5b3fcb13d55f215f14e1b2bb7b92c2674ede22f969f033", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/validate.js": "f8300538e4327b46959ff83a7dabd55e42148ef07fa66c79f72b308a46cfd369", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/TypeInfo.js": "03276253d0ab3d4947f7bf63ca4ccc4fe0073f73063952103530f8193a98e546", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/assertValidName.js": "d6b44f4dddcc50ebe92c7cb9b94ecc9b27627994b3c156ce63d33279c7f7d0e4", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/astFromValue.js": "d460204b065b8e1036e215faa9a617a20c268e687ed424208dabc5cd5f40f2d1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildASTSchema.js": "d4aeade98d0a1bc6ffdc160fdc78e9edf22a9efdc550cdc6121d1c83ff1e6c84", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildClientSchema.js": "1ac64f6aacdf57e9b552a0c45e7176c988c0d97310f646d7d048899524856d3a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/coerceInputValue.js": "ee59bf0ed0857af92c4f40a309365fd57770821b3c788b6b13d0d1051ffec0da", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/concatAST.js": "bf410b42f7aa015ff0cd938623894906ac19329d9cd9a2298cf97de7cb01442a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/extendSchema.js": "802fbd291d21cfa97c1ebea4fa6528ac6cddbbcc27c169942c975ce708a9f295", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findBreakingChanges.js": "2c8dfd8acf617e5d8a96e389ba6bccb0845fc4470f680957a1ca64514337574a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findDeprecatedUsages.js": "5a5366e6ed3c99e16d73b1235ee9062ac10d3ec16afafc4d6c4f635cc12370f6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getIntrospectionQuery.js": "81125e8afec11871bf532dff9dd523016292c5a57f84d0c04a142a6704ba1572", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationAST.js": "5fef27e56f708c5336a78e611f08627122a6673d10ea1db6f5466c6f984ad9a2", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationRootType.js": "58addddae7feabecce40191d836f7e65a9478cf0d9df6c360c4c30e6f5f6f09c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/index.js": "d0e34dcf95627362527e93e7eaa970caee63b40ed04ff1da320b6426484c8278", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/introspectionFromSchema.js": "50a2202f28b25e98bc496f151de8fc6b2feeb81bab9739fee97f66ac30ef8652", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/lexicographicSortSchema.js": "ad2b935289966fc75700a3b511b86e79130d505399b0c2de052e9915624e2c2a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/printSchema.js": "104a4666bf3e745ad1fbd30c227d3e06902a2b255c95e29183dcd6d98fad76be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/separateOperations.js": "ac7e7789fddc0fcf2b439897fce86601a16c6d3f4e20b7f6837278bcf1c36a56", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/stripIgnoredCharacters.js": "be44c652bfa218b18e5d4387a068d2e431d75e770ddc38ced6eb95a0b8854eab", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeComparators.js": "dcda7fffeaf3999bfd993a4faac36547dace3ec3f78d7686f4334f86a6c3aec0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeFromAST.js": "99cd615599626259b8e2b9974e22bf8beb495f541b20d78f4a01d48de2f97942", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromAST.js": "26098b7e5074d408f877f8b4566e6183cd77f076ee2e3f1413e53c4677e150ce", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromASTUntyped.js": "9db575f95392635bb02dc417d920e9ae6552b2fa871b19e94ad0cc9ceb83c892", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/ValidationContext.js": "52808fc712f4ef50f79edcb30cb907721e449b55c54225551d2ad09660351dba", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/index.js": "1b91224c90bae4b49c6b8227c190c34cb3600433423c257a672801e12910c06d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ExecutableDefinitionsRule.js": "f94c0c866335d1c249d08efa55da0dd6e5954844fa7851b6a0d6b3cd86383819", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FieldsOnCorrectTypeRule.js": "587eb8a9e7376e58ea6a0bbb12585ca45621a0be102dc7e6c641adee9652e91d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FragmentsOnCompositeTypesRule.js": "6a6172831b4e7e6431af810505e23bdf82b7f46d92cfe719226c549a36a75b92", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownArgumentNamesRule.js": "2ab0571f6a6b9c5486b76a09955efde97ef3ff7e9cc06ecfcec8e1f830b8e79b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownDirectivesRule.js": "57f4c9df617702d3695c3010f6822641b4c7343c65a4873bab6b29c911424e2c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownFragmentNamesRule.js": "24273c31aa208652113a2e635853c336227a2756fc642ece6e977bd9c4ccb094", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownTypeNamesRule.js": "b600453083f32688c43f2225399439b4f6312903397572b947ea4fc80ac17b52", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneAnonymousOperationRule.js": "ec860c3ce338509b1e407c5cea36aa6e97e34ed207e5cdf719d6f7fa4026398c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneSchemaDefinitionRule.js": "d3e40186003d89bd45c751156a6df69af6970beb5493c891cd79e1eaab734968", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoFragmentCyclesRule.js": "1445f37ad14819d117371c70acdc725459d89b9a8773dd2c15e131f5d71b92ac", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUndefinedVariablesRule.js": "7aaf4409398526fef667fa6cede56b60824166409247b62c2f74b2c4a1ea5497", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedFragmentsRule.js": "9d1c9b11d6287f077cff8661c704f9aa0fdf53de0b4894c4e7fe29597a32a704", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedVariablesRule.js": "8d477bdbc19eb02556e7899b97b147511ccb69777ae29136c12e2f49d6c7a264", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/OverlappingFieldsCanBeMergedRule.js": "5f74a4c076f8c18ce6371bfda2d213c4461655f830fb506569a1cc71faa56e96", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleFragmentSpreadsRule.js": "0bf927e91d4103c630ef3a10b586586ababb1412874917fed1b5b908d40bc54b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleTypeExtensionsRule.js": "2c1f8a1371656ff8c992d0e4df5500ecb36475286b5f131d9a880018baf00c73", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ProvidedRequiredArgumentsRule.js": "03d3af937d5e08c544a10911770f0660ed6eb18d2b8877acc677aac90583f86e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ScalarLeafsRule.js": "374bba26d4e9e085560ef20179604955ae191e7c68c85f2f1905fb68686bc079", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/SingleFieldSubscriptionsRule.js": "8a748eb20d1b21fee38aaeae7b368893f113b5b35bbd0fd94c22df072d23e0be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueArgumentNamesRule.js": "fe7fafd8d4a5efe66f0d690ef868f55f46c78512cf98abc1bd6ab4c175b86d79", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectiveNamesRule.js": "6664f72231dfa4fa4337ffeed4868a95e91970825bd45f77d7d4f92bcb49bb50", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectivesPerLocationRule.js": "0aedfb8194bd8630b84e344a8ff0f6572bb169ad290a18c8391000324c4d8a03", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueEnumValueNamesRule.js": "fbfec070d1c6ca58514f0e459b7dc5c8fe505d81bd1323509802d65db1463272", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFieldDefinitionNamesRule.js": "3da7356eaaf7ae41bf5b86c7c55c4049b962bd9d5339252a90cc46538714b694", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFragmentNamesRule.js": "523f0dfeb1ab059bd396d240376e3b602d32405826dd53d8b2e575aaebd7c055", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueInputFieldNamesRule.js": "433e878697932b4b5cd82dc9282f10650d568360afcd68a6130b9a54ef21e7f1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationNamesRule.js": "1de59fc010ff56ef74c856d23f949c523de78f299e9aa7c6d882f1f49959e3be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationTypesRule.js": "2faa2b9c0e9c0197f053162e1356dbfa819e02f0c294271873576ac4ea5786fc", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueTypeNamesRule.js": "d01564b48843b9a316b314f1f9079a25d381bbe02bb47bc47dca0a144fdb596f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueVariableNamesRule.js": "5522d2278b64aaf70c30d0dc5e37f70bdbde0201bd6c06c136e0d9d50b0a3c4a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ValuesOfCorrectTypeRule.js": "f50002aac89fa875cfb594c7696d0d6eaa8ad39d699e3d0550cda34799797faa", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesAreInputTypesRule.js": "13337bf58985546627c8060c3ff1201dd21bb6d67e5293819ad0eefa8196a144", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesInAllowedPositionRule.js": "3032c0af7f9287cb10230685807e1460f12775e24ac76f88496769ad0acc43f0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/specifiedRules.js": "0e2f6954eb79701a15731f528209a5616ee028133cbf7d4fbecb4e88ac5028c5", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/validate.js": "44bf2bc96845af0bf77e4421f0a05ee57cdc2a688abb32ff6270eacc501327d1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/version.js": "a135d0beee3fa1405733453e86f1c887e4771d56357504dd78eaa872c0a2b482", + "https://deno.land/x/graphql_deno@v15.0.0/mod.ts": "c85d6ab2a9c22ff494bfaa5f662507249650419af6c6c27a7a5c8175de61870d", + "https://deno.land/x/graphql_request@v4.1.0/mod.ts": "a24c6f401b8253bb84c773583bf2b37b1b74eec960639f36eb874ca17972f9e7", + "https://deno.land/x/graphql_request@v4.1.0/src/createRequestBody.ts": "80b40c902de346622c222a068c4f96679a1bb3233c9b31d728518566ca4e679a", + "https://deno.land/x/graphql_request@v4.1.0/src/index.ts": "15194aba3fdfc1ba896e7fcf9c11227dd1962b635c7d2101e23ed2e0a57cd900", + "https://deno.land/x/graphql_request@v4.1.0/src/parseArgs.ts": "d00abde98728be96c902debd7ee5032b05f69fb6bb00f86a2b5adf09159541aa", + "https://deno.land/x/graphql_request@v4.1.0/src/types.dom.ts": "56432fd7ce0aff31542ec7198ff1a3ea5e3d62fda009510f78bf9c0fc47af13e", + "https://deno.land/x/graphql_request@v4.1.0/src/types.ts": "63a98d9f8efb1d6a580bbef62861c764f0f0b0d3dda04674f4d9a32d6490440b", + "https://deno.land/x/http_client@v0.0.3/mod.ts": "0ab3466319925fbc7c118024bd96e86f7cded7f8e7adade336a86e57d65229f4", + "https://deno.land/x/http_client@v0.0.3/src/client.ts": "2e4a9b7e735c2fcf6569b590c1390ffe6560de175d26bc77f32cd7769ab42d95", + "https://deno.land/x/http_client@v0.0.3/src/common.ts": "048ba99bcd9527cef687d44c6d5d1cf2cdbf2968149d233455098c105b50ef76", + "https://deno.land/x/http_client@v0.0.3/src/helpers.ts": "c91aec2f0158e0f6284a98947065b08de21818dcdaa4cac78daacfad5d332146", + "https://deno.land/x/http_client@v0.0.3/src/request.ts": "39ef37dbd2ea8115c284e269259feb4c839367410687a2371580206d6b2a7210", + "https://deno.land/x/http_client@v0.0.3/src/response.ts": "1f47769856e63fb22048ca9ce0c7282ed75905a5209b20619fb79b47d8ee030b", + "https://deno.land/x/monads@v0.5.10/either/either.ts": "89f539c7d50bd0ee8d9b902f37ef16687c19b62cc9dd23454029c97fbfc15cc6", + "https://deno.land/x/monads@v0.5.10/index.ts": "f0e90b8c1dd767efca137d682ac1a19b2dbae4d1990b8a79a40b4e054c69b3d6", + "https://deno.land/x/monads@v0.5.10/mod.ts": "f1b16a34d47e58fdf9f1f54c49d2fe6df67b3d2e077e21638f25fbe080eee6cf", + "https://deno.land/x/monads@v0.5.10/option/option.ts": "76ef03c3370207112759f932f39aab04999cdd1a5c5a954769b3868602faf883", + "https://deno.land/x/monads@v0.5.10/result/result.ts": "bb482b7b90949d3a67e78b4b0dd949774eccaa808df39ac83f6a585526edeb37", + "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", + "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", + "https://deno.land/x/zip@v1.2.5/compress.ts": "43d9f4440960d15a85aec58f5d365acc25530d3d4186b2f5f896c090ecac20e8", + "https://deno.land/x/zip@v1.2.5/decompress.ts": "0bce3d453726f686274fab3f6c19b72b5e74223a00d89c176b1de49a5dd5528d", + "https://deno.land/x/zip@v1.2.5/deps.ts": "79548387594b3ae1efaaa870b5a507c4d6bedede13dbd5d4ad42f6cda0aeef86", + "https://deno.land/x/zip@v1.2.5/mod.ts": "28eecbc3e1e5adf564f4aa465e64268713a05653104bacdcb04561533f8caf57", + "https://deno.land/x/zip@v1.2.5/utils.ts": "43c323f2b79f9db1976c5739bbb1f9cced20e8077ca7e7e703f9d01d4330bd9d", + "https://deno.land/x/zipjs@v2.7.31/index.js": "7c71926e0c9618e48a22d9dce701131704fd3148a1d2eefd5dba1d786c846a5f", + "https://deno.land/x/zipjs@v2.7.31/lib/core/codec-pool.js": "e5ab8ee3ec800ed751ef1c63a1bd8e50f162aa256a5f625d173d7a32e76e828c", + "https://deno.land/x/zipjs@v2.7.31/lib/core/codec-worker.js": "744b7e149df6f2d105afbcb9cce573df2fbf7bf1c2e14c3689220c2dedeabe65", + "https://deno.land/x/zipjs@v2.7.31/lib/core/configuration.js": "baa316a63df2f8239f9d52cd4863eaedaddd34ad887b7513588da75d19e84932", + "https://deno.land/x/zipjs@v2.7.31/lib/core/constants.js": "14fe1468b87cd0fe20c6f1fec916485f875d8592beba94c9241af4cbd12dd88f", + "https://deno.land/x/zipjs@v2.7.31/lib/core/io.js": "4c4e86ba187540be533003271f222183455897cd144cb542539e9480882c2dda", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/aes-crypto-stream.js": "8242f23a221c496996071b68d498e821ca6b8f20d04bdf74ee0a589ac3367cc5", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/codec-stream.js": "685f1120b94b6295dcd61b195d6202cd24a5344e4588dc52f42e8ac0f9dfe294", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/codecs/crc32.js": "dfdde666f72b4a5ffc8cf5b1451e0db578ce4bd90de20df2cff5bfd47758cb23", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/codecs/deflate.js": "08c1b24d1845528f6db296570d690ecbe23c6c01c6cb26b561e601e770281c3a", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/codecs/inflate.js": "55d00eed332cf2c4f61e2ee23133e3257768d0608572ee3f9641a2921c3a6f67", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/codecs/sjcl.js": "462289c5312f01bba8a757a7a0f3d8f349f471183cb4c49fb73d58bba18a5428", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/common-crypto.js": "4d462619848d94427fcd486fd94e5c0741af60e476df6720da8224b086eba47e", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/crc32-stream.js": "10e26bd18df0e1e89d61a62827a1a1c19f4e541636dd0eccbd85af3afabce289", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/stream-adapter.js": "9e7f3fe1601cc447943cd37b5adb6d74c6e9c404d002e707e8eace7bc048929c", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/zip-crypto-stream.js": "19305af1e8296e7fa6763f3391d0b8149a1e09c659e1d1ff32a484448b18243c", + "https://deno.land/x/zipjs@v2.7.31/lib/core/streams/zip-entry-stream.js": "01d4dc0843e8c43d32454cbb15e4d1f9b7122ab288d7650129d010df54bc0b8e", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/cp437-decode.js": "d665ded184037ffe5d255be8f379f90416053e3d0d84fac95b28f4aeaab3d336", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/decode-text.js": "c04a098fa7c16470c48b6abd4eb4ac48af53547de65e7c8f39b78ae62330ad57", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/default-mime-type.js": "177ae00e1956d3d00cdefc40eb158cb591d3d24ede452c056d30f98d73d9cd73", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/encode-text.js": "c51a8947c15b7fe31b0036b69fd68817f54b30ce29502b5c9609d8b15e3b20d9", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/mime-type.js": "6c6dfa4daf98ef59cd65118073b74f327ceab2ef28140e38934b0d15eb2b5c29", + "https://deno.land/x/zipjs@v2.7.31/lib/core/util/stream-codec-shim.js": "1323016ec3c743942dc887215832badc7f2c1e8dbb37b71c94bf54276d2b281a", + "https://deno.land/x/zipjs@v2.7.31/lib/core/zip-entry.js": "d30a535cd1e75ef98094cd04120f178c103cdc4055d23ff747ffc6a154da8d2d", + "https://deno.land/x/zipjs@v2.7.31/lib/core/zip-fs-core.js": "6fbd3ad9dbf0d07e19e1a59863743d2069f2b5fca691bdd6cd8d052ee7ce0c06", + "https://deno.land/x/zipjs@v2.7.31/lib/core/zip-reader.js": "4e63d88e4eb5f7419e2dc3ccb741014240267a49fb80a9cbcb024149990b532b", + "https://deno.land/x/zipjs@v2.7.31/lib/core/zip-writer.js": "b78c099828ec3134983c259adc4d6118fbfda7f033a7e95de8176a470e9a5a54", + "https://deno.land/x/zipjs@v2.7.31/lib/z-worker-inline.js": "9869579df96d7b75a10c70f231837c418b0cdd0ac26df12f17dade6bbaa6c17a", + "https://deno.land/x/zipjs@v2.7.31/lib/zip-fs.js": "a733360302f5fbec9cc01543cb9fcfe7bae3f35a50d0006626ce42fe8183b63f", + "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", + "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1", + "https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774", + "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", + "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", + "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", + "https://esm.sh/extract-files@12.0.0/extractFiles.mjs": "58e5f1fcf50e3a362d9bde0e2c1a624a7d89d60ac2de005afd895d0d452cca62", + "https://esm.sh/extract-files@12.0.0/isExtractableFile.mjs": "2757358bbc9141b93cb27d7e0ba98c1e703172dd58fb7d533a31476419d2416f", + "https://esm.sh/v135/extract-files@12.0.0/denonext/extractFiles.js": "59b30d503c5d9ce135dd76c34ac92cf7e9f85cd129d6be2ff61553bb612ac75f", + "https://esm.sh/v135/extract-files@12.0.0/denonext/isExtractableFile.js": "93462c162e29fc0e8fd7e9de3665dfd7705f03d41d2721d654f177bc2a7fe305", + "https://esm.sh/v135/is-plain-obj@4.1.0/denonext/is-plain-obj.mjs": "d3d86a7174ad7935de7b00f904b6424c103bce530c502efb7f42114cbb1a555f" + } +} diff --git a/deps/cli.ts b/deps/cli.ts new file mode 100644 index 00000000..585f3daa --- /dev/null +++ b/deps/cli.ts @@ -0,0 +1,10 @@ +//! dependencies used by the cli + +export * from "./common.ts"; + +export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; +export { + Command, + type CommandResult, + CompletionsCommand, +} from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; diff --git a/deps/common.ts b/deps/common.ts new file mode 100644 index 00000000..4f438125 --- /dev/null +++ b/deps/common.ts @@ -0,0 +1,10 @@ +//! dependencies used by all + +export { z as zod } from "https://deno.land/x/zod@v3.22.4/mod.ts"; +export * as semver from "https://deno.land/std@0.205.0/semver/mod.ts"; +export * as log from "https://deno.land/std@0.205.0/log/mod.ts"; +export * as std_fmt_colors from "https://deno.land/std@0.205.0/fmt/colors.ts"; +export * as std_url from "https://deno.land/std@0.205.0/url/mod.ts"; +export * as std_path from "https://deno.land/std@0.205.0/path/mod.ts"; +export * as std_fs from "https://deno.land/std@0.205.0/fs/mod.ts"; +export * as dax from "https://deno.land/x/dax@0.35.0/mod.ts"; diff --git a/deps/dev.ts b/deps/dev.ts new file mode 100644 index 00000000..59af4329 --- /dev/null +++ b/deps/dev.ts @@ -0,0 +1,4 @@ +//! dependencies used by tests + +export * from "./common.ts"; +export * as std_assert from "https://deno.land/std@0.205.0/assert/mod.ts"; diff --git a/deps/ports.ts b/deps/ports.ts new file mode 100644 index 00000000..33d62761 --- /dev/null +++ b/deps/ports.ts @@ -0,0 +1,8 @@ +//! This contains dependencies used by plugins + +export * from "./common.ts"; +export * as zipjs from "https://deno.land/x/zipjs@v2.7.31/index.js"; +export { Foras } from "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts"; +export * as std_tar from "https://deno.land/std@0.129.0/archive/tar.ts"; +export * as std_streams from "https://deno.land/std@0.129.0/streams/mod.ts"; +export * as std_io from "https://deno.land/std@0.129.0/io/mod.ts"; diff --git a/examples/protoc/ghjk.ts b/examples/protoc/ghjk.ts new file mode 100644 index 00000000..6007c04f --- /dev/null +++ b/examples/protoc/ghjk.ts @@ -0,0 +1,4 @@ +export { ghjk } from "../../mod.ts"; +import protoc from "../../ports/protoc.ts"; + +protoc({}); diff --git a/ghjk.ts b/ghjk.ts index 344495e9..21d41439 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,18 +1,35 @@ -/* -import { run, rust } from "./src/ghjk.ts"; - -rust({ - version: "1.55.0", -}); - -rust({ - version: "nightly", - name: "nrust", -}); - -await run(); -*/ - export { ghjk } from "./mod.ts"; +import node from "./ports/node.ts"; +import pnpm from "./ports/pnpm.ts"; +import cargo_binstall from "./ports/cargo-binstall.ts"; +import wasmedge from "./ports/wasmedge.ts"; +import wasm_tools from "./ports/wasm-tools.ts"; +import wasm_opt from "./ports/wasm-opt.ts"; +import cargo_insta from "./ports/cargo-insta.ts"; +import jco from "./ports/jco.ts"; +import mold from "./ports/mold.ts"; +import act from "./ports/act.ts"; +import asdf from "./ports/asdf.ts"; +import protoc from "./ports/protoc.ts"; +import earthly from "./ports/earthly.ts"; +import ruff from "./ports/ruff.ts"; +import whiz from "./ports/whiz.ts"; -console.log("config"); +// node({}); +// wasmedge({}); +// pnpm({}); +// cargo_binstall({}); +// wasm_tools({}); +// wasm_opt({}); +// cargo_insta({}); +// jco({}); +// mold({}); +// act({}); +// asdf({ +// pluginRepo: "https://github.com/asdf-community/asdf-cmake", +// installType: "version", +// }); +// protoc({ }); +// earthly({}); +// ruff({}); +// whiz({}); diff --git a/host/deno.ts b/host/deno.ts new file mode 100644 index 00000000..56dd4267 --- /dev/null +++ b/host/deno.ts @@ -0,0 +1,114 @@ +//! this loads the ghjk.ts module and provides a program for it + +//// +/// + +import { std_url } from "../deps/common.ts"; + +import { inWorker } from "../utils/mod.ts"; +import logger, { setup as setupLogger } from "../utils/logger.ts"; +import type { PortsModuleConfigBase } from "../modules/ports/mod.ts"; + +if (inWorker()) { + initWorker(); +} + +declare global { + interface Window { + ports: PortsModuleConfigBase; + } +} + +function initWorker() { + setupLogger(); + self.ports = { + ports: {}, + installs: [], + }; + + self.onmessage = onMsg; +} + +export type DriverRequests = { + ty: "serialize"; + uri: string; +}; +export type DriverResponse = { + ty: "serialize"; + payload: unknown; +}; +async function onMsg(msg: MessageEvent) { + const req = msg.data; + if (!req.ty) { + logger().error(`invalid msg data`, req); + throw new Error(`unrecognized event data`); + } + let res: DriverResponse; + if (req.ty == "serialize") { + res = { + ty: req.ty, + payload: await serializeConfig(req.uri), + }; + } else { + logger().error(`invalid DriverRequest type`, req); + throw new Error(`unrecognized request type: ${req.ty}`); + } + self.postMessage(res); +} + +async function serializeConfig(uri: string) { + const mod = await import(uri); + const config = mod.ghjk.getConfig(mod.secureConfig); + return JSON.parse(JSON.stringify(config)); +} + +export async function getSerializedConfig(configUri: string) { + const resp = await rpc(configUri, { + ty: "serialize", + uri: configUri, + }); + if (resp.ty != "serialize") { + throw new Error(`invalid response type: ${resp.ty}`); + } + return resp.payload; +} + +async function rpc(moduleUri: string, req: DriverRequests) { + const baseName = std_url.basename(moduleUri); + const dirBaseName = std_url.basename(std_url.dirname(moduleUri)); + const worker = new Worker(import.meta.url, { + name: `${dirBaseName}/${baseName}`, + type: "module", + // TODO: proper permissioning + // deno: { + // namespace: true, + // permissions: { + // sys: true, + // net: true, + // read: ["."], + // hrtime: false, + // write: false, + // run: false, + // ffi: false, + // env: true, + // } as Deno.PermissionOptions, + // }, + } as WorkerOptions); + + const promise = new Promise((resolve, reject) => { + worker.onmessage = (evt: MessageEvent) => { + const res = evt.data; + resolve(res); + }; + worker.onmessageerror = (evt) => { + reject(evt.data); + }; + worker.onerror = (err) => { + reject(err); + }; + }); + worker.postMessage(req); + const resp = await promise; + worker.terminate(); + return resp; +} diff --git a/host/mod.ts b/host/mod.ts new file mode 100644 index 00000000..0b368068 --- /dev/null +++ b/host/mod.ts @@ -0,0 +1,80 @@ +import "../setup_logger.ts"; + +import { std_path } from "../deps/common.ts"; +import { cliffy_cmd } from "../deps/cli.ts"; +import logger from "../utils/logger.ts"; +// import { $ } from "../utils/mod.ts"; + +import { envDirFromConfig, findConfig, isColorfulTty } from "../utils/mod.ts"; +import validators from "./types.ts"; +import * as std_modules from "../modules/std.ts"; +import * as deno from "./deno.ts"; + +export async function main() { + const configPathIn = Deno.env.get("GHJK_CONFIG") ?? + await findConfig(Deno.cwd()); + if (!configPathIn) { + logger().error("ghjk did not find any `ghjk.ts` config."); + Deno.exit(2); + } + const configPath = std_path.resolve(Deno.cwd(), configPathIn); + const envDir = envDirFromConfig(configPath); + + logger().debug({ configPath }); + logger().debug({ envDir }); + + let serializedJson; + switch (std_path.extname(configPath)) { + case "": + logger().warning("config file has no extension, assuming deno config"); + /* falls through */ + case ".ts": + serializedJson = await deno.getSerializedConfig( + std_path.toFileUrl(configPath).href, + ); + break; + // case ".jsonc": + case ".json": + serializedJson = JSON.parse(await Deno.readTextFile(configPath)); + break; + default: + throw new Error( + `unrecognized ghjk config type provided at path: ${configPath}`, + ); + } + const serializedConfig = validators.serializedConfig.parse(serializedJson); + + const ctx = { configPath, envDir }; + let cmd: cliffy_cmd.Command = new cliffy_cmd.Command() + .name("ghjk") + .version("0.1.0") // FIXME: better way to resolve version + .description("Programmable runtime manager.") + .action(function () { + this.showHelp(); + }) + .command( + "config", + new cliffy_cmd.Command() + .description("Print the extracted config from the ghjk.ts file") + .action(function () { + console.log(Deno.inspect(serializedConfig, { + depth: 10, + colors: isColorfulTty(), + })); + }), + ); + for (const man of serializedConfig.modules) { + const mod = std_modules.map[man.id]; + if (!mod) { + throw new Error(`unrecognized module specified by ghjk.ts: ${man.id}`); + } + const instance = mod.ctor(ctx, man); + cmd = cmd.command(man.id, instance.command()); + } + cmd + .command("completions", new cliffy_cmd.CompletionsCommand()) + .parse(Deno.args); + // const serializedConfig = validators.serializedConfig.parse( + // serializedJson, + // ); +} diff --git a/host/types.ts b/host/types.ts new file mode 100644 index 00000000..73aeb410 --- /dev/null +++ b/host/types.ts @@ -0,0 +1,14 @@ +import { zod } from "../deps/common.ts"; +import moduleValidators from "../modules/types.ts"; + +const serializedConfig = zod.object( + { + modules: zod.array(moduleValidators.moduleManifest), + }, +); + +export type SerializedConfig = zod.infer; + +export default { + serializedConfig, +}; diff --git a/install.ts b/install.ts index 95c4aca1..e23db5f9 100644 --- a/install.ts +++ b/install.ts @@ -1,5 +1,11 @@ -// this is only a shortcut for the first install +//! Setup ghjk for the CWD -import { install } from "./cli/core/hooks.ts"; +import { install } from "./install/mod.ts"; -await install(); +if (import.meta.main) { + await install(); +} else { + throw new Error( + "unexpected ctx: if you want to access the ghjk installer, import `install` from ./install/mod.ts", + ); +} diff --git a/install/hooks/bash.sh b/install/hooks/bash.sh new file mode 100644 index 00000000..ff44a681 --- /dev/null +++ b/install/hooks/bash.sh @@ -0,0 +1,67 @@ +# please keep this posix compatible and avoid bash extensions when possible +# the zsh impl also relies on this +# https;//shellcheck.net can come in handy in this + +# Define color variables +ansi_red='\033[0;31m' +# GREEN='\033[0;32m' +ansi_yel='\033[0;33m' +# BLUE='\033[0;34m' +ansi_nc='\033[0m' # No Color + +init_ghjk() { + if [ -n "${GHJK_CLEANUP+x}" ]; then + eval "$GHJK_CLEANUP" + fi + unset GHJK_CLEANUP + unset GHJK_LAST_LOADER_PATH + unset GHJK_LAST_LOADER_TS + cur_dir=$PWD + while [ "$cur_dir" != "/" ]; do + if [ -e "$cur_dir/ghjk.ts" ]; then + envDir="$HOME/.local/share/ghjk/envs/$(printf "$cur_dir" | tr '/' '.')" + if [ -d "$envDir" ]; then + export GHJK_LAST_LOADER_PATH="$envDir/loader.sh" + export GHJK_LAST_LOADER_TS=$(stat -c "%Y" "$GHJK_LAST_LOADER_PATH" | tr -d '\n') + . "$envDir/loader.sh" + # FIXME: -ot not valid in POSIX + # shellcheck disable=SC3000-SC4000 + if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then + printf "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}\n" + fi + else + printf "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}\n" + printf "$envDir\n" + fi + return + fi + cur_dir="$(dirname "$cur_dir")" + done +} + +# onlt load bash-prexec if we detect bash +# bash-preexec itslef only executes if it detects bash +# but even reliably resolving it's address +# requires bash extensions. +if [ -n "${BASH_SOURCE+x}" ]; then + myDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") + . "$myDir/bash-preexec.sh" +fi + +export LAST_PWD="$PWD" +# use precmd to check for ghjk.ts before every prompt draw +# precmd is avail natively for zsh +precmd() { + if [ "$LAST_PWD" != "$PWD" ] || ( + # if the last detected loader has been touched + [ -n "${GHJK_LAST_LOADER_PATH+x}" ] && [ $(stat -c "%Y" "$GHJK_LAST_LOADER_PATH" | tr -d '\n') != $(("$GHJK_LAST_LOADER_TS")) ] + ); then + echo "got here" + init_ghjk + export LAST_PWD="$PWD" + fi + +} + +# try loading any relevant ghjk.ts right away +init_ghjk diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish new file mode 100644 index 00000000..a12e3b9a --- /dev/null +++ b/install/hooks/fish.fish @@ -0,0 +1,46 @@ +function init_ghjk + if set --query GHJK_CLEANUP + eval $GHJK_CLEANUP + end + set --erase GHJK_CLEANUP + set --erase GHJK_LAST_LOADER_PATH + set --erase GHJK_LAST_LOADER_TS + set --local cur_dir $PWD + while test $cur_dir != "/" + if test -e $cur_dir/ghjk.ts + set --local envDir $HOME/.local/share/ghjk/envs/(string replace --all / . $cur_dir) + if test -d $envDir + set -g -x GHJK_LAST_LOADER_PATH $envDir/loader.fish + set -g -x GHJK_LAST_LOADER_TS (stat -c "%Y" $envDir/loader.fish | tr -d '\n') + source $envDir/loader.fish + if test $envDir/loader.fish -ot $cur_dir/ghjk.ts + set_color FF4500 + echo "[ghjk] Detected changes, please sync..." + set_color normal + end + else + set_color FF4500 + echo "[ghjk] Uninstalled runtime found, please sync..." + echo $envDir + set_color normal + end + return + end + set cur_dir (dirname $cur_dir) + end +end + +function ghjk_prompt_hook --on-event fish_prompt + # only init if the loader has been modified + if set --query GHJK_LAST_LOADER_PATH; and test (stat -c "%Y" $GHJK_LAST_LOADER_PATH | tr -d '\n') != $GHJK_LAST_LOADER_TS + init_ghjk + end +end + +# try to detect ghjk.ts on each change of PWD +function ghjk_pwd_hook --on-variable PWD + init_ghjk +end + +# try loading any relevant ghjk.ts right away +init_ghjk diff --git a/install/hooks/zsh.zsh b/install/hooks/zsh.zsh new file mode 100644 index 00000000..a235648a --- /dev/null +++ b/install/hooks/zsh.zsh @@ -0,0 +1,3 @@ +if [ -e ~/.zshenv ]; then . ~/.zshenv; fi +myDir=$(dirname -- "$(readlink -f -- "${(%):-%x}")") +. $myDir/env.sh diff --git a/install/mod.ts b/install/mod.ts new file mode 100644 index 00000000..5833acdb --- /dev/null +++ b/install/mod.ts @@ -0,0 +1,151 @@ +//! this installs the different shell ghjk hooks in ~/.local/share/ghjk +//! and a `ghjk` bin at ~/.local/share/bin + +// TODO: support for different environments to use different versions of ghjk + +import "../setup_logger.ts"; +import logger from "../utils/logger.ts"; +import { std_fs, std_path } from "../deps/cli.ts"; +import { dirs, importRaw } from "../utils/mod.ts"; +import { spawnOutput } from "../utils/mod.ts"; + +// null means it should be removed (for cleaning up old versions) +const getHooksVfs = async () => ({ + "bash-preexec.sh": await importRaw( + "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", + ), + + ".zshenv": ( + await importRaw(import.meta.resolve("./hooks/zsh.zsh")) + ), + + // the hook run before every prompt draw in bash + "env.sh": ( + await importRaw(import.meta.resolve("./hooks/bash.sh")) + ), + + "env.fish": ( + await importRaw(import.meta.resolve("./hooks/fish.fish")) + ), +}); + +async function detectShell(): Promise { + let path = Deno.env.get("SHELL"); + if (!path) { + try { + path = await spawnOutput([ + "ps", + "-p", + String(Deno.ppid), + "-o", + "comm=", + ]); + } catch (err) { + throw new Error(`cannot get parent process name: ${err}`); + } + } + return std_path.basename(path, ".exe").toLowerCase().trim(); +} +async function unpackVFS(baseDir: string): Promise { + await Deno.mkdir(baseDir, { recursive: true }); + + const hookVfs = await getHooksVfs(); + for (const [subpath, content] of Object.entries(hookVfs)) { + const path = std_path.resolve(baseDir, subpath); + if (content === null) { + await Deno.remove(path); + } else { + await Deno.mkdir(std_path.dirname(path), { recursive: true }); + await Deno.writeTextFile(path, content.trim()); + } + } +} + +async function filterAddFile( + path: string, + marker: RegExp, + content: string | null, +) { + const file = await Deno.readTextFile(path).catch(async (err) => { + if (err instanceof Deno.errors.NotFound) { + await Deno.mkdir(std_path.dirname(path), { recursive: true }); + return ""; + } + throw err; + }); + const lines = file.split("\n"); + + let i = 0; + while (i < lines.length) { + if (marker.test(lines[i])) { + lines.splice(i, 1); + } else { + i += 1; + } + } + + if (content !== null) { + lines.push(content); + } + + await Deno.writeTextFile(path, lines.join("\n")); +} + +export async function install() { + if (Deno.build.os == "windows") { + throw new Error("windows is not yet supported :/"); + } + const { homeDir, shareDir } = dirs(); + logger().debug("installing hooks", { shareDir }); + await unpackVFS(shareDir); + const shell = await detectShell(); + if (shell === "fish") { + await filterAddFile( + std_path.resolve(homeDir, ".config/fish/config.fish"), + /\.local\/share\/ghjk\/env/, + ". $HOME/.local/share/ghjk/env.fish", + ); + } else if (shell === "bash") { + await filterAddFile( + std_path.resolve(homeDir, ".bashrc"), + /\.local\/share\/ghjk\/env/, + ". $HOME/.local/share/ghjk/env.sh", + ); + } else if (shell === "zsh") { + await filterAddFile( + std_path.resolve(homeDir, ".zshrc"), + /\.local\/share\/ghjk\/env/, + // NOTE: we use the posix for zsh + ". $HOME/.local/share/ghjk/env.sh", + ); + } else { + throw new Error(`unsupported shell: ${shell}`); + } + const skipBinInstall = Deno.env.get("GHJK_SKIP_EXE_INSTALL"); + if (!skipBinInstall && skipBinInstall != "0" && skipBinInstall != "false") { + switch (Deno.build.os) { + case "linux": + case "freebsd": + case "solaris": + case "illumos": + case "darwin": { + // TODO: respect xdg dirs + const exeDir = Deno.env.get("GHJK_EXE_INSTALL_DIR") ?? + std_path.resolve(homeDir, ".local", "bin"); + await std_fs.ensureDir(exeDir); + const exePath = std_path.resolve(exeDir, `ghjk`); + logger().debug("installing executable", { exePath }); + await Deno.writeTextFile( + exePath, + `#!/bin/sh +deno run --unstable-worker-options -A ${import.meta.resolve("../main.ts")} $*`, + { mode: 0o700 }, + ); + break; + } + default: + throw new Error(`${Deno.build.os} is not yet supported`); + } + } + logger().info("install success"); +} diff --git a/main.ts b/main.ts new file mode 100755 index 00000000..3433dc66 --- /dev/null +++ b/main.ts @@ -0,0 +1,11 @@ +#! /usr/bin/env -S deno run --unstable-worker-options -A + +import { main } from "./host/mod.ts"; + +if (import.meta.main) { + await main(); +} else { + throw new Error( + "unexpected ctx: if you want to run the ghjk cli, import `main` from ./host/mod.ts", + ); +} diff --git a/mod.ts b/mod.ts index 7559c433..3c2f2857 100644 --- a/mod.ts +++ b/mod.ts @@ -1,2 +1,60 @@ -// this is only a shortcut for the cli -export * from "./cli/mod.ts"; +//! This module is intended to be re-exported by `ghjk.ts` config scripts. Please +//! avoid importing elsewhere at it has side-effects. + +import "./setup_logger.ts"; + +import { + type PortsModuleConfigBase, + type PortsModuleSecureConfig, + type RegisteredPorts, +} from "./modules/ports/types.ts"; +import type { SerializedConfig } from "./host/types.ts"; +import logger from "./utils/logger.ts"; +import * as std_ports from "./modules/ports/std.ts"; +import { std_modules } from "./modules/mod.ts"; + +// we need to use global variables to allow +// pots to access the config object. +// accessing it through ecma module imports wouldn't work +// as ports might import a different version of this module. +declare global { + interface Window { + ports: PortsModuleConfigBase; + } +} + +function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { + let allowedDeps; + if (secureConfig?.allowedPortDeps) { + allowedDeps = {} as RegisteredPorts; + for (const depId of secureConfig.allowedPortDeps) { + const regPort = std_ports.map[depId.id]; + if (!regPort) { + throw new Error( + `unrecognized dep "${depId.id}" found in "allowedPluginDeps"`, + ); + } + allowedDeps[depId.id] = regPort; + } + } else { + allowedDeps = std_ports.map; + } + const config: SerializedConfig = { + modules: [{ + id: std_modules.ports, + config: { + installs: self.ports.installs, + ports: self.ports.ports, + allowedDeps: allowedDeps, + }, + }], + }; + return config; +} + +// freeze the object to prevent malicious tampering of the secureConfig +export const ghjk = Object.freeze({ + getConfig: Object.freeze(getConfig), +}); + +export { logger }; diff --git a/modules/mod.ts b/modules/mod.ts new file mode 100644 index 00000000..849201d9 --- /dev/null +++ b/modules/mod.ts @@ -0,0 +1,6 @@ +export * as std_modules from "./std.ts"; +import { cliffy_cmd } from "../deps/cli.ts"; + +export abstract class ModuleBase { + abstract command(): cliffy_cmd.Command; +} diff --git a/modules/ports/ambient.ts b/modules/ports/ambient.ts new file mode 100644 index 00000000..89ae033a --- /dev/null +++ b/modules/ports/ambient.ts @@ -0,0 +1,85 @@ +import { + type AmbientAccessPortManifest, + type DownloadArgs, + type InstallArgs, + type ListAllArgs, + type ListBinPathsArgs, + PortBase, +} from "./types.ts"; +import { ChildError, spawnOutput } from "../../utils/mod.ts"; + +export class AmbientAccessPort extends PortBase { + constructor(public manifest: AmbientAccessPortManifest) { + super(); + if (manifest.deps && manifest.deps.length > 0) { + throw new Error( + `ambient access plugin has deps ${JSON.stringify(manifest)}`, + ); + } + } + async latestStable(_env: ListAllArgs): Promise { + const execPath = await this.pathToExec(); + let versionOut; + try { + versionOut = await spawnOutput([ + execPath, + this.manifest.versionExtractFlag, + ]); + } catch (err) { + if (err instanceof ChildError) { + new Error( + `error trying to get version output for "${this.manifest.name}@${this.manifest.version}" using command ${execPath} ${this.manifest.versionExtractFlag}: ${err}`, + { + cause: err, + }, + ); + } + throw err; + } + const extractionRegex = new RegExp( + this.manifest.versionExtractRegex, + this.manifest.versionExtractRegexFlags, + ); + const matches = versionOut.match(extractionRegex); + if (!matches) { + throw new Error( + `error trying extract version for "${this.manifest.name}@${this.manifest.version}" using regex ${extractionRegex} from output: ${versionOut}`, + ); + } + + return matches[0]; + } + + async listAll(env: ListAllArgs): Promise { + return [await this.latestStable(env)]; + } + + async listBinPaths( + _env: ListBinPathsArgs, + ): Promise { + return [await this.pathToExec()]; + } + + download(_env: DownloadArgs): void | Promise { + // no op + } + install(_env: InstallArgs): void | Promise { + // no op + } + async pathToExec(): Promise { + try { + const output = await spawnOutput(["which", this.manifest.execName]); + return output.trim(); + } catch (err) { + if (err instanceof ChildError) { + new Error( + `error trying to get exec path for "${this.manifest.name}@${this.manifest.version}" for exec name ${this.manifest.execName}: ${err}`, + { + cause: err, + }, + ); + } + throw err; + } + } +} diff --git a/modules/ports/asdf.ts b/modules/ports/asdf.ts new file mode 100644 index 00000000..b88637be --- /dev/null +++ b/modules/ports/asdf.ts @@ -0,0 +1,159 @@ +import { + type AsdfInstallConfigX, + type DepShims, + type DownloadArgs, + type InstallArgs, + type ListAllArgs, + type ListBinPathsArgs, + PortBase, + type TheAsdfPortManifest, +} from "./types.ts"; +import { + depBinShimPath, + getInstallId, + pathWithDepShims, + spawn, + spawnOutput, +} from "../../utils/mod.ts"; +// import * as std_ports from "../std.ts"; +import { std_fs, std_path } from "../../deps/common.ts"; + +// FIXME: find a better way to expose std_plug.plug_id s +// that allows standard plugs to depend on each other +const curl_aa_id = { + id: "curl@aa", +}; + +const git_aa_id = { + id: "git@aa", +}; + +export const manifest: TheAsdfPortManifest = { + ty: "asdf", + name: "asdf@asdf", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [curl_aa_id, git_aa_id], + // there should only be a single asdf port registered at any time + conflictResolution: "override", +}; + +export class AsdfPort extends PortBase { + manifest = manifest; + constructor( + public asdfDir: string, + public pluginDir: string, + public config: AsdfInstallConfigX, + ) { + super(); + } + static async init( + envDir: string, + installConfig: AsdfInstallConfigX, + depShims: DepShims, + ) { + const asdfDir = std_path.resolve(envDir, "asdf"); + const installId = getInstallId(installConfig); + + const pluginDir = std_path.resolve(asdfDir, installId); + if (!await std_fs.exists(pluginDir)) { + const tmpCloneDirPath = await Deno.makeTempDir({ + prefix: `ghjk_asdf_clone_${installId}_`, + }); + await spawn( + [ + depBinShimPath(git_aa_id, "git", depShims), + "clone", + installConfig.pluginRepo, + "--depth", + "1", + tmpCloneDirPath, + ], + ); + await std_fs.copy( + tmpCloneDirPath, + pluginDir, + ); + void Deno.remove(tmpCloneDirPath, { recursive: true }); + } + return new AsdfPort(asdfDir, pluginDir, installConfig); + } + + async listAll(_args: ListAllArgs): Promise { + const out = await spawnOutput([ + std_path.resolve(this.pluginDir, "bin", "list-all"), + ]); + return out.split(" ").filter((str) => str.length > 0).map((str) => + str.trim() + ); + } + + async latestStable(args: ListAllArgs): Promise { + const binPath = std_path.resolve(this.pluginDir, "bin", "latest-stable"); + if (!await std_fs.exists(binPath)) { + return super.latestStable(args); + } + const out = await spawnOutput([binPath], { + env: { + PATH: pathWithDepShims(args.depShims), + ASDF_INSTALL_TYPE: this.config.installType, + // FIXME: asdf requires these vars for latest-stable. this makes no sense! + ASDF_INSTALL_VERSION: this.config.version ?? "", + // ASDF_INSTALL_PATH: args.installPath, + }, + }); + return out.trim(); + } + + async listBinPaths(args: ListBinPathsArgs): Promise { + const binPath = std_path.resolve(this.pluginDir, "bin", "list-bin-paths"); + if (!await std_fs.exists(binPath)) { + return super.listBinPaths(args); + } + + const out = await spawnOutput([binPath], { + env: { + PATH: pathWithDepShims(args.depShims), + ASDF_INSTALL_TYPE: this.config.installType, + ASDF_INSTALL_VERSION: args.installVersion, + ASDF_INSTALL_PATH: args.installPath, + }, + }); + return out.split(" ").filter((str) => str.length > 0).map((str) => + str.trim() + ); + } + + async download(args: DownloadArgs) { + const binPath = std_path.resolve(this.pluginDir, "bin", "download"); + // some plugins don't have a download script despite the spec + if (!await std_fs.exists(binPath)) { + return; + } + await spawn([ + std_path.resolve(this.pluginDir, "bin", "download"), + ], { + env: { + PATH: pathWithDepShims(args.depShims), + ASDF_INSTALL_TYPE: this.config.installType, + ASDF_INSTALL_VERSION: args.installVersion, + ASDF_INSTALL_PATH: args.installPath, + ASDF_DOWNLOAD_PATH: args.downloadPath, + }, + }); + } + async install(args: InstallArgs) { + await spawn([ + std_path.resolve(this.pluginDir, "bin", "install"), + ], { + env: { + PATH: pathWithDepShims(args.depShims), + ASDF_INSTALL_TYPE: this.config.installType, + ASDF_INSTALL_VERSION: args.installVersion, + ASDF_INSTALL_PATH: args.installPath, + ASDF_DOWNLOAD_PATH: args.downloadPath, + ASDF_CONCURRENCY: args.availConcurrency.toString(), + }, + }); + } +} diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts new file mode 100644 index 00000000..6b2186c8 --- /dev/null +++ b/modules/ports/mod.ts @@ -0,0 +1,144 @@ +export * from "./types.ts"; + +import { cliffy_cmd } from "../../deps/cli.ts"; +import { semver } from "../../deps/common.ts"; + +import validators, { + type AmbientAccessPortManifest, + type DenoWorkerPortManifest, + type InstallConfig, + type PortManifest, + type PortsModuleConfig, + type PortsModuleConfigBase, +} from "./types.ts"; +import { type GhjkCtx } from "../types.ts"; +import logger from "../../utils/logger.ts"; +import { ModuleBase } from "../mod.ts"; +import { sync } from "./sync.ts"; + +export class PortsModule extends ModuleBase { + constructor( + public ctx: GhjkCtx, + public config: PortsModuleConfig, + ) { + super(); + } + command() { + return new cliffy_cmd.Command() + // .alias("port") + .action(function () { + this.showHelp(); + }) + .description("Ports module, install programs into your env.") + .command( + "sync", + new cliffy_cmd.Command().description("Syncs the environment.") + .action(() => sync(this.ctx.envDir, this.config)), + ) + .command( + "list", + new cliffy_cmd.Command().description("") + .action(() => { + console.log( + this.config.installs.map((install) => ({ + install, + port: this.config.ports[install.portName], + })), + ); + }), + ) + .command("outdated", new cliffy_cmd.Command()) + .command("cleanup", new cliffy_cmd.Command()) + .command("completions", new cliffy_cmd.CompletionsCommand()); + } +} + +export function registerDenoPort( + cx: PortsModuleConfigBase, + manifest: DenoWorkerPortManifest, +) { + registerPort(cx, manifest); +} + +export function registerAmbientPort( + cx: PortsModuleConfigBase, + manifest: AmbientAccessPortManifest, +) { + registerPort(cx, manifest); +} + +export function registerPort( + cx: PortsModuleConfigBase, + manifestUnclean: PortManifest, +) { + const manifest = validators.portManifest.parse(manifestUnclean); + const conflict = cx.ports[manifest.name]; + if (conflict) { + if ( + conflict.conflictResolution == "override" && + manifest.conflictResolution == "override" + ) { + throw new Error( + `Two instances of port "${manifest.name}" found with ` + + `both set to "${manifest.conflictResolution}" conflictResolution"`, + ); + } else if (conflict.conflictResolution == "override") { + logger().debug("port rejected due to override", { + retained: conflict, + rejected: manifest, + }); + // do nothing + } else if (manifest.conflictResolution == "override") { + logger().debug("port override", { + new: manifest, + replaced: conflict, + }); + cx.ports[manifest.name] = manifest; + } else if ( + semver.compare( + semver.parse(manifest.version), + semver.parse(conflict.version), + ) == 0 + ) { + throw new Error( + `Two instances of the port "${manifest.name}" found with an identical version` + + `and both set to "deferToNewer" conflictResolution.`, + ); + } else if ( + semver.compare( + semver.parse(manifest.version), + semver.parse(conflict.version), + ) > 0 + ) { + logger().debug("port replaced after version defer", { + new: manifest, + replaced: conflict, + }); + cx.ports[manifest.name] = manifest; + } else { + logger().debug("port rejected due after defer", { + retained: conflict, + rejected: manifest, + }); + } + } else { + logger().debug("port registered", manifest.name); + cx.ports[manifest.name] = manifest; + } +} + +export function addInstall( + cx: PortsModuleConfigBase, + configUnclean: InstallConfig, +) { + const config = validators.installConfig.parse(configUnclean); + if (!cx.ports[config.portName]) { + throw new Error( + `unrecognized port "${config.portName}" specified by install ${ + JSON.stringify(config) + }`, + ); + } + logger().debug("install added", config); + cx.installs.push(config); +} diff --git a/modules/ports/std.ts b/modules/ports/std.ts new file mode 100644 index 00000000..ba211466 --- /dev/null +++ b/modules/ports/std.ts @@ -0,0 +1,62 @@ +//! This plugin exports the list of standard ports other +//! plugins are allowed to depend on. +import validators, { type PortDep, type PortManifest } from "./types.ts"; +import { manifest as man_tar_aa } from "../../ports/tar.ts"; +import { manifest as man_git_aa } from "../../ports/git.ts"; +import { manifest as man_curl_aa } from "../../ports/curl.ts"; +import { manifest as man_unzip_aa } from "../../ports/unzip.ts"; +import { manifest as man_cbin_ghrel } from "../../ports/cargo-binstall.ts"; +import { manifest as man_node_org } from "../../ports/node.ts"; +import { manifest as man_pnpm_ghrel } from "../../ports/pnpm.ts"; + +const aaPorts: PortManifest[] = [ + man_tar_aa, + man_git_aa, + man_curl_aa, + man_unzip_aa, +]; + +const denoPorts: PortManifest[] = [ + man_cbin_ghrel, + man_node_org, + man_pnpm_ghrel, +]; + +export const map = Object.freeze( + Object.fromEntries( + [ + ...aaPorts, + ...denoPorts, + ] + .map((man) => validators.portManifest.parse(man)) + .map((man) => [man.name, man]), + ), +); + +export const tar_aa = Object.freeze({ + id: man_tar_aa.name, +} as PortDep); + +export const git_aa = Object.freeze({ + id: man_git_aa.name, +} as PortDep); + +export const curl_aa = Object.freeze({ + id: man_curl_aa.name, +} as PortDep); + +export const unzip_aa = Object.freeze({ + id: man_unzip_aa.name, +} as PortDep); + +export const cbin_ghrel = Object.freeze({ + id: man_cbin_ghrel.name, +} as PortDep); + +export const node_org = Object.freeze({ + id: man_node_org.name, +} as PortDep); + +export const pnpm_ghrel = Object.freeze({ + id: man_pnpm_ghrel.name, +} as PortDep); diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts new file mode 100644 index 00000000..2628e055 --- /dev/null +++ b/modules/ports/sync.ts @@ -0,0 +1,439 @@ +import { std_fs, std_path, zod } from "../../deps/cli.ts"; +import logger from "../../utils/logger.ts"; +import validators, { + type AmbientAccessPortManifestX, + type DenoWorkerPortManifestX, + type DepShims, + type InstallConfig, + InstallConfigX, + type PortArgsBase, + type PortManifestX, + type PortsModuleConfig, +} from "./types.ts"; +import { DenoWorkerPort } from "./worker.ts"; +import { AmbientAccessPort } from "./ambient.ts"; +import { AsdfPort } from "./asdf.ts"; +import { AVAIL_CONCURRENCY, getInstallId } from "../../utils/mod.ts"; + +async function writeLoader( + envDir: string, + env: Record, + pathVars: Record, +) { + const loader = { + posix: [ + `export GHJK_CLEANUP_POSIX="";`, + ...Object.entries(env).map(([k, v]) => + // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution + `GHJK_CLEANUP_POSIX+="export ${k}='$${k}';"; +export ${k}='${v}';` + ), + ...Object.entries(pathVars).map(([k, v]) => + // NOTE: double quote the path vars for expansion + // single quote GHJK_CLEANUP additions to avoid expansion/exec before eval + `GHJK_CLEANUP_POSIX+='${k}=$(echo "$${k}" | tr ":" "\\n" | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr "\\n" ":");${k}="\${${k}%:}"'; +${k}="${v}:$${k}"; +` + ), + ].join("\n"), + fish: [ + `set --erase GHJK_CLEANUP_FISH`, + ...Object.entries(env).map(([k, v]) => + `set --global --append GHJK_CLEANUP_FISH "set --global --export ${k} '$${k}';"; +set --global --export ${k} '${v}';` + ), + ...Object.entries(pathVars).map(([k, v]) => + `set --global --append GHJK_CLEANUP_FISH 'set --global --path ${k} (string match --invert --regex "^$HOME\\/\\.local\\/share\\/ghjk\\/envs" $${k});'; +set --global --prepend ${k} ${v}; +` + ), + ].join("\n"), + }; + await Deno.mkdir(envDir, { recursive: true }); + await Deno.writeTextFile( + `${envDir}/loader.fish`, + loader.fish, + ); + await Deno.writeTextFile( + `${envDir}/loader.sh`, + loader.posix, + ); +} + +export async function sync(envDir: string, cx: PortsModuleConfig) { + const installs = buildInstallGraph(cx); + const artifacts = new Map(); + const pendingInstalls = [...installs.indie]; + while (pendingInstalls.length > 0) { + const installId = pendingInstalls.pop()!; + const inst = installs.all.get(installId)!; + + const manifest = cx.ports[inst.portName] ?? + cx.allowedDeps[inst.portName]!; + const depShims: DepShims = {}; + + // create the shims for the deps + const depShimsRootPath = await Deno.makeTempDir({ + prefix: `ghjk_dep_shims_${installId}_`, + }); + for (const depId of manifest.deps ?? []) { + const depPort = cx.allowedDeps[depId.id]!; + const depInstall = { + portName: depPort.name, + }; + const depInstallId = getInstallId(depInstall); + const depArtifacts = artifacts.get(depInstallId); + if (!depArtifacts) { + throw new Error( + `artifacts not found for plug dep "${depInstallId}" when installing "${installId}"`, + ); + } + const depShimDir = std_path.resolve(depShimsRootPath, depInstallId); + await Deno.mkdir(depShimDir); + // TODO: expose LD_LIBRARY from deps + + const { binPaths, installPath } = depArtifacts; + depShims[depId.id] = await shimLinkPaths( + binPaths, + installPath, + depShimDir, + ); + } + + let thisArtifacts; + try { + thisArtifacts = await doInstall(envDir, inst, manifest, depShims); + } catch (err) { + throw new Error(`error installing ${installId}`, { cause: err }); + } + artifacts.set(installId, thisArtifacts); + void Deno.remove(depShimsRootPath, { recursive: true }); + + // mark where appropriate if some other install was depending on it + const parents = installs.revDepEdges.get(installId) ?? []; + for (const parentId of parents) { + const parentDeps = installs.depEdges.get(parentId)!; + + // swap remove from parent deps + const idx = parentDeps.indexOf(installId); + const last = parentDeps.pop()!; + if (parentDeps.length > idx) { + parentDeps[idx] = last; + } + + if (parentDeps.length == 0) { + pendingInstalls.push(parentId); + } + } + } + + const shimDir = std_path.resolve(envDir, "shims"); + if (await std_fs.exists(shimDir)) { + await Deno.remove(shimDir, { recursive: true }); + } + // create shims for the environment + await Promise.allSettled([ + Deno.mkdir(std_path.resolve(shimDir, "bin"), { recursive: true }), + Deno.mkdir(std_path.resolve(shimDir, "lib"), { recursive: true }), + Deno.mkdir(std_path.resolve(shimDir, "include"), { recursive: true }), + ]); + // FIXME: detect conflicts + for (const instId of installs.user) { + const { binPaths, libPaths, includePaths, installPath } = artifacts.get( + instId, + )!; + // bin shims + void await shimLinkPaths( + binPaths, + installPath, + std_path.resolve(shimDir, "bin"), + ); + // lib shims + void await shimLinkPaths( + libPaths, + installPath, + std_path.resolve(shimDir, "lib"), + ); + // include shims + void await shimLinkPaths( + includePaths, + installPath, + std_path.resolve(shimDir, "include"), + ); + } + + // write loader for the env vars mandated by the installs + const env: Record = {}; + for (const [instId, item] of artifacts) { + for (const [key, val] of Object.entries(item.env)) { + const conflict = env[key]; + if (conflict) { + throw new Error( + `duplicate env var found ${key} from sources ${instId} & ${ + conflict[1] + }`, + ); + } + env[key] = [val, instId]; + } + } + let LD_LIBRARY_ENV: string; + switch (Deno.build.os) { + case "darwin": + LD_LIBRARY_ENV = "DYLD_LIBRARY_PATH"; + break; + case "linux": + LD_LIBRARY_ENV = "LD_LIBRARY_PATH"; + break; + default: + throw new Error(`unsupported os ${Deno.build.os}`); + } + const pathVars = { + PATH: `${envDir}/shims/bin`, + LIBRARY_PATH: `${envDir}/shims/lib`, + [LD_LIBRARY_ENV]: `${envDir}/shims/lib`, + C_INCLUDE_PATH: `${envDir}/shims/include`, + CPLUS_INCLUDE_PATH: `${envDir}/shims/include`, + }; + // FIXME: prevent malicious env manipulations + await writeLoader( + envDir, + Object.fromEntries( + Object.entries(env).map(([key, [val, _]]) => [key, val]), + ), + pathVars, + ); +} +function buildInstallGraph(cx: PortsModuleConfig) { + const installs = { + all: new Map(), + indie: [] as string[], + // edges from dependency to dependent + revDepEdges: new Map(), + // edges from dependent to dependency + depEdges: new Map(), + user: new Set(), + }; + const foundInstalls: InstallConfig[] = []; + for (const inst of cx.installs) { + const instId = getInstallId(inst); + // FIXME: better support for multi installs + if (installs.user.has(instId)) { + throw new Error(`duplicate install found by plugin ${inst.portName}`); + } + installs.user.add(instId); + foundInstalls.push(inst); + } + + while (foundInstalls.length > 0) { + const inst = foundInstalls.pop()!; + const manifest = cx.ports[inst.portName] ?? + cx.allowedDeps[inst.portName]; + if (!manifest) { + throw new Error( + `unable to find plugin "${inst.portName}" specified by install ${ + JSON.stringify(inst) + }`, + ); + } + const installId = getInstallId(inst); + + // we might get multiple instances of an install at this point + // due to a plugin being a dependency to multiple others + const conflict = installs.all.get(installId); + if (conflict) { + continue; + } + + installs.all.set(installId, inst); + + if (!manifest.deps || manifest.deps.length == 0) { + installs.indie.push(installId); + } else { + const deps = []; + for (const depId of manifest.deps) { + const depPort = cx.allowedDeps[depId.id]; + if (!depPort) { + throw new Error( + `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, + ); + } + const depInstall = { + portName: depPort.name, + }; + const depInstallId = getInstallId(depInstall); + + // check for cycles + { + const thisDeps = installs.revDepEdges.get(installId); + if (thisDeps && thisDeps.includes(depInstallId)) { + throw new Error( + `cyclic dependency detected between "${installId}" and "${depInstallId}"`, + ); + } + } + + if (!installs.all.has(depInstallId)) { + foundInstalls.push(depInstall); + } + deps.push(depInstallId); + + // make sure the dependency knows this install depends on it + const reverseDeps = installs.revDepEdges.get(depInstallId) ?? []; + reverseDeps.push(installId); + installs.revDepEdges.set(depInstallId, reverseDeps); + } + installs.depEdges.set(installId, deps); + } + } + + return installs; +} + +async function shimLinkPaths( + targetPaths: string[], + installPath: string, + shimDir: string, +) { + const shims: Record = {}; + const foundTargetPaths = [...targetPaths]; + while (foundTargetPaths.length > 0) { + const file = foundTargetPaths.pop()!; + if (std_path.isGlob(file)) { + const glob = file.startsWith("/") + ? file + : std_path.joinGlobs([installPath, file], { extended: true }); + for await (const entry of std_fs.expandGlob(glob)) { + foundTargetPaths.push(entry.path); + } + continue; + } + const filePath = std_path.resolve(installPath, file); + const fileName = std_path.basename(filePath); // TODO: aliases + const shimPath = std_path.resolve(shimDir, fileName); + + if (shims[fileName]) { + throw new Error( + `duplicate shim found when adding shim for file "${fileName}"`, + ); + } + try { + await Deno.remove(shimPath); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) { + throw error; + } + } + await Deno.symlink(filePath, shimPath, { type: "file" }); + shims[fileName] = shimPath; + } + return shims; +} + +type DePromisify = T extends Promise ? Inner : T; +type InstallArtifacts = DePromisify>; + +async function doInstall( + envDir: string, + instUnclean: InstallConfig, + manifest: PortManifestX, + depShims: DepShims, +) { + logger().debug("installing", { envDir, instUnclean, port: manifest }); + let port; + let inst: InstallConfigX; + if (manifest.ty == "denoWorker") { + inst = validators.installConfig.parse(instUnclean); + port = new DenoWorkerPort( + manifest as DenoWorkerPortManifestX, + ); + } else if (manifest.ty == "ambientAccess") { + inst = validators.installConfig.parse(instUnclean); + port = new AmbientAccessPort( + manifest as AmbientAccessPortManifestX, + ); + } else if (manifest.ty == "asdf") { + const asdfInst = validators.asdfInstallConfig.parse(instUnclean); + logger().debug(instUnclean); + inst = asdfInst; + port = await AsdfPort.init(envDir, asdfInst, depShims); + } else { + throw new Error( + `unsupported plugin type "${(manifest as unknown as any).ty}": ${ + JSON.stringify(manifest) + }`, + ); + } + const installId = getInstallId(inst); + const installVersion = validators.string.parse( + inst.version ?? await port.latestStable({ + depShims, + }), + ); + const installPath = std_path.resolve( + envDir, + "installs", + installId, + installVersion, + ); + const downloadPath = std_path.resolve( + envDir, + "downloads", + installId, + installVersion, + ); + const baseArgs: PortArgsBase = { + installPath: installPath, + // installType: "version", + installVersion: installVersion, + depShims, + platform: Deno.build, + config: inst, + }; + { + logger().info(`downloading ${installId}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_download_${installId}:${installVersion}_`, + }); + await port.download({ + ...baseArgs, + downloadPath: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + { + logger().info(`installing ${installId}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_install_${installId}@${installVersion}_`, + }); + await port.install({ + ...baseArgs, + availConcurrency: AVAIL_CONCURRENCY, + downloadPath: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + const binPaths = validators.stringArray.parse( + await port.listBinPaths({ + ...baseArgs, + }), + ); + const libPaths = validators.stringArray.parse( + await port.listLibPaths({ + ...baseArgs, + }), + ); + const includePaths = validators.stringArray.parse( + await port.listIncludePaths({ + ...baseArgs, + }), + ); + const env = zod.record(zod.string()).parse( + await port.execEnv({ + ...baseArgs, + }), + ); + return { env, binPaths, libPaths, includePaths, installPath, downloadPath }; +} diff --git a/modules/ports/types.ts b/modules/ports/types.ts new file mode 100644 index 00000000..331b876c --- /dev/null +++ b/modules/ports/types.ts @@ -0,0 +1,289 @@ +import { semver, zod } from "../../deps/common.ts"; +import logger from "../../utils/logger.ts"; +import { std_path } from "../../deps/common.ts"; + +// TODO: find a better identification scheme for ports + +const portDep = zod.object({ + id: zod.string(), +}); + +const portManifestBase = zod.object({ + ty: zod.string(), + name: zod.string().min(1), + version: zod.string() + .refine((str) => semver.parse(str), { + message: "invalid semver string", + }), + conflictResolution: zod + .enum(["deferToNewer", "override"]) + .nullish() + .default("deferToNewer"), + deps: zod.array(portDep).nullish(), +}).passthrough(); + +const denoWorkerPortManifest = portManifestBase.merge( + zod.object({ + ty: zod.literal("denoWorker"), + moduleSpecifier: zod.string().url(), + }), +); + +const ambientAccessPortManifest = portManifestBase.merge( + zod.object({ + ty: zod.literal("ambientAccess"), + execName: zod.string().min(1), + versionExtractFlag: zod.enum([ + "version", + "-v", + "--version", + "-V", + "-W version", + ]), + versionExtractRegex: zod.string().refine((str) => new RegExp(str), { + message: "invalid RegExp string", + }), + versionExtractRegexFlags: zod.string().refine( + (str) => new RegExp("", str), + { + message: "invalid RegExp flags", + }, + ), + // TODO: custom shell shims + }), +); +const theAsdfPortManifest = portManifestBase.merge( + zod.object({ + ty: zod.literal("asdf"), + moduleSpecifier: zod.string().url(), + }), +); + +const portManifest = zod.discriminatedUnion("ty", [ + denoWorkerPortManifest, + ambientAccessPortManifest, + theAsdfPortManifest, +]); + +const installConfigBase = zod.object({ + version: zod.string() + .nullish(), + conflictResolution: zod + .enum(["deferToNewer", "override"]) + .nullish() + .default("deferToNewer"), + portName: zod.string().min(1), +}).passthrough(); + +const stdInstallConfig = installConfigBase.merge(zod.object({})); + +const asdfInstallConfig = installConfigBase.merge( + zod.object({ + pluginRepo: zod.string().url(), + installType: zod + .enum(["version", "ref"]), + }), +); + +// NOTE: zod unions are tricky. It'll parse with the first schema +// in the array that parses. And if this early schema is a subset +// of its siblings (and it doesn't have `passthrough`), it will discard +// fields meant for sibs. +// Which's to say ordering matters +const installConfig = zod.union([ + asdfInstallConfig, + stdInstallConfig, +]); + +const portsModuleConfigBase = zod.object({ + ports: zod.record(zod.string(), portManifest), + installs: zod.array(installConfig), +}); + +const portsModuleSecureConfig = zod.object({ + allowedPortDeps: zod.array(portDep).nullish(), +}); + +const portsModuleConfig = portsModuleConfigBase.merge(zod.object({ + allowedDeps: zod.record(zod.string(), portManifest), +})); + +const validators = { + portDep, + portManifestBase, + denoWorkerPortManifest, + ambientAccessPortManifest, + string: zod.string(), + installConfigBase, + stdInstallConfig, + installConfig, + asdfInstallConfig, + portManifest, + portsModuleConfigBase, + portsModuleSecureConfig, + portsModuleConfig, + theAsdfPortManifest, + stringArray: zod.string().min(1).array(), +}; +export default validators; + +// Describes the plugin itself +export type PortManifestBase = zod.input; + +export type DenoWorkerPortManifest = zod.input< + typeof validators.denoWorkerPortManifest +>; + +export type AmbientAccessPortManifest = zod.input< + typeof validators.ambientAccessPortManifest +>; +export type TheAsdfPortManifest = zod.input< + typeof validators.theAsdfPortManifest +>; + +// Describes the plugin itself +export type PortManifest = zod.input< + typeof validators.portManifest +>; + +export type PortManifestBaseX = zod.infer; +export type DenoWorkerPortManifestX = zod.infer< + typeof validators.denoWorkerPortManifest +>; +export type AmbientAccessPortManifestX = zod.infer< + typeof validators.ambientAccessPortManifest +>; +// This is the transformed version of PortManifest, ready for consumption +export type PortManifestX = zod.infer< + typeof validators.portManifest +>; + +export type PortDep = zod.infer; + +export type RegisteredPorts = Record; + +export type InstallConfigBase = zod.input< + typeof validators.installConfigBase +>; +export type InstallConfigSimple = Omit; + +export type AsdfInstallConfig = zod.input; +export type AsdfInstallConfigX = zod.infer; + +// Describes a single installation done by a specific plugin. +// export type InstallConfig = zod.input; +export type InstallConfig = zod.input; +export type InstallConfigX = zod.infer; + +export type PortsModuleConfigBase = zod.infer< + typeof validators.portsModuleConfigBase +>; + +/// This is a secure sections of the config intended to be direct exports +/// from the config script instead of the global variable approach the +/// main [`GhjkConfig`] can take. +export type PortsModuleSecureConfig = zod.infer< + typeof validators.portsModuleSecureConfig +>; + +export type PortsModuleConfig = zod.infer; + +export abstract class PortBase { + abstract manifest: PortManifest; + + execEnv( + _args: ExecEnvArgs, + ): Promise> | Record { + return {}; + } + + listBinPaths( + args: ListBinPathsArgs, + ): Promise | string[] { + return [ + std_path.joinGlobs([std_path.resolve(args.installPath, "bin"), "*"]), + ]; + } + + listLibPaths( + args: ListBinPathsArgs, + ): Promise | string[] { + return [ + std_path.joinGlobs([std_path.resolve(args.installPath, "lib"), "*"]), + ]; + } + + listIncludePaths( + args: ListBinPathsArgs, + ): Promise | string[] { + return [ + std_path.joinGlobs([std_path.resolve(args.installPath, "include"), "*"]), + ]; + } + + latestStable(args: ListAllArgs): Promise | string { + return (async () => { + logger().warning( + `using default implementation of latestStable for port ${this.manifest.name}`, + ); + const allVers = await this.listAll(args); + if (allVers.length == 0) { + throw new Error("no versions found"); + } + return allVers[allVers.length - 1]; + })(); + } + + abstract listAll(args: ListAllArgs): Promise | string[]; + + abstract download(args: DownloadArgs): Promise | void; + + abstract install(args: InstallArgs): Promise | void; +} + +interface ASDF_CONFIG_EXAMPLE { + ASDF_INSTALL_TYPE: "version" | "ref"; + ASDF_INSTALL_VERSION: string; // full version number or Git Ref depending on ASDF_INSTALL_TYPE + ASDF_INSTALL_PATH: string; // the path to where the tool should, or has been installed + ASDF_CONCURRENCY: number; // the number of cores to use when compiling the source code. Useful for setting make -j + ASDF_DOWNLOAD_PATH: string; // the path to where the source code or binary was downloaded to by bin/download + ASDF_PLUGIN_PATH: string; // the path the plugin was installed + ASDF_PLUGIN_SOURCE_URL: string; // the source URL of the plugin + ASDF_PLUGIN_PREV_REF: string; // prevous git-ref of the plugin repo + ASDF_PLUGIN_POST_REF: string; // updated git-ref of the plugin repo + ASDF_CMD_FILE: string; // resolves to the full path of the file being sourced +} + +export type DepShims = Record< + string, + Record +>; + +export type PlatformInfo = Omit; + +export interface PortArgsBase { + // installType: "version" | "ref"; + installVersion: string; + installPath: string; + depShims: DepShims; + platform: PlatformInfo; + config: InstallConfigX; +} + +export interface ListAllArgs { + depShims: DepShims; +} + +export type ListBinPathsArgs = PortArgsBase; +export type ExecEnvArgs = PortArgsBase; + +export interface DownloadArgs extends PortArgsBase { + downloadPath: string; + tmpDirPath: string; +} + +export interface InstallArgs extends PortArgsBase { + availConcurrency: number; + downloadPath: string; + tmpDirPath: string; +} diff --git a/modules/ports/worker.ts b/modules/ports/worker.ts new file mode 100644 index 00000000..e1e54a69 --- /dev/null +++ b/modules/ports/worker.ts @@ -0,0 +1,294 @@ +//// +/// + +import logger from "../../utils/logger.ts"; +import { inWorker } from "../../utils/mod.ts"; +import { + type DenoWorkerPortManifestX, + type DownloadArgs, + type ExecEnvArgs, + type InstallArgs, + type ListAllArgs, + type ListBinPathsArgs, + PortBase, +} from "./types.ts"; + +type WorkerReq = { + ty: "assert"; + arg: { + moduleSpecifier: string; + }; +} | { + ty: "listAll"; + arg: ListAllArgs; +} | { + ty: "latestStable"; + arg: ListAllArgs; +} | { + ty: "execEnv"; + arg: ExecEnvArgs; +} | { + ty: "download"; + arg: DownloadArgs; +} | { + ty: "install"; + arg: InstallArgs; +} | { + ty: "listBinPaths"; + arg: ListBinPathsArgs; +} | { + ty: "listLibPaths"; + arg: ListBinPathsArgs; +} | { + ty: "listIncludePaths"; + arg: ListBinPathsArgs; +}; + +type WorkerResp = { + ty: "assert"; + // success +} | { + ty: "listAll"; + payload: string[]; +} | { + ty: "latestStable"; + payload: string; +} | { + ty: "listBinPaths"; + payload: string[]; +} | { + ty: "listLibPaths"; + payload: string[]; +} | { + ty: "listIncludePaths"; + payload: string[]; +} | { + ty: "execEnv"; + payload: Record; +} | { + ty: "download"; +} | { + ty: "install"; +}; + +/// Make sure to call this before any `await` point or your +/// plug might miss messages +export function initDenoWorkerPort

(portCtor: () => P) { + if (inWorker()) { + // let plugClass: (new () => PlugBase) | undefined; + // const plugInit = () => { + // if (!plugClass) { + // throw new Error("worker yet to be initialized"); + // } + // return new plugClass(); + // }; + self.onmessage = async (msg: MessageEvent) => { + const req = msg.data; + if (!req.ty) { + logger().error("invalid worker request", req); + throw new Error("unrecognized worker request type"); + } + let res: WorkerResp; + if (req.ty == "assert") { + throw new Error("not yet impl"); + /* const { default: defExport } = await import(req.arg.moduleSpecifier); + if (typeof defExport != "function") { + throw new Error( + `default export of module ${req.arg.moduleSpecifier} is not a function`, + ); + } + plugClass = defExport; + res = { + ty: req.ty, + }; */ + } else if (req.ty == "listAll") { + res = { + ty: req.ty, + // id: req.id, + payload: await portCtor().listAll(req.arg), + }; + } else if (req.ty === "latestStable") { + res = { + ty: req.ty, + payload: await portCtor().latestStable(req.arg), + }; + } else if (req.ty === "execEnv") { + res = { + ty: req.ty, + payload: await portCtor().execEnv(req.arg), + }; + } else if (req.ty === "listBinPaths") { + res = { + ty: req.ty, + payload: await portCtor().listBinPaths(req.arg), + }; + } else if (req.ty === "listLibPaths") { + res = { + ty: req.ty, + payload: await portCtor().listLibPaths(req.arg), + }; + } else if (req.ty === "listIncludePaths") { + res = { + ty: req.ty, + payload: await portCtor().listIncludePaths(req.arg), + }; + } else if (req.ty === "download") { + await portCtor().download(req.arg), + res = { + ty: req.ty, + }; + } else if (req.ty === "install") { + await portCtor().install(req.arg), + res = { + ty: req.ty, + }; + } else { + logger().error("unrecognized worker request type", req); + throw new Error("unrecognized worker request type"); + } + self.postMessage(res); + }; + } else { + throw new Error("expecting to be running not running in Worker"); + } +} +// type MethodKeys = { +// [P in keyof T]-?: T[P] extends Function ? P : never; +// }[keyof T]; + +export class DenoWorkerPort extends PortBase { + constructor( + public manifest: DenoWorkerPortManifestX, + ) { + super(); + } + + /// This creates a new worker on every call + async call( + req: WorkerReq, + ): Promise { + const worker = new Worker(this.manifest.moduleSpecifier, { + name: `${this.manifest.name}@${this.manifest.version}`, + type: "module", + }); + const promise = new Promise((resolve, reject) => { + worker.onmessage = (evt: MessageEvent) => { + const res = evt.data; + resolve(res); + }; + worker.onmessageerror = (evt) => { + reject(evt.data); + }; + worker.onerror = (err) => { + reject(err); + }; + }); + worker.postMessage(req); + const resp = await promise; + worker.terminate(); + return resp; + } + + async listAll(env: ListAllArgs): Promise { + const req: WorkerReq = { + ty: "listAll", + // id: crypto.randomUUID(), + arg: env, + }; + const res = await this.call(req); + if (res.ty == "listAll") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + + async latestStable(env: ListAllArgs): Promise { + const req: WorkerReq = { + ty: "latestStable", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "latestStable") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + + async execEnv( + env: ExecEnvArgs, + ): Promise> { + const req: WorkerReq = { + ty: "execEnv", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "execEnv") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + async listBinPaths( + env: ListBinPathsArgs, + ): Promise { + const req: WorkerReq = { + ty: "listBinPaths", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "listBinPaths") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + + async listLibPaths( + env: ListBinPathsArgs, + ): Promise { + const req: WorkerReq = { + ty: "listLibPaths", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "listLibPaths") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + + async listIncludePaths( + env: ListBinPathsArgs, + ): Promise { + const req: WorkerReq = { + ty: "listIncludePaths", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "listIncludePaths") { + return res.payload; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + + async download(env: DownloadArgs): Promise { + const req: WorkerReq = { + ty: "download", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "download") { + return; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + async install(env: InstallArgs): Promise { + const req: WorkerReq = { + ty: "install", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "install") { + return; + } + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); + } +} diff --git a/modules/std.ts b/modules/std.ts new file mode 100644 index 00000000..e0d1b8ca --- /dev/null +++ b/modules/std.ts @@ -0,0 +1,25 @@ +import { PortsModule } from "./ports/mod.ts"; +import portsValidators from "./ports/types.ts"; +import { type GhjkCtx, type ModuleManifest } from "./types.ts"; + +export const ports = "ports"; + +export const tasks = "tasks"; + +export const map = { + [ports as string]: { + ctor: (ctx: GhjkCtx, manifest: ModuleManifest) => + new PortsModule( + ctx, + portsValidators.portsModuleConfig.parse(manifest.config), + ), + }, + [tasks as string]: { + // TODO: impl tasks module + ctor: (ctx: GhjkCtx, manifest: ModuleManifest) => + new PortsModule( + ctx, + portsValidators.portsModuleConfig.parse(manifest.config), + ), + }, +}; diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts new file mode 100644 index 00000000..c4d982b7 --- /dev/null +++ b/modules/tasks/mod.ts @@ -0,0 +1,21 @@ +// TODO: + +// SKETCH +/* +- Host runs ghjk.ts in a "./host/deno.ts" Worker sandbox to get serialized config +- Serialized config describes meta of all specified Tasks +- Host runs ghjk.ts in a Task specific Worker config instructing it to exec task Foo + - When run in Task Worker, ghjk.ts will only execute the instructed Task + - ghjk.ts task items are just mainly deno functions. + - dax is provided by default to make shelling out ergonmic + - We shim up Port installs in the environment/PATH to make tools avail + +This is a pretty much deno agnostic design. Unix inspired. + +Host program -> Config program +Host program -> Task program(s) + +It just so happens our programs are Workers and the both the tasks +and configs are defined in a single file. The current design should +hopefully make it extensible if that's ever desired. +*/ diff --git a/modules/types.ts b/modules/types.ts new file mode 100644 index 00000000..f66eb299 --- /dev/null +++ b/modules/types.ts @@ -0,0 +1,21 @@ +import { zod } from "../deps/common.ts"; + +// FIXME: better module ident/versioning +const moduleId = zod.string(); + +const moduleManifest = zod.object({ + id: moduleId, + config: zod.unknown(), +}); + +export type ModuleId = zod.infer; +export type ModuleManifest = zod.infer; +export type GhjkCtx = { + configPath: string; + envDir: string; +}; + +export default { + moduleManifest, + moduleId, +}; diff --git a/port.ts b/port.ts new file mode 100644 index 00000000..1106dea0 --- /dev/null +++ b/port.ts @@ -0,0 +1,132 @@ +//! this provides common exports for Port implementors + +import { + addInstall, + type AmbientAccessPortManifest, + type DenoWorkerPortManifest, + type DownloadArgs, + type InstallConfig, + type PortBase, + type PortsModuleConfigBase, + registerAmbientPort, + registerDenoPort, + registerPort, +} from "./modules/ports/mod.ts"; +import { std_fs, std_path, std_url } from "./deps/ports.ts"; +import { initDenoWorkerPort } from "./modules/ports/worker.ts"; +import * as asdf from "./modules/ports/asdf.ts"; +import logger, { setup as setupLogger } from "./utils/logger.ts"; +import { inWorker } from "./utils/mod.ts"; + +export * from "./modules/ports/mod.ts"; +export * from "./utils/mod.ts"; +export * from "./deps/ports.ts"; +export { default as logger } from "./utils/logger.ts"; +export { initDenoWorkerPort } from "./modules/ports/worker.ts"; +export * as asdf from "./modules/ports/asdf.ts"; +export type * from "./modules/ports/mod.ts"; +export * from "./utils/unarchive.ts"; + +if (inWorker()) { + setupLogger(); +} + +declare global { + interface Window { + // this is null except when we're realmed along `ghjk.ts` + // i.e. a deno worker port context won't have it avail + ports: PortsModuleConfigBase; + } +} + +function isInConfig() { + return !!self.ports; +} + +export function registerDenoPortGlobal

( + manifest: DenoWorkerPortManifest, + portCtor: () => P, +) { + if (isInConfig()) { + registerDenoPort(self.ports, manifest); + } else if (inWorker()) { + initDenoWorkerPort(portCtor); + } +} + +export function registerAsdfPort() { + if (isInConfig()) { + registerPort(self.ports, asdf.manifest); + } +} + +export function registerAmbientPortGlobal( + manifestUnclean: AmbientAccessPortManifest, +) { + if (isInConfig()) { + registerAmbientPort(self.ports, manifestUnclean); + } +} + +export function addInstallGlobal( + config: InstallConfig, +) { + if (isInConfig()) { + addInstall(self.ports, config); + } +} + +/// This avoid re-downloading a file if it's already successfully downloaded before. +export async function downloadFile( + env: DownloadArgs, + url: string, + options: { + fileName?: string; + mode?: number; + } = {}, +) { + const { fileName, mode } = { + fileName: std_url.basename(url), + mode: 0o666, + ...options, + }; + const fileDwnPath = std_path.resolve(env.downloadPath, fileName); + if (await std_fs.exists(fileDwnPath)) { + logger().debug(`file ${fileName} already downloaded, skipping`); + return; + } + const tmpFilePath = std_path.resolve( + env.tmpDirPath, + fileName, + ); + + const resp = await fetch(url); + + if (!resp.ok) { + throw new Error( + `${resp.status}: ${resp.statusText} downloading file at ${url}`, + ); + } + const length = resp.headers.get("content-length"); + logger().debug( + `downloading file: `, + { + fileSize: length ? Number(length) / 1024 : "N/A", + url, + to: fileDwnPath, + }, + ); + + const dest = await Deno.open( + tmpFilePath, + { create: true, truncate: true, write: true, mode }, + ); + await resp.body!.pipeTo(dest.writable, { preventClose: false }); + await std_fs.ensureDir(env.downloadPath); + await std_fs.copy( + tmpFilePath, + fileDwnPath, + ); +} + +export const removeFile = Deno.remove; diff --git a/ports/act.ts b/ports/act.ts new file mode 100644 index 00000000..d2bfbe1a --- /dev/null +++ b/ports/act.ts @@ -0,0 +1,122 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "act@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "nektos"; +const repoName = "act"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + listBinPaths(): string[] { + return [ + "act", + ]; + } + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.tmpDirPath, + args.installPath, + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + let os; + let ext; + switch (platform.os) { + case "linux": + os = "Linux"; + ext = "tar.gz"; + break; + case "darwin": + os = "Darwin"; + ext = "tar.gz"; + break; + case "windows": + os = "Windows"; + ext = "zip"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}_${os}_${arch}.${ext}`; +} diff --git a/ports/asdf.ts b/ports/asdf.ts new file mode 100644 index 00000000..bc05223c --- /dev/null +++ b/ports/asdf.ts @@ -0,0 +1,14 @@ +import { + addInstallGlobal, + asdf, + AsdfInstallConfig, + registerAsdfPort, +} from "../port.ts"; + +registerAsdfPort(); +export default function install(config: Omit) { + addInstallGlobal({ + portName: asdf.manifest.name, + ...config, + }); +} diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts new file mode 100644 index 00000000..e1ef6d24 --- /dev/null +++ b/ports/cargo-binstall.ts @@ -0,0 +1,101 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +export const manifest = { + ty: "denoWorker" as const, + name: "cargo-binstall@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "cargo-bins"; +const repoName = "cargo-binstall"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async listAll() { + const metadataRequest = await fetch( + `https://index.crates.io/ca/rg/cargo-binstall`, + ); + const metadataText = await metadataRequest.text(); + const versions = metadataText + .split("\n") + .filter((str) => str.length > 0) + .map((str) => + JSON.parse(str) as { + vers: string; + } + ); + return versions.map((ver) => ver.vers); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.tmpDirPath, + std_path.resolve(args.installPath, "bin"), + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + if (platform.os == "darwin") { + // NOTE: the archive file name extensions are different from os to os + return `${repoAddress}/releases/download/v${installVersion}/${repoName}-${arch}-apple-darwin.full.zip`; + } else if (platform.os == "linux") { + // TODO: support for ubuntu/debian versions + // we'll need a way to expose that to ports + const os = "unknown-linux-musl"; + return `${repoAddress}/releases/download/v${installVersion}/${repoName}-${arch}-${os}.full.tgz`; + } else { + throw new Error(`unsupported os: ${platform.os}`); + } +} diff --git a/ports/cargo-insta.ts b/ports/cargo-insta.ts new file mode 100644 index 00000000..1d184c52 --- /dev/null +++ b/ports/cargo-insta.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigSimple, + logger, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, +} from "../port.ts"; +import * as std_ports from "../modules/ports/std.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "cargo-insta@cbinst", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_ports.cbin_ghrel, + ], +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +export class Port extends PortBase { + manifest = manifest; + + async listAll() { + const metadataRequest = await fetch( + `https://index.crates.io/ca/rg/cargo-insta`, + ); + const metadataText = await metadataRequest.text(); + const versions = metadataText + .split("\n") + .filter((str) => str.length > 0) + .map((str) => + JSON.parse(str) as { + vers: string; + } + ); + return versions.map((ver) => ver.vers); + } + + async download(args: DownloadArgs) { + const fileName = "cargo-insta"; + if ( + await std_fs.exists(std_path.resolve(args.downloadPath, fileName)) + ) { + logger().debug( + `file ${fileName} already downloaded, skipping whole download`, + ); + return; + } + await spawn([ + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), + "cargo-insta", + `--version`, + args.installVersion, + `--install-path`, + args.tmpDirPath, + `--no-confirm`, + `--disable-strategies`, + `compile`, + `--no-track`, + ]); + await std_fs.copy( + args.tmpDirPath, + args.downloadPath, + ); + } + + async install(args: InstallArgs) { + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.downloadPath, + std_path.resolve(args.installPath, "bin"), + ); + } +} diff --git a/ports/curl.ts b/ports/curl.ts new file mode 100644 index 00000000..0b7f3753 --- /dev/null +++ b/ports/curl.ts @@ -0,0 +1,22 @@ +import { + addInstallGlobal, + type AmbientAccessPortManifest, + registerAmbientPortGlobal, +} from "../port.ts"; + +export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, + name: "curl@aa", + version: "0.1.0", + execName: "curl", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPortGlobal(manifest); +export default function install() { + addInstallGlobal({ + portName: manifest.name, + }); +} diff --git a/ports/earthly.ts b/ports/earthly.ts new file mode 100644 index 00000000..3ac4b167 --- /dev/null +++ b/ports/earthly.ts @@ -0,0 +1,109 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "earthly@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "earthly"; +const repoName = "earthly"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + const fileName = repoName; + await downloadFile(args, downloadUrl(args.installVersion, args.platform), { + mode: 0o700, + fileName, + }); + } + + async install(args: InstallArgs) { + const fileName = repoName; + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.ensureDir(std_path.resolve(args.installPath, "bin")); + await std_fs.copy( + fileDwnPath, + std_path.resolve(args.installPath, "bin", fileName), + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "amd64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + let os; + switch (platform.os) { + case "linux": + os = "linux"; + break; + case "darwin": + os = "darwin"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${os}-${arch}`; +} diff --git a/ports/git.ts b/ports/git.ts new file mode 100644 index 00000000..c6bad39d --- /dev/null +++ b/ports/git.ts @@ -0,0 +1,22 @@ +import { + addInstallGlobal, + type AmbientAccessPortManifest, + registerAmbientPortGlobal, +} from "../port.ts"; + +export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, + name: "git@aa", + version: "0.1.0", + execName: "git", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPortGlobal(manifest); +export default function git() { + addInstallGlobal({ + portName: manifest.name, + }); +} diff --git a/ports/jco.ts b/ports/jco.ts new file mode 100644 index 00000000..d13886d6 --- /dev/null +++ b/ports/jco.ts @@ -0,0 +1,110 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + ListAllArgs, + pathWithDepShims, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; +import node from "./node.ts"; +import * as std_ports from "../modules/ports/std.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "jco@npm", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_ports.node_org, + ], +}; +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); + // FIXME: conflict flags for install configs + node({}); +} + +class Port extends PortBase { + manifest = manifest; + + async listAll(_env: ListAllArgs) { + const metadataRequest = await fetch( + `https://registry.npmjs.org/@bytecodealliance/jco`, + { + headers: { + // use abbreviated registry info which's still 500kb unzipped + "Accept": "application/vnd.npm.install-v1+json", + }, + }, + ); + const metadata = await metadataRequest.json() as { + versions: Record; + }; + + const versions = Object.keys(metadata.versions); + return versions; + } + + async download(args: DownloadArgs) { + await downloadFile( + args, + artifactUrl(args.installVersion, args.platform), + ); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + await std_fs.copy( + std_path.resolve( + args.tmpDirPath, + "package", + ), + args.installPath, + ); + await spawn([ + depBinShimPath(std_ports.node_org, "npm", args.depShims), + "install", + "--no-fund", + ], { + cwd: args.installPath, + env: { + PATH: pathWithDepShims(args.depShims), + }, + }); + await std_fs.ensureDir(std_path.resolve(args.installPath, "bin")); + await Deno.symlink( + std_path.resolve(args.installPath, "src", "jco.js"), + std_path.resolve(args.installPath, "bin", "jco"), + ); + } +} + +function artifactUrl(installVersion: string, _platform: PlatformInfo) { + return `https://registry.npmjs.org/@bytecodealliance/jco/-/jco-${installVersion}.tgz`; +} diff --git a/ports/mold.ts b/ports/mold.ts new file mode 100644 index 00000000..f0ae0e3a --- /dev/null +++ b/ports/mold.ts @@ -0,0 +1,118 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "mold@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "rui314"; +const repoName = "mold"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + const dirs = []; + for await ( + const entry of std_fs.expandGlob( + std_path.joinGlobs([args.tmpDirPath, "*"]), + ) + ) { + dirs.push(entry); + } + if (dirs.length != 1 || !dirs[0].isDirectory) { + throw new Error("unexpected archive contents"); + } + await std_fs.copy( + dirs[0].path, + args.installPath, + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + if (platform.os == "linux") { + const os = "linux"; + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${ + installVersion.startsWith("v") ? installVersion.slice(1) : installVersion + }-${arch}-${os}.tar.gz`; + } else { + throw new Error(`unsupported os: ${platform.os}`); + } +} diff --git a/ports/node.ts b/ports/node.ts new file mode 100644 index 00000000..0d133016 --- /dev/null +++ b/ports/node.ts @@ -0,0 +1,127 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + ExecEnvArgs, + InstallArgs, + type InstallConfigSimple, + ListAllArgs, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, +} from "../port.ts"; +// import * as std_ports from "../std.ts"; + +const tar_aa_id = { + id: "tar@aa", +}; + +// TODO: sanity check exports of all ports +export const manifest = { + ty: "denoWorker" as const, + name: "node@org", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + tar_aa_id, + ], +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +// FIXME: improve multi platform support story +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +export class Port extends PortBase { + manifest = manifest; + + execEnv(args: ExecEnvArgs) { + return { + NODE_PATH: args.installPath, + }; + } + + // we wan't to avoid adding libraries found by default at /lib + // to PATHs as they're just node_module sources + listLibPaths(): string[] { + return []; + } + + async listAll(_env: ListAllArgs) { + const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); + const metadata = await metadataRequest.json() as { version: string }[]; + + const versions = metadata.map((v) => v.version); + // sort them numerically to make sure version 0.10.0 comes after 0.2.9 + return versions.sort((a, b) => + a.localeCompare(b, undefined, { numeric: true }) + ); + } + + async download(args: DownloadArgs) { + await downloadFile(args, artifactUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + await spawn([ + depBinShimPath(tar_aa_id, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + await std_fs.copy( + std_path.resolve( + args.tmpDirPath, + std_path.basename(fileDwnPath, ".tar.gz"), + ), + args.installPath, + ); + } +} + +// node distribute archives that contain the binary, ecma source for npm/npmx/corepack, include source files and more +function artifactUrl(installVersion: string, platform: PlatformInfo) { + let arch; + let os; + switch (platform.arch) { + case "x86_64": + arch = "x64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + switch (platform.os) { + case "linux": + os = "linux"; + break; + case "darwin": + os = "darwin"; + break; + default: + throw new Error(`unsupported os: ${platform.arch}`); + } + return `https://nodejs.org/dist/${installVersion}/node-${installVersion}-${os}-${arch}.tar.gz`; +} diff --git a/ports/pnpm.ts b/ports/pnpm.ts new file mode 100644 index 00000000..41b58a6d --- /dev/null +++ b/ports/pnpm.ts @@ -0,0 +1,113 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + ListAllArgs, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, +} from "../port.ts"; + +export const manifest = { + ty: "denoWorker" as const, + name: "pnpm@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +class Port extends PortBase { + manifest = manifest; + + async listAll(_env: ListAllArgs) { + const metadataRequest = await fetch( + `https://registry.npmjs.org/@pnpm/exe`, + { + headers: { + // use abbreviated registry info which's still 500kb unzipped + "Accept": "application/vnd.npm.install-v1+json", + }, + }, + ); + const metadata = await metadataRequest.json() as { + versions: Record; + }; + + const versions = Object.keys(metadata.versions); + return versions; + } + + async download(args: DownloadArgs) { + await downloadFile( + args, + artifactUrl(args.installVersion, args.platform), + { + mode: 0o700, + }, + ); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + await std_fs.ensureDir(std_path.resolve(args.installPath, "bin")); + await std_fs.copy( + fileDwnPath, + std_path.resolve( + args.installPath, + "bin", + args.platform.os == "windows" ? "pnpm.exe" : "pnpm", + ), + ); + } +} + +// pnpm distribute an executable directly +function artifactUrl(installVersion: string, platform: PlatformInfo) { + let arch; + let os; + switch (platform.arch) { + case "x86_64": + arch = "x64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + switch (platform.os) { + case "linux": + os = "linuxstatic"; + break; + case "darwin": + os = "macos"; + break; + case "windows": + os = "win"; + return `https://github.com/pnpm/pnpm/releases/download/v${installVersion}/pnpm-${os}-${arch}.exe`; + default: + throw new Error(`unsupported os: ${platform.arch}`); + } + return `https://github.com/pnpm/pnpm/releases/download/v${installVersion}/pnpm-${os}-${arch}`; +} diff --git a/ports/protoc.ts b/ports/protoc.ts new file mode 100644 index 00000000..cb81281f --- /dev/null +++ b/ports/protoc.ts @@ -0,0 +1,113 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "protoc@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "protocolbuffers"; +const repoName = "protobuf"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + await std_fs.copy( + args.tmpDirPath, + args.installPath, + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let os; + switch (platform.os) { + case "linux": + os = "linux"; + break; + case "darwin": + os = "osx"; + break; + default: + throw new Error(`unsupported os: ${platform.os}`); + } + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch_64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/protoc-${ + installVersion.replace(/^v/, "") + }-${os}-${arch}.zip`; +} diff --git a/ports/ruff.ts b/ports/ruff.ts new file mode 100644 index 00000000..2455529d --- /dev/null +++ b/ports/ruff.ts @@ -0,0 +1,118 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "ruff@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "astral-sh"; +const repoName = "ruff"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.tmpDirPath, + std_path.resolve(args.installPath, "bin"), + ); + // await Deno.chmod(std_path.resolve(args.installPath, "bin", "ruff"), 0o700); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + let os; + let ext; + switch (platform.os) { + case "linux": + os = "unknown-linux-musl"; + ext = "tar.gz"; + break; + case "darwin": + os = "apple-darwin"; + ext = "tar.gz"; + break; + case "windows": + os = "pc-windows-msvc"; + ext = "zip"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${arch}-${os}.${ext}`; +} diff --git a/ports/tar.ts b/ports/tar.ts new file mode 100644 index 00000000..d6825d19 --- /dev/null +++ b/ports/tar.ts @@ -0,0 +1,22 @@ +import { + addInstallGlobal, + type AmbientAccessPortManifest, + registerAmbientPortGlobal, +} from "../port.ts"; + +export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, + name: "tar@aa", + version: "0.1.0", + execName: "tar", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPortGlobal(manifest); +export default function install() { + addInstallGlobal({ + portName: manifest.name, + }); +} diff --git a/ports/unzip.ts b/ports/unzip.ts new file mode 100644 index 00000000..b7a617c6 --- /dev/null +++ b/ports/unzip.ts @@ -0,0 +1,22 @@ +import { + addInstallGlobal, + type AmbientAccessPortManifest, + registerAmbientPortGlobal, +} from "../port.ts"; + +export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, + name: "unzip@aa", + version: "0.1.0", + execName: "unzip", + versionExtractFlag: "-v", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPortGlobal(manifest); +export default function install() { + addInstallGlobal({ + portName: manifest.name, + }); +} diff --git a/ports/wasm-opt.ts b/ports/wasm-opt.ts new file mode 100644 index 00000000..95f5bbe0 --- /dev/null +++ b/ports/wasm-opt.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigSimple, + logger, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, +} from "../port.ts"; +import * as std_ports from "../modules/ports/std.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "wasm-opt@cbinst", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_ports.cbin_ghrel, + ], +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +export class Port extends PortBase { + manifest = manifest; + + async listAll() { + const metadataRequest = await fetch( + `https://index.crates.io/wa/sm/wasm-opt`, + ); + const metadataText = await metadataRequest.text(); + const versions = metadataText + .split("\n") + .filter((str) => str.length > 0) + .map((str) => + JSON.parse(str) as { + vers: string; + } + ); + return versions.map((ver) => ver.vers); + } + + async download(args: DownloadArgs) { + const fileName = "wasm-opt"; + if ( + await std_fs.exists(std_path.resolve(args.downloadPath, fileName)) + ) { + logger().debug( + `file ${fileName} already downloaded, skipping whole download`, + ); + return; + } + await spawn([ + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), + "wasm-opt", + `--version`, + args.installVersion, + `--install-path`, + args.tmpDirPath, + `--no-confirm`, + `--disable-strategies`, + `compile`, + `--no-track`, + ]); + await std_fs.copy( + args.tmpDirPath, + args.downloadPath, + ); + } + + async install(args: InstallArgs) { + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.downloadPath, + std_path.resolve(args.installPath, "bin"), + ); + } +} diff --git a/ports/wasm-tools.ts b/ports/wasm-tools.ts new file mode 100644 index 00000000..6f5fe833 --- /dev/null +++ b/ports/wasm-tools.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigSimple, + logger, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, +} from "../port.ts"; +import * as std_ports from "../modules/ports/std.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "wasm-tools@cbinst", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_ports.cbin_ghrel, + ], +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +export class Port extends PortBase { + manifest = manifest; + + async listAll() { + const metadataRequest = await fetch( + `https://index.crates.io/wa/sm/wasm-tools`, + ); + const metadataText = await metadataRequest.text(); + const versions = metadataText + .split("\n") + .filter((str) => str.length > 0) + .map((str) => + JSON.parse(str) as { + vers: string; + } + ); + return versions.map((ver) => ver.vers); + } + + async download(args: DownloadArgs) { + const fileName = "wasm-tools"; + if ( + await std_fs.exists(std_path.resolve(args.downloadPath, fileName)) + ) { + logger().debug( + `file ${fileName} already downloaded, skipping whole download`, + ); + return; + } + await spawn([ + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), + "wasm-tools", + `--version`, + args.installVersion, + `--install-path`, + args.tmpDirPath, + `--no-confirm`, + `--disable-strategies`, + `compile`, + `--no-track`, + ]); + await std_fs.copy( + args.tmpDirPath, + args.downloadPath, + ); + } + + async install(args: InstallArgs) { + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.downloadPath, + std_path.resolve(args.installPath, "bin"), + ); + } +} diff --git a/ports/wasmedge.ts b/ports/wasmedge.ts new file mode 100644 index 00000000..7fc779da --- /dev/null +++ b/ports/wasmedge.ts @@ -0,0 +1,176 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + ExecEnvArgs, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, +} from "../port.ts"; +import * as std_ports from "../modules/ports/std.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "wasmedge@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_ports.tar_aa, + ], +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +// TODO: wasmedge extension and plugin support +/* +const supportedExtensions = ["tensorflow" as const, "image" as const]; + +type DeArray = A extends Array ? T : A; + +type SupportedExtensions = DeArray; + +const supportedPlugins = [ + "wasi_nn-openvino" as const, + "wasi_crypto" as const, + "wasi_nn-pytorch" as const, + "wasi_nn-tensorflowlite" as const, + "wasi_nn-ggml" as const, + "wasi_nn-ggml-cuda" as const, + "wasi_nn-ggml-cuda" as const, + "wasmedge_tensorflow" as const, + "wasmedge_tensorflowlite" as const, + "wasmedge_image" as const, + "wasmedge_rustls" as const, + "wasmedge_bpf" as const, +]; + */ +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "WasmEdge"; +const repoName = "WasmEdge"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + execEnv(args: ExecEnvArgs) { + return { + WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib"), + }; + } + + listLibPaths(): string[] { + return ["lib*/*"]; + } + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + // NOTE: this downloads a 1+ meg json + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await spawn([ + depBinShimPath(std_ports.tar_aa, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + + const dirs = []; + for await ( + const entry of std_fs.expandGlob( + std_path.joinGlobs([args.tmpDirPath, "*"]), + ) + ) { + dirs.push(entry); + } + if (dirs.length != 1 || !dirs[0].isDirectory) { + throw new Error("unexpected archive contents"); + } + await std_fs.copy( + dirs[0].path, + args.installPath, + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + if (platform.os == "darwin") { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${installVersion}-${platform.os}_${arch}.tar.gz`; + } else if (platform.os == "linux") { + // TODO: support for ubuntu/debian versions + // we'll need a way to expose that to ports + const os = "manylinux2014"; + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch64"; // NOTE: arch is different from darwin releases + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${installVersion}-${os}_${arch}.tar.gz`; + } else { + throw new Error(`unsupported os: ${platform.os}`); + } +} diff --git a/ports/whiz.ts b/ports/whiz.ts new file mode 100644 index 00000000..ab68f32e --- /dev/null +++ b/ports/whiz.ts @@ -0,0 +1,114 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigSimple, + type PlatformInfo, + PortBase, + registerDenoPortGlobal, + removeFile, + std_fs, + std_path, + std_url, + unarchive, +} from "../port.ts"; + +const manifest = { + ty: "denoWorker" as const, + name: "whiz@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPortGlobal(manifest, () => new Port()); + +export default function install(config: InstallConfigSimple = {}) { + addInstallGlobal({ + portName: manifest.name, + ...config, + }); +} + +const repoOwner = "zifeo"; +const repoName = "whiz"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Port extends PortBase { + manifest = manifest; + + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); + + const metadata = await metadataRequest.json() as { + tag_name: string; + }; + + return metadata.tag_name; + } + + async listAll() { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases`, + ); + + const metadata = await metadataRequest.json() as [{ + tag_name: string; + }]; + + return metadata.map((rel) => rel.tag_name).reverse(); + } + + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } + + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + + await unarchive(fileDwnPath, args.tmpDirPath); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( + args.tmpDirPath, + std_path.resolve(args.installPath, "bin"), + ); + } +} + +function downloadUrl(installVersion: string, platform: PlatformInfo) { + let arch; + switch (platform.arch) { + case "x86_64": + arch = "x86_64"; + break; + case "aarch64": + arch = "aarch64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + let os; + const ext = "tar.gz"; + switch (platform.os) { + case "linux": + os = "unknown-linux-musl"; + break; + case "darwin": + os = "apple-darwin"; + break; + case "windows": + os = "pc-windows-msvc"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${installVersion}-${arch}-${os}.${ext}`; +} diff --git a/setup_logger.ts b/setup_logger.ts new file mode 100644 index 00000000..348b2bf0 --- /dev/null +++ b/setup_logger.ts @@ -0,0 +1,3 @@ +import { setup } from "./utils/logger.ts"; + +setup(); diff --git a/tests/ambient.ts b/tests/ambient.ts new file mode 100644 index 00000000..10973f06 --- /dev/null +++ b/tests/ambient.ts @@ -0,0 +1,32 @@ +import "../setup_logger.ts"; +import { std_assert } from "../deps/dev.ts"; +import { AmbientAccessPort } from "../modules/ports/ambient.ts"; +import { type AmbientAccessPortManifest } from "../modules/ports/types.ts"; + +import * as tar from "../ports/tar.ts"; +import * as git from "../ports/git.ts"; +import * as curl from "../ports/curl.ts"; +import * as unzip from "../ports/unzip.ts"; + +const manifests = [ + { + name: "ls", + execName: "ls", + version: "0.1.0", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", + }, + tar.manifest, + git.manifest, + curl.manifest, + unzip.manifest, +]; +for (const manifest of manifests) { + Deno.test(`ambient access ${manifest.name}`, async () => { + const plug = new AmbientAccessPort(manifest as AmbientAccessPortManifest); + const versions = await plug.listAll({ depShims: {} }); + console.log(versions); + std_assert.assertEquals(versions.length, 1); + }); +} diff --git a/tests/e2e.ts b/tests/e2e.ts new file mode 100644 index 00000000..806aea1b --- /dev/null +++ b/tests/e2e.ts @@ -0,0 +1,155 @@ +import { dockerE2eTest } from "./utils.ts"; + +// order tests by download size to make failed runs less expensive +await dockerE2eTest([ + // 3 megs + { + name: "protoc", + imports: `import port from "$ghjk/ports/protoc.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `protoc --version`, + }, + // 6 megs + { + name: "ruff", + imports: `import port from "$ghjk/ports/ruff.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `ruff --version`, + }, + // 7 megs + { + name: "whiz", + imports: `import port from "$ghjk/ports/whiz.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `whiz --version`, + }, + // 7 megs + { + name: "act", + imports: `import port from "$ghjk/ports/act.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `act --version`, + }, + // 7 megs + { + name: "cargo-binstall", + imports: `import port from "$ghjk/ports/cargo-binstall.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `cargo-binstall -V`, + }, + // 8 megs + { + name: "mold", + imports: `import port from "$ghjk/ports/mold.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `mold -V`, + }, + // 16 megs + { + name: "wasmedge", + imports: `import port from "$ghjk/ports/wasmedge.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `wasmedge --version`, + }, + // cargo binstall +7 megs + { + name: "cargo-insta", + imports: `import port from "$ghjk/ports/cargo-insta.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `cargo-insta -V`, + }, + // cargo binsatll 13 megs + { + name: "wasm-tools", + imports: `import port from "$ghjk/ports/wasm-tools.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `wasm-tools -V`, + }, + // 25 megs + { + name: "node", + imports: `import port from "$ghjk/ports/node.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `node --version`, + }, + // cargo-binstall + 22 megs + { + name: "wasm-opt", + imports: `import port from "$ghjk/ports/wasm-opt.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `wasm-opt --version`, + }, + // 42 megs + { + name: "pnpm", + imports: `import port from "$ghjk/ports/earthly.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `earthly --version`, + }, + // 56 megs + { + name: "pnpm", + imports: `import port from "$ghjk/ports/pnpm.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `pnpm --version`, + }, + // node + more megs + { + name: "jco", + imports: `import port from "$ghjk/ports/jco.ts"`, + confFn: `async () => { + port({ }); + }`, + ePoint: `jco --version`, + }, + // 77 meg + + { + name: "asdf-cmake", + imports: `import port from "$ghjk/ports/asdf.ts"`, + confFn: `async () => { + port({ + pluginRepo: "https://github.com/asdf-community/asdf-cmake", + installType: "version", + }); + }`, + ePoint: `cmake --version`, + }, + // // big + // { + // name: "asdf-python", + // imports: `import port from "$ghjk/ports/asdf.ts"`, + // confFn: `async () => { + // port({ + // portRepo: "https://github.com/asdf-community/asdf-python", + // installType: "version", + // }); + // }`, + // ePoint: `python --version`, + // }, +]); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile new file mode 100644 index 00000000..af549eb7 --- /dev/null +++ b/tests/test.Dockerfile @@ -0,0 +1,61 @@ +ARG DENO_V=1.38.4 + +FROM docker.io/denoland/deno:debian-${DENO_V} + +ARG FISH_V=3.6.0-3.1 +ARG ZSH_V=5.9-4+b2 +ARG GIT_V=1:2.39.2-1.1 +ARG CURL_V=7.88.1-10+deb12u4 +ARG XZ_V=5.4.1-0.2 +ARG UNZIP_V=6.0-28 + +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt update; \ + apt install --yes --no-install-recommends \ + # test deps + fish=$FISH_V \ + zsh=$ZSH_V \ + # asdf deps + git=$GIT_V \ + curl=$CURL_V \ + xz-utils=$XZ_V \ + unzip=$UNZIP_V \ + ca-certificates \ + ;\ + apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; + +WORKDIR /ghjk + +COPY deno.lock ./ +COPY deps/* ./deps/ +RUN deno cache deps/* +COPY . ./ +RUN ln -s ./main.ts /bin/ghjk + +WORKDIR /app + +ENV GHJK_EXE_INSTALL_DIR=/usr/bin +# explicitly set the shell var as detection fails otherwise +# because ps program is not present in this image +RUN SHELL=/bin/bash deno run -A /ghjk/install.ts +RUN SHELL=/bin/fish deno run -A /ghjk/install.ts +RUN SHELL=/bin/zsh deno run -A /ghjk/install.ts + +RUN cat > ghjk.ts < Promise); + envs?: Record; + ePoint: string; +}; + +export async function dockerE2eTest(cases: DockerE2eTestCase[]) { + // const socket = Deno.env.get("DOCKER_SOCK") ?? "/var/run/docker.sock"; + // const docker = new Docker(socket); + const dockerCmd = (Deno.env.get("DOCKER_CMD") ?? "docker").split(/\s/); + const dFileTemplate = await Deno.readTextFile( + import.meta.resolve("./test.Dockerfile").slice(6), + ); + const templateStrings = { + addConfig: `#{{CMD_ADD_CONFIG}}`, + }; + const defaultEnvs: Record = {}; + + for (const { name, envs: testEnvs, confFn, ePoint, imports } of cases) { + Deno.test(`dockerTest - ${name}`, async () => { + const tag = `ghjk_test_${name}`; + const env = { + ...defaultEnvs, + ...testEnvs, + }; + const configFile = `export { ghjk } from "/ghjk/mod.ts"; +${imports.replaceAll("$ghjk", "/ghjk")} + +await (${confFn.toString()})()`; + + const dFile = dFileTemplate.replaceAll( + templateStrings.addConfig, + configFile, + ); + await spawn([ + ...dockerCmd, + "buildx", + "build", + "--tag", + tag, + "--network=host", + // add to images list + "--output", + "type=docker", + "-f-", + ".", + ], { env, pipeInput: dFile }); + for (const shell of ["bash", "fish", "zsh"]) { + await spawn([ + ...dockerCmd, + "run", + "--rm", + ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) + .flat(), + tag, + shell, + "-c", + ePoint, + ], { env }); + } + await spawn([ + ...dockerCmd, + "rmi", + tag, + ]); + }); + } +} diff --git a/tools/jco.ts b/tools/jco.ts deleted file mode 100644 index 42b41344..00000000 --- a/tools/jco.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - DownloadEnv, - ExecPathEnv, - InstallEnv, - ListAllEnv, - ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; - -export function jco() { - return new class extends Tool { - name = "jco"; - dependencies = ["node"]; - - execEnv(env: ExecPathEnv) { - throw new Error("Method not implemented."); - return {}; - } - - listBinPaths(env: ListBinPathsEnv) { - throw new Error("Method not implemented."); - return {}; - } - - listAll(env: ListAllEnv) { - const pkg = "@bytecodealliance/jco"; - const metadataRequest = await fetch(`https://registry.npmjs.org/${pkg}`); - const metadata = await metadataRequest.json(); - - const versions = Object.keys(metadata.versions); - versions.sort(); - - console.log(versions); - return versions; - } - - download(env: DownloadEnv) { - throw new Error("Method not implemented."); - } - - install(env: InstallEnv) { - /* - npm install -g @bytecodealliance/jco - or - -PACKAGE=@bytecodealliance/jco -PACKAGE_INTERNAL=jco -BIN="jco" -BIN_PATH="${ASDF_INSTALL_PATH}/src/jco.js" - -if [[ "${ASDF_INSTALL_TYPE:-version}" == 'ref' ]]; then - echo >&2 "⛔ This plugin does not support installing by ref." - exit 1 -fi - -TARBALL_URL="https://registry.npmjs.org/${PACKAGE}/-/${PACKAGE_INTERNAL}-${ASDF_INSTALL_VERSION}.tgz" -echo "Downloading ${PACKAGE} v${ASDF_INSTALL_VERSION} from ${TARBALL_URL}" - -mkdir -p "${ASDF_INSTALL_PATH}" -curl --silent --fail --show-error --location "${TARBALL_URL}" | - tar xzf - --strip-components=1 --no-same-owner -C "${ASDF_INSTALL_PATH}" - -chmod +x "${BIN_PATH}" -mkdir -p "${ASDF_INSTALL_PATH}/bin" -ln -sf "${BIN_PATH}" "${ASDF_INSTALL_PATH}/bin/${BIN}" - */ - throw new Error("Method not implemented."); - } - }(); -} diff --git a/tools/node.ts b/tools/node.ts deleted file mode 100644 index 252f260e..00000000 --- a/tools/node.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - DownloadEnv, - ExecPathEnv, - InstallEnv, - ListAllEnv, - ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; -import { runOrExit } from "../cli/utils.ts"; - -export function node({ version }: { version: string }) { - return new class extends Tool { - name = "node"; - dependencies = []; - - execEnv(env: ExecPathEnv) { - return { - NODE_PATH: env.ASDF_INSTALL_PATH, - }; - } - - listBinPaths(env: ListBinPathsEnv) { - return { - "bin/node": "node", - "bin/npm": "npm", - "bin/npx": "npx", - }; - } - - listAll(env: ListAllEnv) { - const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); - const metadata = await metadataRequest.json(); - - const versions = metadata.map((v: any) => v.version); - versions.sort(); - - console.log(versions); - return versions; - } - - download(env: DownloadEnv) { - /* - const infoRequest = await fetch( - `https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-arm64.tar.gz`, - ); - Deno.writeFile( - "node-v21.1.0-darwin-arm64.tar.gz", - infoRequest.body!, - ); - */ - } - - async install(env: InstallEnv) { - await Deno.remove(env.ASDF_INSTALL_PATH, { recursive: true }); - await runOrExit(["tar", "-xzf", "node-v21.1.0-darwin-arm64.tar.gz"]); - await Deno.rename( - "node-v21.1.0-darwin-arm64", - env.ASDF_INSTALL_PATH, - ); - } - }(); -} diff --git a/tools/rust.ts b/tools/rust.ts deleted file mode 100644 index 4f38461d..00000000 --- a/tools/rust.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - DownloadEnv, - ExecPathEnv, - InstallEnv, - ListAllEnv, - ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; - -export function rust({ version }: { version: string }) { - return new class extends Tool { - name = "rust"; - dependencies = []; - - execEnv(env: ExecPathEnv) { - throw new Error("Method not implemented."); - return {}; - } - - listBinPaths(env: ListBinPathsEnv) { - throw new Error("Method not implemented."); - return {}; - } - - listAll(env: ListAllEnv) { - throw new Error("Method not implemented."); - return []; - } - - download(env: DownloadEnv) { - throw new Error("Method not implemented."); - } - - install(env: InstallEnv) { - throw new Error("Method not implemented."); - } - }(); -} diff --git a/utils/logger.ts b/utils/logger.ts new file mode 100644 index 00000000..4322020a --- /dev/null +++ b/utils/logger.ts @@ -0,0 +1,79 @@ +import { log, std_fmt_colors, std_path, std_url } from "../deps/common.ts"; + +// TODO: consult GHJK_LOG variable +export default function logger( + name: ImportMeta | string = self.name, +) { + if (typeof name === "object") { + name = std_url.basename(name.url); + name = name.replace(std_path.extname(name), ""); + } + return log.getLogger(name); +} + +function formatter(lr: log.LogRecord) { + const loggerName = lr.loggerName !== "default" ? " " + lr.loggerName : ""; + let msg = `[${lr.levelName}${loggerName}] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + + return msg; +} + +export function setup() { + log.setup({ + handlers: { + console: new ConsoleErrHandler("NOTSET"), + }, + + loggers: { + default: { + level: "DEBUG", + handlers: ["console"], + }, + [self.name]: { + level: "DEBUG", + handlers: ["console"], + }, + }, + }); +} + +export class ConsoleErrHandler extends log.handlers.BaseHandler { + constructor( + levelName: log.LevelName, + options: log.HandlerOptions = { formatter }, + ) { + super(levelName, options); + } + override log(msg: string): void { + console.error(msg); + } + override format(logRecord: log.LogRecord): string { + let msg = super.format(logRecord); + + switch (logRecord.level) { + case log.LogLevels.INFO: + msg = std_fmt_colors.green(msg); + break; + case log.LogLevels.WARNING: + msg = std_fmt_colors.yellow(msg); + break; + case log.LogLevels.ERROR: + msg = std_fmt_colors.red(msg); + break; + case log.LogLevels.CRITICAL: + msg = std_fmt_colors.bold(std_fmt_colors.red(msg)); + break; + case log.LogLevels.DEBUG: + msg = std_fmt_colors.dim(msg); + break; + default: + break; + } + + return msg; + } +} diff --git a/utils/mod.ts b/utils/mod.ts new file mode 100644 index 00000000..2de7f445 --- /dev/null +++ b/utils/mod.ts @@ -0,0 +1,226 @@ +import { dax, std_fs, std_path } from "../deps/common.ts"; +import logger from "./logger.ts"; +import type { + DepShims, + InstallConfig, + PortDep, +} from "../modules/ports/types.ts"; + +export function dbg(val: T) { + logger().debug("inline", val); + return val; +} + +export class ChildError extends Error { + constructor( + public code: number, + public output: string, + ) { + super(`ChildError - ${code} - ${output}`); + } +} + +export type SpawnOptions = { + cwd?: string; + env?: Record; + pipeInput?: string; + // pipeOut?: WritableStream; + // pipeErr?: WritableStream; +}; + +/// This is deprecated, please use the dax lib +// as exposed from this module by the `$` object +export async function spawn( + cmd: string[], + options: SpawnOptions = {}, +) { + const { cwd, env, pipeInput } = { + ...options, + }; + logger().debug("spawning", cmd); + const child = new Deno.Command(cmd[0], { + args: cmd.slice(1), + cwd, + ...(pipeInput + ? { + stdin: "piped", + } + : {}), + env, + }).spawn(); + + if (pipeInput) { + const writer = child.stdin.getWriter(); + await writer.write(new TextEncoder().encode(pipeInput)); + writer.releaseLock(); + await child.stdin.close(); + } + const { code, success } = await child.status; + if (!success) { + throw new Error(`child failed with code ${code}`); + } +} + +export async function spawnOutput( + cmd: string[], + options: Omit = {}, +): Promise { + const { cwd, env } = { + ...options, + }; + logger().debug("spawning", cmd); + const child = new Deno.Command(cmd[0], { + args: cmd.slice(1), + cwd, + stdout: "piped", + stderr: "piped", + env, + }).spawn(); + + const { code, success, stdout, stderr } = await child.output(); + if (!success) { + throw new Error( + `child failed with code ${code} - ${new TextDecoder().decode(stderr)}`, + ); + } + return new TextDecoder().decode(stdout); +} + +export function pathWithDepShims( + depShims: DepShims, +) { + const set = new Set(); + for (const [_, bins] of Object.entries(depShims)) { + for (const [_, binPath] of Object.entries(bins)) { + set.add(std_path.dirname(binPath)); + } + } + return `${[...set.keys()].join(":")}:${Deno.env.get("PATH")}`; +} + +export function depBinShimPath( + dep: PortDep, + binName: string, + depShims: DepShims, +) { + const shimPaths = depShims[dep.id]; + if (!shimPaths) { + throw new Error(`unable to find shims for dep ${dep.id}`); + } + const path = shimPaths[binName]; + if (!path) { + throw new Error( + `unable to find shim path for bin "${binName}" of dep ${dep.id}`, + ); + } + return path; +} + +export function getInstallId(install: InstallConfig) { + if ("pluginRepo" in install) { + const url = new URL(install.pluginRepo); + const pluginId = `${url.hostname}-${url.pathname.replaceAll("/", ".")}`; + return `asdf-${pluginId}`; + } + return install.portName; +} + +export const $ = dax.build$( + {}, +); +$.setPrintCommand(true); + +export function inWorker() { + return typeof WorkerGlobalScope !== "undefined" && + self instanceof WorkerGlobalScope; +} + +let colorEnvFlagSet = false; +Deno.permissions.query({ + name: "env", + variable: "CLICOLOR_FORCE", +}).then((perm) => { + if (perm.state == "granted") { + const val = Deno.env.get("CLICOLOR_FORCE"); + colorEnvFlagSet = !!val && val != "0" && val != "false"; + } +}); + +export function isColorfulTty(outFile = Deno.stdout) { + if (colorEnvFlagSet) { + return true; + } + if (Deno.isatty(outFile.rid)) { + const { columns } = Deno.consoleSize(); + return columns > 0; + } + return false; +} + +export async function findConfig(path: string) { + let current = path; + while (current !== "/") { + const location = `${path}/ghjk.ts`; + if (await std_fs.exists(location)) { + return location; + } + current = std_path.dirname(current); + } + return null; +} + +export function envDirFromConfig(config: string) { + const { shareDir } = dirs(); + return std_path.resolve( + shareDir, + "envs", + std_path.dirname(config).replaceAll("/", "."), + ); +} + +export function home_dir(): string | null { + switch (Deno.build.os) { + case "linux": + case "darwin": + return Deno.env.get("HOME") ?? null; + case "windows": + return Deno.env.get("USERPROFILE") ?? null; + default: + return null; + } +} + +export function dirs() { + const home = home_dir(); + if (!home) { + throw new Error("cannot find home dir"); + } + return { homeDir: home, shareDir: `${home}/.local/share/ghjk` }; +} + +export const AVAIL_CONCURRENCY = Number.parseInt( + Deno.env.get("DENO_JOBS") ?? "1", +); + +if (Number.isNaN(AVAIL_CONCURRENCY)) { + throw new Error(`Value of DENO_JOBS is NAN: ${Deno.env.get("DENO_JOBS")}`); +} + +export async function importRaw(spec: string) { + const url = new URL(spec); + if (url.protocol == "file:") { + return await Deno.readTextFile(url.pathname); + } + if (url.protocol.match(/^http/)) { + const resp = await fetch(url); + if (!resp.ok) { + throw new Error( + `error importing raw using fetch from ${spec}: ${resp.status} - ${resp.statusText}`, + ); + } + return await resp.text(); + } + throw new Error( + `error importing raw from ${spec}: unrecognized protocol ${url.protocol}`, + ); +} diff --git a/utils/unarchive.ts b/utils/unarchive.ts new file mode 100644 index 00000000..6172fcc5 --- /dev/null +++ b/utils/unarchive.ts @@ -0,0 +1,127 @@ +import { + Foras, + std_fs, + std_io, + std_path, + std_streams, + std_tar, + zipjs, +} from "../deps/ports.ts"; + +/// Uses file extension to determine type +/// Does not support symlinks +export async function unarchive( + path: string, + dest = "./", + ext = std_path.extname(path), +) { + switch (ext) { + case ".gz": + case ".tar.gz": + case ".tgz": + await untgz(path, dest); + break; + case ".tar": + await untar(path, dest); + break; + case ".zip": + await unzip(path, dest); + break; + default: + throw Error("unsupported archive extension: ${ext}"); + } +} + +export async function untgz( + path: string, + dest = "./", +) { + // FIXME: replace Foras with zip.js from below if possible + // this unzips the whole thing into memory first + // but I was not able to figure out the + await Foras.initBundledOnce(); + const tgzFile = await Deno.open(path, { read: true }); + const gzDec = new Foras.GzDecoder(); + await std_streams.copy(tgzFile, { + write(buf) { + const mem = new Foras.Memory(buf); + gzDec.write(mem); + mem.freeNextTick(); + return Promise.resolve(buf.length); + }, + }); + const buf = gzDec.finish().copyAndDispose(); + await Deno.writeFile("/tmp/my.tar", buf); + await untarReader(new std_io.Buffer(buf), dest); +} +export async function untar( + path: string, + dest = "./", +) { + const tarFile = await Deno.open(path, { + read: true, + }); + + try { + await untarReader(tarFile, dest); + } catch (err) { + throw err; + } finally { + tarFile.close(); + } +} + +/// This does not close the reader +export async function untarReader( + reader: Deno.Reader, + dest = "./", +) { + for await (const entry of new std_tar.Untar(reader)) { + const filePath = std_path.resolve(dest, entry.fileName); + if (entry.type === "directory") { + await std_fs.ensureDir(filePath); + continue; + } + await std_fs.ensureDir(std_path.dirname(filePath)); + const file = await Deno.open(filePath, { + create: true, + truncate: true, + write: true, + mode: entry.fileMode, + }); + await std_streams.copy(entry, file); + file.close(); + } +} + +export async function unzip( + path: string, + dest = "./", +) { + const zipFile = await Deno.open(path, { read: true }); + const zipReader = new zipjs.ZipReader(zipFile.readable); + try { + await Promise.allSettled( + (await zipReader.getEntries()).map(async (entry) => { + const filePath = std_path.resolve(dest, entry.filename); + if (entry.directory) { + await std_fs.ensureDir(filePath); + return; + } + await std_fs.ensureDir(std_path.dirname(filePath)); + const file = await Deno.open(filePath, { + create: true, + truncate: true, + write: true, + mode: entry.externalFileAttribute >> 16, + }); + if (!entry.getData) throw Error("impossible"); + await entry.getData(file.writable); + }), + ); + } catch (err) { + throw err; + } finally { + zipReader.close(); + } +}