diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5c09652d..019d7a14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,14 +29,25 @@ jobs: - uses: pre-commit/action@v3.0.0 test-e2e: - runs-on: ubuntu-latest + runs-on: "${{ matrix.os }}" + strategy: + matrix: + include: + - runs-on: ubuntu-latest + e2eType: "docker" + - os: macos-latest + e2eType: "local" + # - os: windows-latest + # e2eType: "local" 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 + - if: "${{ matrix.e2eType == 'docker' }}" + uses: docker/setup-buildx-action@v3 + - if: "${{ matrix.e2eType == 'docker' }}" + uses: actions-hub/docker/cli@master env: SKIP_LOGIN: true - run: deno task test diff --git a/action.yml b/action.yml index 8a047137..7ab9bb29 100644 --- a/action.yml +++ b/action.yml @@ -1,12 +1,16 @@ name: 'Setup Ghjk' description: 'Installs ghjk and optionally syncs according to the config' inputs: - installer-uri: - description: 'Alternative installer script to use' + version: + description: 'Github version/branch to install from' 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' + default: 'v0.1.0-alpha' + # 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 diff --git a/deno.jsonc b/deno.jsonc index a883c5e4..565811e3 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,7 +1,7 @@ { "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/*", + "test": "deno test --fail-fast --parallel --unstable -A 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 index c255f3d3..51b5ae15 100644 --- a/deno.lock +++ b/deno.lock @@ -3,7 +3,20 @@ "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" + "https://deno.land/x/graphql_request/mod.ts": "https://deno.land/x/graphql_request@v4.1.0/mod.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/mod.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/mod.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/act.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/act.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/asdf.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/asdf.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/cargo-insta.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/cargo-insta.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/earthly.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/earthly.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/jco.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/jco.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/mold.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/mold.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/pnpm.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/pnpm.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/protoc.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/protoc.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-opt.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-opt.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-tools.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-tools.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasmedge.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasmedge.ts", + "https://raw.github.com/metatypedev/ghjk/v0.1.0-alpha/ports/whiz.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/whiz.ts" }, "remote": { "https://deno.land/std@0.129.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", @@ -680,6 +693,43 @@ "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" + "https://esm.sh/v135/is-plain-obj@4.1.0/denonext/is-plain-obj.mjs": "d3d86a7174ad7935de7b00f904b6424c103bce530c502efb7f42114cbb1a555f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/deps/cli.ts": "6b3e3892e3373982aed490c3e70bb6f2780ad7fd59d14af38f61a9741ca48313", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/deps/common.ts": "b0cf740a4dd06b5a75e1135652a51325ffd203c5b741e6586f78417ab0c971c1", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/deps/ports.ts": "4f7588d0a8cc43ea7d8e699ccddabf55b7e5a5f412e9c9aed934927334990825", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/mod.ts": "7d11384072e5f4951a7205e52c4bc5f4114f14bf2e4e5c356b7305ce1a497a02", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/mod.ts": "f067464d56df9a3f65bf8fb0b2eae1ebff35c6e1b9eec9526c8aa6e796ed9c9f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/ambient.ts": "fa81ee853cf15e8c5d597fc8089ae93a85ded53ba1010144119ac1324c6f04cb", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/asdf.ts": "de9da4606e8132fcf7b746249df316cc5ff1dc22ac9f12ca0b307233de56548b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/mod.ts": "9f7ffac4d2fe260c3323e697a6c95721afd4e29c5440652c76bf8c02ad613b26", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/std.ts": "581e3ffb9d02ea74ef612bac78ed3ae2f98e83ee47743da59aaefa5d4595563e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/sync.ts": "36396e3fd65f6281c8d9d9e4f3fc0f0308a206b5a64c416c925a010545340f2b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/types.ts": "a90db021bd6f1c6a2e7a28272c9a44931695e7315de43004c5912517a95eed61", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/ports/worker.ts": "58808257f72b43f2f46fbc06a01e1d16d010b8aeb6fe2115ab4cf7fcd0772fa4", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/std.ts": "802dfeb8a9d2a228efb21c3cb5fb1d93fb0a04fc19b8780d0a1d49082416d242", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/modules/types.ts": "f8f6ba9dad92a43b25f3cfd45b5fe6b2024f384bec66a19b59e8e4df3cf92c41", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/port.ts": "6bbaa05ec814b52c85d47361b940595a0e53fa072beb35f536b0eab343ade73e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/act.ts": "abaa3470806fb22f6ec8f001682d330421b051e7f866c053c1bc139eb5aef683", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/asdf.ts": "ef0289c4975adabc55d68742b3faf7485ad0597f98be3209a1eebc166df1bbbe", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/cargo-binstall.ts": "dc15a935de4d52a0a74b199e88b8a4ccec07814b71099d2dc30d5f2c1ea14c7d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/cargo-insta.ts": "3e78ae521c01319ba93debccf802223a53aed5bd7346d4f4b63c9d40d53e02be", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/curl.ts": "51e9b6a9fca8c143f0891a116b5d419abafc2b1ab9b8fd6dd554cf49b86c046c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/earthly.ts": "26905b01a2a4634c4c12b7e9ccb2c0128ba786d2fca18b6646821f8f193edd1d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/git.ts": "526c272fbc952badc0adc587eb87928cdcae56fee9175b3e1c723380556dc145", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/jco.ts": "929966ac92646e554ca59eddece64fb321a21b3ebec06a9f4e7a292f6190ad4f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/mold.ts": "b9c89b7c06b0053c08cd66b3575f8a439e60b4aebcf0e29c2b06000ffe4429ee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/node.ts": "1f67e1527ac77e5d58bb98850143edf7830c537a75b212691f7d257ca901e9be", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/pnpm.ts": "cada3428c32c630ae9fb8c09b3d18dcf5f3286ffca0abc27fab06620ca055640", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/protoc.ts": "5e41a1afd807db81bd777112d64476aa2f47bb3174af674a4ba25de6efab4dd2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/tar.ts": "443245883d07c3fc29f614ee94a4d398a8ea1d2d5dd4238536874a1973b7cc99", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/unzip.ts": "82ec32bc21934ba63fea6f74bc24812b8932315f5060f7500f881ea819272fbf", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-opt.ts": "5ddb2a1223260a1f56b6f8bf8ffd18c12f10ecb556c34f161502626796f7fb33", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasm-tools.ts": "7edabd0219fe9698916da9a7b8de5046bb859c216b27cb1381906a549714f227", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/wasmedge.ts": "4862d701577ed602c90e7d0a2f81e8f0447d12ba80293f72d7c6966f1131f186", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/ports/whiz.ts": "a3ce5ee7c274961515afd4e832db85e77c79a512e4f17605a7d0394842eda41d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/utils/logger.ts": "a85d11b0c5f24ee2d27fc9346c4c415abfebf0b734e7d6d64b63f3d3cce1991a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/utils/mod.ts": "599527b5217f9048c292c85238892eecf71e5380862b9bcd9176022e81a10993", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.1.0-alpha/utils/unarchive.ts": "12d648379b1cec5aaefae1b04f6bcbdbd3eb5d7999664a6d78550651c12c63e4" } } diff --git a/ghjk.ts b/ghjk.ts index 21d41439..2fc80651 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -24,7 +24,7 @@ import whiz from "./ports/whiz.ts"; // cargo_insta({}); // jco({}); // mold({}); -// act({}); +act({}); // asdf({ // pluginRepo: "https://github.com/asdf-community/asdf-cmake", // installType: "version", diff --git a/host/mod.ts b/host/mod.ts index 0b368068..23c2c871 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -1,24 +1,20 @@ -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 logger, { isColorfulTty } from "../utils/logger.ts"; // import { $ } from "../utils/mod.ts"; -import { envDirFromConfig, findConfig, isColorfulTty } from "../utils/mod.ts"; +import { envDirFromConfig } 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); +export interface MainArgs { + ghjkDir: string; + configPath: string; +} +export async function main(args: MainArgs) { + const { configPath, ghjkDir } = args; + const envDir = envDirFromConfig(ghjkDir, configPath); logger().debug({ configPath }); logger().debug({ envDir }); diff --git a/install.ts b/install.ts index e23db5f9..fd50cd50 100644 --- a/install.ts +++ b/install.ts @@ -1,9 +1,26 @@ -//! Setup ghjk for the CWD +//! Install ghjk for the current user -import { install } from "./install/mod.ts"; +import "./setup_logger.ts"; +import { defaultInstallArgs, detectShell, install } from "./install/mod.ts"; if (import.meta.main) { - await install(); + const skipBinInstall = Deno.env.get("GHJK_INSTALL_SKIP_EXE"); + await install({ + ...defaultInstallArgs, + ghjkDir: Deno.env.get("GHJK_DIR") ?? defaultInstallArgs.ghjkDir, + skipExecInstall: skipBinInstall == "0" || skipBinInstall == "false", + shellsToHook: Deno.env.get("GHJK_INSTALL_HOOK_SHELLS") + ?.split(",") + ?.map((str) => str.trim()) + ?.filter((str) => str.length > 0) ?? + [ + await detectShell(), + ], + ghjkExecInstallDir: Deno.env.get("GHJK_INSTALL_EXE_DIR") ?? + defaultInstallArgs.ghjkExecInstallDir, + shellHookMarker: Deno.env.get("GHJK_INSTALL_HOOK_MARKER") ?? + defaultInstallArgs.shellHookMarker, + }); } 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 index ff44a681..3017b26a 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -10,16 +10,16 @@ ansi_yel='\033[0;33m' ansi_nc='\033[0m' # No Color init_ghjk() { - if [ -n "${GHJK_CLEANUP+x}" ]; then - eval "$GHJK_CLEANUP" + if [ -n "${GHJK_CLEANUP_POSIX+x}" ]; then + eval "$GHJK_CLEANUP_POSIX" fi - unset GHJK_CLEANUP + unset GHJK_CLEANUP_POSIX 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 '/' '.')" + envDir="__GHJK_DIR__/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') @@ -54,9 +54,8 @@ export LAST_PWD="$PWD" 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")) ] + [ -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 diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index a12e3b9a..6068ab8b 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -1,14 +1,14 @@ function init_ghjk - if set --query GHJK_CLEANUP - eval $GHJK_CLEANUP + if set --query GHJK_CLEANUP_FISH + eval $GHJK_CLEANUP_FISH end - set --erase GHJK_CLEANUP + set --erase GHJK_CLEANUP_FISH 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) + set --local envDir __GHJK_DIR__/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') @@ -32,14 +32,18 @@ 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 + if test ! $ghjk_prompt_init_flag; and set --query GHJK_LAST_LOADER_PATH; and test (stat -c "%Y" $GHJK_LAST_LOADER_PATH | tr -d '\n') != $GHJK_LAST_LOADER_TS init_ghjk + set -g ghjk_prompt_init_flag true end + set -g ghjk_prompt_init_flag false end # try to detect ghjk.ts on each change of PWD function ghjk_pwd_hook --on-variable PWD init_ghjk + # set the flag so that the prompt hook doesn't load it again + set -g ghjk_prompt_init_flag true end # try loading any relevant ghjk.ts right away diff --git a/install/mod.ts b/install/mod.ts index 5833acdb..412fab1b 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -3,11 +3,9 @@ // 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"; +import { $, dirs, importRaw } from "../utils/mod.ts"; // null means it should be removed (for cleaning up old versions) const getHooksVfs = async () => ({ @@ -29,34 +27,36 @@ const getHooksVfs = async () => ({ ), }); -async function detectShell(): Promise { +export async function detectShell(): Promise { let path = Deno.env.get("SHELL"); if (!path) { try { - path = await spawnOutput([ - "ps", - "-p", - String(Deno.ppid), - "-o", - "comm=", - ]); + path = await $`ps -p ${Deno.ppid} -o comm=`.text(); } 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)) { +async function unpackVFS( + vfs: Record, + baseDir: string, + replacements: [RegExp, string][], +): Promise { + await $.path(baseDir).ensureDir(); + + for (const [subpath, content] of Object.entries(vfs)) { const path = std_path.resolve(baseDir, subpath); if (content === null) { - await Deno.remove(path); + await $.path(baseDir).remove({ recursive: true }); } else { - await Deno.mkdir(std_path.dirname(path), { recursive: true }); - await Deno.writeTextFile(path, content.trim()); + let text = content.trim(); + for (const [re, repl] of replacements) { + text = text.replace(re, repl); + } + await $.path(std_path.dirname(path)).ensureDir(); + await $.path(path).writeText(text); } } } @@ -91,54 +91,111 @@ async function filterAddFile( await Deno.writeTextFile(path, lines.join("\n")); } -export async function install() { +export interface InstallArgs { + homeDir: string; + ghjkDir: string; + shellsToHook: string[]; + /// The mark used when adding the hook to the user's shell rcs + /// Override t + shellHookMarker: string; + /// The ghjk bin is optional, one can always invoke it + /// using `deno run --flags uri/to/ghjk/main.ts`; + skipExecInstall: boolean; + /// The directory in which to install the ghjk exec + /// Preferrably, one that's in PATH + ghjkExecInstallDir: string; + /// the deno exec to be used by the ghjk executable + /// by default will be "deno" i.e. whatever the shell resolves that to + ghjkExecDenoExec: string; +} + +export const defaultInstallArgs: InstallArgs = { + ghjkDir: std_path.resolve(dirs().shareDir, "ghjk"), + homeDir: dirs().homeDir, + shellsToHook: [], + shellHookMarker: "ghjk-hook-default", + skipExecInstall: true, + // TODO: respect xdg dirs + ghjkExecInstallDir: std_path.resolve(dirs().homeDir, ".local", "bin"), + ghjkExecDenoExec: "deno", +}; +export async function install( + args: InstallArgs = defaultInstallArgs, +) { + logger().debug("installing", args); 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 { ghjkDir } = args; + // const hookVfs = ; + logger().debug("unpacking vfs", { ghjkDir }); + await unpackVFS( + await getHooksVfs(), + ghjkDir, + [[/__GHJK_DIR__/g, ghjkDir]], + ); + for (const shell of args.shellsToHook) { + const { homeDir } = args; + if (shell === "fish") { + const rcPath = std_path.resolve(homeDir, ".config/fish/config.fish"); + logger().debug("installing hook", { + ghjkDir, + shell, + marker: args.shellHookMarker, + rcPath, + }); + await filterAddFile( + rcPath, + new RegExp(args.shellHookMarker, "g"), + `. ${ghjkDir}/env.fish # ${args.shellHookMarker}`, + ); + } else if (shell === "bash") { + const rcPath = std_path.resolve(homeDir, ".bashrc"); + logger().debug("installing hook", { + ghjkDir, + shell, + marker: args.shellHookMarker, + rcPath, + }); + await filterAddFile( + rcPath, + new RegExp(args.shellHookMarker, "g"), + `. ${ghjkDir}/env.sh # ${args.shellHookMarker}`, + ); + } else if (shell === "zsh") { + const rcPath = std_path.resolve(homeDir, ".zshrc"); + logger().debug("installing hook", { + ghjkDir, + shell, + marker: args.shellHookMarker, + rcPath, + }); + await filterAddFile( + rcPath, + new RegExp(args.shellHookMarker, "g"), + // NOTE: we use the posix hook for zsh as well + `. ${ghjkDir}/env.sh # ${args.shellHookMarker}`, + ); + } else { + throw new Error(`unsupported shell: ${shell}`); + } } - const skipBinInstall = Deno.env.get("GHJK_SKIP_EXE_INSTALL"); - if (!skipBinInstall && skipBinInstall != "0" && skipBinInstall != "false") { + if (!args.skipExecInstall) { 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`); + await std_fs.ensureDir(args.ghjkExecInstallDir); + const exePath = std_path.resolve(args.ghjkExecInstallDir, `ghjk`); logger().debug("installing executable", { exePath }); await Deno.writeTextFile( exePath, `#!/bin/sh -deno run --unstable-worker-options -A ${import.meta.resolve("../main.ts")} $*`, +${args.ghjkExecDenoExec} run --unstable-worker-options -A ${ + import.meta.resolve("../main.ts") + } $*`, { mode: 0o700 }, ); break; diff --git a/main.ts b/main.ts index 3433dc66..e200a4a3 100755 --- a/main.ts +++ b/main.ts @@ -1,9 +1,22 @@ #! /usr/bin/env -S deno run --unstable-worker-options -A - +import "./setup_logger.ts"; import { main } from "./host/mod.ts"; +import { std_path } from "./deps/common.ts"; +import logger from "./utils/logger.ts"; +import { dirs, findConfig } from "./utils/mod.ts"; if (import.meta.main) { - await main(); + const configPath = Deno.env.get("GHJK_CONFIG") ?? + await findConfig(Deno.cwd()); + if (!configPath) { + logger().error("ghjk did not find any `ghjk.ts` config."); + Deno.exit(2); + } + await main({ + ghjkDir: Deno.env.get("GHJK_DIR") ?? + std_path.resolve(dirs().shareDir, "ghjk"), + configPath: std_path.resolve(Deno.cwd(), configPath), + }); } else { throw new Error( "unexpected ctx: if you want to run the ghjk cli, import `main` from ./host/mod.ts", diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 2628e055..ccefe905 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -31,7 +31,7 @@ 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}%:}"'; + `GHJK_CLEANUP_POSIX+='${k}=$(echo "$${k}" | tr ":" "\\n" | grep -vE "^${envDir}" | tr "\\n" ":");${k}="\${${k}%:}"'; ${k}="${v}:$${k}"; ` ), @@ -43,7 +43,7 @@ ${k}="${v}:$${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 --append GHJK_CLEANUP_FISH 'set --global --path ${k} (string match --invert --regex "^${envDir}" $${k});'; set --global --prepend ${k} ${v}; ` ), diff --git a/ports/wasmedge.ts b/ports/wasmedge.ts index 7fc779da..3f5cf512 100644 --- a/ports/wasmedge.ts +++ b/ports/wasmedge.ts @@ -69,6 +69,7 @@ export class Port extends PortBase { execEnv(args: ExecEnvArgs) { return { WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib"), + WASMEDGE_INCLUDE_DIR: std_path.resolve(args.installPath, "include"), }; } diff --git a/tests/e2e.ts b/tests/e2e.ts index 806aea1b..a2e6164a 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,7 +1,7 @@ -import { dockerE2eTest } from "./utils.ts"; +import { dockerE2eTest, E2eTestCase, localE2eTest } from "./utils.ts"; // order tests by download size to make failed runs less expensive -await dockerE2eTest([ +const cases: E2eTestCase[] = [ // 3 megs { name: "protoc", @@ -152,4 +152,20 @@ await dockerE2eTest([ // }`, // ePoint: `python --version`, // }, -]); +]; + +if (Deno.env.get("GHJK_E2E_TYPE") == "both") { + localE2eTest(cases); + await dockerE2eTest(cases); +} else if (Deno.env.get("GHJK_TEST_E2E_TYPE") == "local") { + localE2eTest(cases); +} else if ( + Deno.env.get("GHJK_TEST_E2E_TYPE") == "docker" || + !Deno.env.has("GHJK_TEST_E2E_TYPE") +) { + await dockerE2eTest(cases); +} else { + throw new Error( + `unexpected GHJK_TEST_E2E_TYPE: ${Deno.env.get("GHJK_TEST_E2E_TYPE")}`, + ); +} diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index af549eb7..f78b714a 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -35,12 +35,9 @@ 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 +ENV GHJK_INSTALL_EXE_DIR=/usr/bin +ENV GHJK_INSTALL_HOOK_SHELLS=fish,bash,zsh +RUN deno run -A /ghjk/install.ts RUN cat > ghjk.ts < Promise); @@ -9,7 +11,66 @@ export type DockerE2eTestCase = { ePoint: string; }; -export async function dockerE2eTest(cases: DockerE2eTestCase[]) { +export function localE2eTest(cases: E2eTestCase[]) { + const defaultEnvs: Record = {}; + for (const { name, envs: testEnvs, confFn, ePoint, imports } of cases) { + Deno.test(`localE2eTest - ${name}`, async () => { + const tmpDir = $.path( + await Deno.makeTempDir({ + prefix: "ghjk_test_localE2e_", + }), + ); + + const ghjkDir = await tmpDir.join("ghjk").ensureDir(); + await install({ + ...defaultInstallArgs, + skipExecInstall: false, + ghjkExecInstallDir: ghjkDir.toString(), + ghjkDir: ghjkDir.toString(), + shellsToHook: [], + }); + await tmpDir.join("ghjk.ts").writeText( + `export { ghjk } from "$ghjk/mod.ts"; +${imports} + +await (${confFn.toString()})()` + .replaceAll( + "$ghjk", + std_url.dirname(import.meta.resolve("../mod.ts")).href, + ), + ); + const env: Record = { + ...defaultEnvs, + ...testEnvs, + BASH_ENV: `${ghjkDir.toString()}/env.sh`, + ZDOTDIR: ghjkDir.toString(), + GHJK_DIR: ghjkDir.toString(), + }; + { + const confHome = await ghjkDir.join(".config").ensureDir(); + const fishConfDir = await confHome.join("fish").ensureDir(); + await fishConfDir.join("config.fish").createSymlinkTo( + ghjkDir.join("env.fish").toString(), + ); + env["XDG_CONFIG_HOME"] = confHome.toString(); + } + await $`${ghjkDir.join("ghjk").toString()} config` + .cwd(tmpDir.toString()) + .env(env); + await $`${ghjkDir.join("ghjk").toString()} ports sync` + .cwd(tmpDir.toString()) + .env(env); + for (const shell of ["bash -c", "fish -c", "zsh -c"]) { + await $.raw`env ${shell} '${ePoint}'` + .cwd(tmpDir.toString()) + .env(env); + } + await tmpDir.remove({ recursive: true }); + }); + } +} + +export async function dockerE2eTest(cases: E2eTestCase[]) { // 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/); @@ -22,7 +83,7 @@ export async function dockerE2eTest(cases: DockerE2eTestCase[]) { const defaultEnvs: Record = {}; for (const { name, envs: testEnvs, confFn, ePoint, imports } of cases) { - Deno.test(`dockerTest - ${name}`, async () => { + Deno.test(`dockerE2eTest - ${name}`, async () => { const tag = `ghjk_test_${name}`; const env = { ...defaultEnvs, diff --git a/utils/logger.ts b/utils/logger.ts index 4322020a..82eede96 100644 --- a/utils/logger.ts +++ b/utils/logger.ts @@ -16,7 +16,7 @@ function formatter(lr: log.LogRecord) { let msg = `[${lr.levelName}${loggerName}] ${lr.msg}`; lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; + msg += `, ${Deno.inspect(arg, { colors: isColorfulTty(), depth: 10 })}`; }); return msg; @@ -77,3 +77,25 @@ export class ConsoleErrHandler extends log.handlers.BaseHandler { return msg; } } + +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; +} diff --git a/utils/mod.ts b/utils/mod.ts index 2de7f445..77c6fd70 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -135,28 +135,6 @@ export function inWorker() { 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 !== "/") { @@ -169,12 +147,11 @@ export async function findConfig(path: string) { return null; } -export function envDirFromConfig(config: string) { - const { shareDir } = dirs(); +export function envDirFromConfig(ghjkDir: string, configPath: string) { return std_path.resolve( - shareDir, + ghjkDir, "envs", - std_path.dirname(config).replaceAll("/", "."), + std_path.dirname(configPath).replaceAll("/", "."), ); } @@ -195,7 +172,10 @@ export function dirs() { if (!home) { throw new Error("cannot find home dir"); } - return { homeDir: home, shareDir: `${home}/.local/share/ghjk` }; + return { + homeDir: home, + shareDir: std_path.resolve(home, ".local", "share"), + }; } export const AVAIL_CONCURRENCY = Number.parseInt(