From 6a7c25af0e57b2ea8ede539e6e85be9bfa831934 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:32:30 +0300 Subject: [PATCH 01/43] wip: docker based suite --- .dockerignore | 2 + .pre-commit-config.yaml | 12 +-- .vscode/settings.json | 2 +- cli/core/hooks.ts | 71 ++++++++++++++-- cli/sync.ts | 1 + cli/utils.ts | 48 ++++++++--- deno.jsonc | 6 ++ deno.lock | 181 ++++++++++++++++++++++++++++++++++++++++ play.ts | 5 ++ tests/dev_deps.ts | 0 tests/test.Dockerfile | 28 +++++++ tests/utils.ts | 85 +++++++++++++++++++ tools/node.ts | 20 ++--- 13 files changed, 424 insertions(+), 37 deletions(-) create mode 100644 .dockerignore create mode 100644 deno.jsonc create mode 100644 deno.lock create mode 100644 play.ts create mode 100644 tests/dev_deps.ts create mode 100644 tests/test.Dockerfile create mode 100644 tests/utils.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..32f8c123 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +.vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f15f5696..bd957993 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,12 +73,12 @@ repos: - ts - tsx files: ^website/ - - id: version - name: "Lock versions" - always_run: true - language: system - entry: bash -c 'deno run -A dev/lock.ts --check' - pass_filenames: false + # - id: version + # name: "Lock versions" + # always_run: true + # language: system + # entry: bash -c 'deno run -A dev/lock.ts --check' + # pass_filenames: false - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: diff --git a/.vscode/settings.json b/.vscode/settings.json index 47187f82..97c886e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,4 +17,4 @@ "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" } -} \ No newline at end of file +} diff --git a/cli/core/hooks.ts b/cli/core/hooks.ts index 0ef38c3e..b4dba930 100644 --- a/cli/core/hooks.ts +++ b/cli/core/hooks.ts @@ -8,10 +8,56 @@ 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)); +const mod = await import(Deno.args[0]); +mod.ghjk.runCli(Deno.args.slice(1)); `, + "hooks/hook.sh": ` +ghjk_already_run=false + +ghjk_hook() { + # Check if the trap has already executed + if [[ "$ghjk_already_run" = true ]]; then + return + fi + ghjk_already_run=true + if [[ -v GHJK_CLEANUP ]]; then + eval $GHJK_CLEANUP + unset GHJK_CLEANUP + fi + current_dir=$PWD + while [ "$current_dir" != "/" ]; do + if [ -e "$current_dir/ghjk.ts" ]; then + shim="$HOME/.local/share/ghjk/shims/$(echo "$current_dir" | tr '/' '.')" + if [ -d "$shim" ]; then + PATH="$shim:$(echo "$PATH" | grep -v "^$HOME\/\.local\/share\/ghjk\/shim")" + source "$shim/loader.fish" + if [ "$shim/loader.fish" -ot "$current_dir/ghjk.ts" ]; then + echo -e "\e[38;2;255;69;0m[ghjk] Detected changes, please sync...\e[0m" + fi + else + echo -e "\e[38;2;255;69;0m[ghjk] Uninstalled runtime found, please sync...\e[0m" + echo "$shim" + fi + alias ghjk="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $current_dir/ghjk.ts" + return + fi + cur_dir="$(dirname "$current_dir")" + done + alias ghjk "echo 'No ghjk.ts config found.'" +} + +trap 'ghjk_hook' DEBUG + +set_again() { + ghjk_already_run=false +} + +if [[ -n "$PROMPT_COMMAND" ]]; then + PROMPT_COMMAND+=";" +fi + +PROMPT_COMMAND+="set_again;" +`, "hooks/hook.fish": ` function ghjk_hook --on-variable PWD if set --query GHJK_CLEANUP @@ -55,10 +101,15 @@ async function detectShell(): Promise { "-o", "comm=", ]); - const path = parent.unwrapOrElse((e) => { - throw new Error(`cannot get parent process name: ${e}`); - }).trimEnd(); - + const path = parent + .unwrapOrElse((e) => { + const envShell = Deno.env.get("SHELL"); + if (!envShell) { + throw new Error(`cannot get parent process name: ${e}`); + } + return envShell; + }) + .trimEnd(); return basename(path, ".exe").toLowerCase(); } @@ -117,6 +168,12 @@ export async function install() { /\.local\/share\/ghjk\/hooks\/hook.fish/, "source $HOME/.local/share/ghjk/hooks/hook.fish", ); + } else if (shell === "bash") { + await filterAddFile( + resolve(homeDir, ".bashrc"), + /\.local\/share\/ghjk\/hooks\/hook.sh/, + "source $HOME/.local/share/ghjk/hooks/hook.sh", + ); } else { throw new Error(`unsupported shell: ${shell}`); } diff --git a/cli/sync.ts b/cli/sync.ts index 73d4cda8..6b5edea4 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -50,6 +50,7 @@ export class SyncCommand extends Command { // (beware of multiple versions of tools libs) // here, only showing what should happen after as an example + return; const nodeTool = node({ version: "v21.1.0" }); // build dag diff --git a/cli/utils.ts b/cli/utils.ts index ee740a03..f19d82de 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -1,33 +1,51 @@ import { Err, Ok, Result } from "./deps.ts"; +export function dbg(val: T) { + console.log("[dbg] ", val); + return val; +} + 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(); + try { + 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)); + return output.success + ? Ok(new TextDecoder().decode(output.stdout)) + : Err(new TextDecoder().decode(output.stderr)); + } catch (err) { + return Err(err.toString()); + } } export async function runOrExit( cmd: string[], - cwd?: string, - env: Record = Deno.env.toObject(), + options: { + cwd?: string; + env?: Record; + pipeInput?: string; + } = {}, ) { + const { cwd, env, pipeInput } = { + ...options, + env: options.env ?? Deno.env.toObject(), + }; + console.log(cmd); const p = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, stdout: "piped", stderr: "piped", + stdin: "piped", env, }).spawn(); @@ -35,10 +53,16 @@ export async function runOrExit( void p.stdout.pipeTo(Deno.stdout.writable, { preventClose: true }); void p.stderr.pipeTo(Deno.stderr.writable, { preventClose: true }); + if (pipeInput) { + const writer = p.stdin.getWriter(); + await writer.write(new TextEncoder().encode(pipeInput)); + await writer.close(); + } const { code, success } = await p.status; if (!success) { Deno.exit(code); } + //await p.stdin.close(); } function home_dir(): string | null { diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 00000000..f7810193 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,6 @@ +{ + "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 --unstable --allow-run=docker --allow-read --allow-env tests/*" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 00000000..2db3ecbf --- /dev/null +++ b/deno.lock @@ -0,0 +1,181 @@ +{ + "version": "3", + "remote": { + "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.205.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.205.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "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/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/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/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/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" + } +} diff --git a/play.ts b/play.ts new file mode 100644 index 00000000..f1d0f7a3 --- /dev/null +++ b/play.ts @@ -0,0 +1,5 @@ +import { runOrExit } from "./cli/utils.ts"; + +await runOrExit(["docker", "buildx", "build", "-t", "test", "-"], { + pipeInput: "RUN echo heyya", +}); diff --git a/tests/dev_deps.ts b/tests/dev_deps.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile new file mode 100644 index 00000000..ea84d76b --- /dev/null +++ b/tests/test.Dockerfile @@ -0,0 +1,28 @@ +FROM docker.io/denoland/deno:debian-1.38.0 + +ENV SHELL=/bin/bash + +WORKDIR /ghjk + +COPY deno.lock ./ +COPY tests/dev_deps.ts ./tests/ +COPY cli/deps.ts ./cli/ +RUN deno cache cli/deps.ts tests/dev_deps.ts +COPY . ./ +RUN deno run -A /ghjk/install.ts + +WORKDIR /app + +RUN cat > ghjk.ts < Promise; + envs?: Record; + epoint: string; +}; + + +async function dockerTest(cases: TestCase[]) { + // 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/cli/mod.ts"; +${imports.replaceAll("$ghjk", "/ghjk/")} + +await (${confFn.toString()})()`; + + const dFile = dfileTemplate.replaceAll( + templateStrings.addConfig, + configFile, + ); + await runOrExit([ + ...dockerCmd, + "buildx", + "build", + "-t", + tag, + "-f-", + ".", + ], { env, pipeInput: dFile }); + await runOrExit([ + ...dockerCmd, + "run", + "--rm", + "-v", + ".:/ghjk:ro", + ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) + .flat(), + tag, + ...epoint.split(/\s/), + ], { env }); + await runOrExit([ + ...dockerCmd, + "rmi", + tag, + ]); + }); + } +} + +await dockerTest([{ + name: "a", + imports: `import { node } from "$ghjk/tools/node.ts"`, + confFn: async () => { + // node({ version: "lts" }); + }, + epoint: `echo yes`, +},{ + name: "b", + imports: `import { node } from "$ghjk/tools/node.ts"`, + confFn: async () => { + // node({ version: "lts" }); + }, + epoint: `echo yes`, +}, +]); diff --git a/tools/node.ts b/tools/node.ts index 252f260e..b7d72aa4 100644 --- a/tools/node.ts +++ b/tools/node.ts @@ -27,7 +27,7 @@ export function node({ version }: { version: string }) { }; } - listAll(env: ListAllEnv) { + async listAll(env: ListAllEnv) { const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); const metadata = await metadataRequest.json(); @@ -38,16 +38,14 @@ export function node({ version }: { version: string }) { 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 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) { From 058a964a30a6ff0921f73cd72060bd21ad9c3270 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 10 Nov 2023 02:53:34 +0300 Subject: [PATCH 02/43] wip: worker based plugins --- .dockerignore | 1 + .gitignore | 1 + README.md | 6 ++ cli/cleanup.ts | 2 +- cli/{core => }/hooks.ts | 5 +- cli/list.ts | 16 +++- cli/mod.ts | 16 ++-- cli/outdated.ts | 2 +- cli/sync.ts | 22 ++++- cli/utils.ts | 6 +- core/logger.ts | 5 ++ core/mod.ts | 78 +++++++++++++++++ cli/core/tools.ts => core/types.ts | 75 ++++++++++++----- core/validators.ts | 25 ++++++ core/worker.ts | 130 +++++++++++++++++++++++++++++ deno.jsonc | 3 +- deno.lock | 62 +++++++++++++- cli/deps.ts => deps/cli.ts | 8 +- deps/common.ts | 6 ++ deps/dev.ts | 3 + deps/plug.ts | 3 + ghjk.ts | 3 +- install.ts | 2 +- mod.ts | 60 ++++++++++++- plug.ts | 34 ++++++++ {tools => plugs}/jco.ts | 6 +- {tools => plugs}/node.ts | 31 +++++-- {tools => plugs}/rust.ts | 6 +- tests/dev_deps.ts | 0 tests/test.Dockerfile | 5 +- tests/utils.ts | 26 +++--- 31 files changed, 566 insertions(+), 82 deletions(-) rename cli/{core => }/hooks.ts (97%) create mode 100644 core/logger.ts create mode 100644 core/mod.ts rename cli/core/tools.ts => core/types.ts (62%) create mode 100644 core/validators.ts create mode 100644 core/worker.ts rename cli/deps.ts => deps/cli.ts (81%) create mode 100644 deps/common.ts create mode 100644 deps/dev.ts create mode 100644 deps/plug.ts create mode 100644 plug.ts rename {tools => plugs}/jco.ts (95%) rename {tools => plugs}/node.ts (73%) rename {tools => plugs}/rust.ts (90%) delete mode 100644 tests/dev_deps.ts diff --git a/.dockerignore b/.dockerignore index 32f8c123..28a19e32 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .git .vscode +tests/ diff --git a/.gitignore b/.gitignore index e43b0f98..049aae8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +play.ts diff --git a/README.md b/README.md index 5eb8a5e6..5288f664 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,9 @@ and looks as follows (abstracting away some implementation details): - pnpm - mold({ if: Deno.build.os === "Macos" }) - hash verifiable dependencies (timestamp) + +## design considerations + +- keep interface with plugins KISS, open to consumption by others +- `ghjk.ts` scripts are executed in a secure sandbox and the plugins it declares + are each run in a separate worker and only given access to relevant dirs diff --git a/cli/cleanup.ts b/cli/cleanup.ts index e4a7f335..e6578314 100644 --- a/cli/cleanup.ts +++ b/cli/cleanup.ts @@ -1,4 +1,4 @@ -import { Command } from "./deps.ts"; +import { Command } from "../deps/cli.ts"; export class CleanupCommand extends Command { constructor() { diff --git a/cli/core/hooks.ts b/cli/hooks.ts similarity index 97% rename from cli/core/hooks.ts rename to cli/hooks.ts index b4dba930..fb3cb58e 100644 --- a/cli/core/hooks.ts +++ b/cli/hooks.ts @@ -1,5 +1,5 @@ -import { basename, dirname, resolve } from "../deps.ts"; -import { dirs, runAndReturn } from "../utils.ts"; +import { basename, dirname, resolve } from "../deps/cli.ts"; +import { dirs, runAndReturn } from "./utils.ts"; // null means it should be removed (for cleaning up old versions) const vfs = { @@ -9,6 +9,7 @@ console.log = (...args) => { log("[ghjk.ts]", ...args); }; const mod = await import(Deno.args[0]); +console.log = log; mod.ghjk.runCli(Deno.args.slice(1)); `, "hooks/hook.sh": ` diff --git a/cli/list.ts b/cli/list.ts index ab950a0d..ce23a543 100644 --- a/cli/list.ts +++ b/cli/list.ts @@ -1,12 +1,20 @@ -import { Command } from "./deps.ts"; +import { Command } from "../deps/cli.ts"; +import { type GhjkCtx } from "../core/mod.ts"; export class ListCommand extends Command { - constructor() { + constructor( + public cx: GhjkCtx, + ) { super(); this .description("") - .action(async () => { - console.log("list"); + .action(() => { + console.log( + cx.installs.map((install) => ({ + install, + plug: cx.plugs.get(install.plugName), + })), + ); }); } } diff --git a/cli/mod.ts b/cli/mod.ts index 372fb6c2..b9b00ac4 100644 --- a/cli/mod.ts +++ b/cli/mod.ts @@ -1,10 +1,12 @@ +import { Command, CommandResult, CompletionsCommand } from "../deps/cli.ts"; + 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"; +import { type GhjkCtx } from "../core/mod.ts"; -function runCli(args: string[]): Promise { +export function runCli(args: string[], cx: GhjkCtx): Promise { return new Command() .name("ghjk") .version("0.1.0") @@ -12,16 +14,10 @@ function runCli(args: string[]): Promise { .action(function () { this.showHelp(); }) - .command("sync", new SyncCommand()) - .command("list", new ListCommand()) + .command("sync", new SyncCommand(cx)) + .command("list", new ListCommand(cx)) .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 index fb6619fa..690a692a 100644 --- a/cli/outdated.ts +++ b/cli/outdated.ts @@ -1,4 +1,4 @@ -import { Command } from "./deps.ts"; +import { Command } from "../deps/cli.ts"; export class OutdatedCommand extends Command { constructor() { diff --git a/cli/sync.ts b/cli/sync.ts index 6b5edea4..cd338e39 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,5 +1,6 @@ -import { node } from "../tools/node.ts"; -import { Command, dirname, exists, resolve } from "./deps.ts"; +import { DenoWorkerPlugManifestX, GhjkCtx } from "../core/mod.ts"; +import { DenoWorkerPlug } from "../core/worker.ts"; +import { Command, dirname, exists, resolve } from "../deps/cli.ts"; import { dirs } from "./utils.ts"; async function findConfig(path: string): Promise { @@ -30,7 +31,9 @@ async function writeLoader(shim: string, env: Record) { } export class SyncCommand extends Command { - constructor() { + constructor( + public cx: GhjkCtx, + ) { super(); this .description("Syncs the runtime.") @@ -45,12 +48,23 @@ export class SyncCommand extends Command { const shim = shimFromConfig(config); console.log(shim); + for (const [name, manifest] of cx.plugs) { + if ("moduleSpecifier" in manifest) { + const plug = new DenoWorkerPlug( + manifest as DenoWorkerPlugManifestX, + ); + const versions = await plug.listAll({}); + console.log(name, { versions }); + plug.terminate(); + } + } // 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 return; + /* const nodeTool = node({ version: "v21.1.0" }); // build dag @@ -80,7 +94,7 @@ export class SyncCommand extends Command { // write shim if config changes or does not exists const env = await nodeTool.execEnv({ ASDF_INSTALL_PATH } as any); - await writeLoader(shim, env); + await writeLoader(shim, env);*/ }); } } diff --git a/cli/utils.ts b/cli/utils.ts index f19d82de..36c5b9db 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -1,4 +1,4 @@ -import { Err, Ok, Result } from "./deps.ts"; +import { Err, Ok, Result } from "../deps/cli.ts"; export function dbg(val: T) { console.log("[dbg] ", val); @@ -56,13 +56,13 @@ export async function runOrExit( if (pipeInput) { const writer = p.stdin.getWriter(); await writer.write(new TextEncoder().encode(pipeInput)); - await writer.close(); + writer.releaseLock(); } + await p.stdin.close(); const { code, success } = await p.status; if (!success) { Deno.exit(code); } - //await p.stdin.close(); } function home_dir(): string | null { diff --git a/core/logger.ts b/core/logger.ts new file mode 100644 index 00000000..578078fe --- /dev/null +++ b/core/logger.ts @@ -0,0 +1,5 @@ +import { log } from "../deps/common.ts"; + +export default function logger() { + return log.getLogger(self.name ?? "ghjk"); +} diff --git a/core/mod.ts b/core/mod.ts new file mode 100644 index 00000000..ce1d9b74 --- /dev/null +++ b/core/mod.ts @@ -0,0 +1,78 @@ +export * from "./types.ts"; +import { semver } from "../deps/common.ts"; +import { + type GhjkCtx, + type InstallConfig, + type PlugManifest, +} from "./types.ts"; +import validators from "./validators.ts"; +import logger from "./logger.ts"; + +export function registerPlug( + cx: GhjkCtx, + manifestUnclean: PlugManifest, +) { + const manifest = validators.plugManifestBase.parse(manifestUnclean); + const conflict = cx.plugs.get(manifest.name); + if (conflict) { + if ( + conflict.conflictResolution == "override" && + manifest.conflictResolution == "override" + ) { + throw Error( + `Two instances of plugin "${manifest.name}" found with ` + + `both set to "${manifest.conflictResolution}" conflictResolution"`, + ); + } else if (conflict.conflictResolution == "override") { + logger().debug("plug rejected due to override", { + retained: conflict, + rejected: manifest, + }); + // do nothing + } else if (manifest.conflictResolution == "override") { + logger().debug("plug override", { + new: manifest, + replaced: conflict, + }); + cx.plugs.set(manifest.name, manifest); + } else if ( + semver.compare(manifest.version, conflict.version) == 0 + ) { + throw Error( + `Two instances of the plug "${manifest.name}" found with an identical version` + + `and bothboth set to "deferToNewer" conflictResolution.`, + ); + } else if ( + semver.compare(manifest.version, conflict.version) > 0 + ) { + logger().debug("plug replaced after version defer", { + new: manifest, + replaced: conflict, + }); + cx.plugs.set(manifest.name, manifest); + } else { + logger().debug("plug rejected due after defer", { + retained: conflict, + rejected: manifest, + }); + } + } else { + logger().debug("plug registered", manifest); + cx.plugs.set(manifest.name, manifest); + } +} + +export function addInstall( + cx: GhjkCtx, + config: InstallConfig, +) { + if (!cx.plugs.has(config.plugName)) { + throw Error( + `unrecognized plug "${config.plugName}" specified by install ${ + JSON.stringify(config) + }`, + ); + } + logger().debug("install added", config); + cx.installs.push(config); +} diff --git a/cli/core/tools.ts b/core/types.ts similarity index 62% rename from cli/core/tools.ts rename to core/types.ts index 0bee9b6b..19ce7ea7 100644 --- a/cli/core/tools.ts +++ b/core/types.ts @@ -1,3 +1,58 @@ +import { zod } from "../deps/common.ts"; +import validators from "./validators.ts"; + +// Describes the plugin itself +export type PlugManifestBase = zod.input; + +export type DenoWorkerPlugManifest = zod.input< + typeof validators.denoWorkerPlugManifest +>; + +// Describes the plugin itself +export type PlugManifest = PlugManifestBase | DenoWorkerPlugManifest; + +export type PlugManifestBaseX = zod.infer; +export type DenoWorkerPlugManifestX = zod.infer< + typeof validators.denoWorkerPlugManifest +>; +// This is the transformed version of PlugManifest, ready for consumption +export type PlugManifestX = PlugManifestBaseX | DenoWorkerPlugManifestX; + +export type RegisteredPlugs = Map; + +export interface InstallConfigBase { + version?: string; +} + +// Describes a single installation done by a specific plugin. +export type InstallConfig = InstallConfigBase & { + plugName: string; +}; + +export interface GhjkCtx { + plugs: RegisteredPlugs; + installs: InstallConfig[]; +} + +export abstract class Plug { + 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; +} + interface ASDF_CONFIG_EXAMPLE { ASDF_INSTALL_TYPE: "version" | "ref"; ASDF_INSTALL_VERSION: string; // full version number or Git Ref depending on ASDF_INSTALL_TYPE @@ -10,7 +65,6 @@ interface ASDF_CONFIG_EXAMPLE { 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; @@ -34,22 +88,3 @@ 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/core/validators.ts b/core/validators.ts new file mode 100644 index 00000000..05f9b51d --- /dev/null +++ b/core/validators.ts @@ -0,0 +1,25 @@ +import { semver, zod } from "../deps/common.ts"; + +const plugManifestBase = zod.object({ + name: zod.string().min(1), + version: zod.string() + .refine((str) => semver.parse(str), { + message: "not a valid semver", + }) + .transform(semver.parse), + conflictResolution: zod + .enum(["deferToNewer", "override"]) + .nullish() + .default("deferToNewer"), +}).passthrough(); + +const denoWorkerPlugManifest = plugManifestBase.merge( + zod.object({ + moduleSpecifier: zod.string().url(), + }), +); + +export default { + plugManifestBase, + denoWorkerPlugManifest, +}; diff --git a/core/worker.ts b/core/worker.ts new file mode 100644 index 00000000..4d94a906 --- /dev/null +++ b/core/worker.ts @@ -0,0 +1,130 @@ +//// +/// + +import logger from "./logger.ts"; +import { + type DenoWorkerPlugManifestX, + type DownloadEnv, + type ExecPathEnv, + type InstallEnv, + type ListAllEnv, + type ListBinPathsEnv, + Plug, +} from "./types.ts"; + +type WorkerReq = + & { + ty: string; + id: string; + } + & ({ + ty: "listAll"; + arg: ListAllEnv; + } | { + ty: "download"; + } | { + ty: "install"; + }); + +type WorkerResp = + & { + id: string; + } + & ({ + ty: "listAll"; + payload: string[]; + } | { + ty: "download"; + }); + +/// Make sure to call this before any `await` point or your +/// plug might miss messages +export function denoWorkerPlug

(plug: P) { + if (self.name) { + self.onmessage = async (msg: MessageEvent) => { + const req = msg.data; + if (!req.ty) { + logger().error("invalid worker request", req); + throw Error("unrecognized worker request type"); + } + let res: WorkerResp; + if (req.ty == "listAll") { + res = { + ty: "listAll", + id: req.id, + payload: await plug.listAll(req.arg), + }; + } else { + logger().error("unrecognized worker request type", req); + throw Error("unrecognized worker request type"); + } + self.postMessage(res); + }; + } +} + +export class DenoWorkerPlug extends Plug { + name: string; + dependencies: string[]; + worker: Worker; + eventListenrs: Map void> = new Map(); + constructor(public manifest: DenoWorkerPlugManifestX) { + super(); + this.name = manifest.name; + this.dependencies = []; // TODO + this.worker = new Worker(manifest.moduleSpecifier, { + name: `${manifest.name}:${manifest.version}`, + type: "module", + }); + this.worker.onmessage = (evt: MessageEvent) => { + const res = evt.data; + if (!res.id) { + logger().error("invalid worker response", res); + throw Error("unrecognized worker request type"); + } + const listener = this.eventListenrs.get(res.id); + if (listener) { + listener(res); + } else { + logger().error("worker response has no listeners", res); + throw Error("recieved worker response has no listeners"); + } + }; + } + terminate() { + this.worker.terminate(); + } + async listAll(env: ListAllEnv): Promise { + const id = crypto.randomUUID(); + const req: WorkerReq = { + ty: "listAll", + id, + arg: env, + }; + const res = await new Promise((resolve) => { + this.eventListenrs.set(id, (res) => resolve(res)); + this.worker.postMessage(req); + }); + this.eventListenrs.delete(id); + if (res.ty == "listAll") { + return res.payload; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + execEnv( + env: ExecPathEnv, + ): Record | Promise> { + throw new Error("Method not implemented."); + } + listBinPaths( + env: ListBinPathsEnv, + ): Record | Promise> { + throw new Error("Method not implemented."); + } + download(env: DownloadEnv): void | Promise { + throw new Error("Method not implemented."); + } + install(env: InstallEnv): void | Promise { + throw new Error("Method not implemented."); + } +} diff --git a/deno.jsonc b/deno.jsonc index f7810193..806f38e0 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +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 --unstable --allow-run=docker --allow-read --allow-env tests/*" + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker --allow-read --allow-env tests/*", + "cache": "deno cache deps/*" } } diff --git a/deno.lock b/deno.lock index 2db3ecbf..f5a28b87 100644 --- a/deno.lock +++ b/deno.lock @@ -9,6 +9,8 @@ "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", "https://deno.land/std@0.205.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.205.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "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", @@ -22,6 +24,11 @@ "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", @@ -91,6 +98,46 @@ "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/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", @@ -176,6 +223,19 @@ "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/monads@v0.5.10/result/result.ts": "bb482b7b90949d3a67e78b4b0dd949774eccaa808df39ac83f6a585526edeb37", + "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" } } diff --git a/cli/deps.ts b/deps/cli.ts similarity index 81% rename from cli/deps.ts rename to deps/cli.ts index 25f39072..c46713ac 100644 --- a/cli/deps.ts +++ b/deps/cli.ts @@ -1,3 +1,7 @@ +//! dependencies used by the cli + +export * from "./common.ts"; + 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 { @@ -8,8 +12,6 @@ export { export { exists } from "https://deno.land/std@0.205.0/fs/mod.ts"; export { Command, + type CommandResult, 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/deps/common.ts b/deps/common.ts new file mode 100644 index 00000000..2c73b7c2 --- /dev/null +++ b/deps/common.ts @@ -0,0 +1,6 @@ +//! dependencies used by all + +export * from "./common.ts"; +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"; diff --git a/deps/dev.ts b/deps/dev.ts new file mode 100644 index 00000000..b3c0b3f2 --- /dev/null +++ b/deps/dev.ts @@ -0,0 +1,3 @@ +//! dependencies used by tests + +export * from "./common.ts"; diff --git a/deps/plug.ts b/deps/plug.ts new file mode 100644 index 00000000..d61da08f --- /dev/null +++ b/deps/plug.ts @@ -0,0 +1,3 @@ +//! This contains dependencies used by plugins + +export * from "./common.ts"; diff --git a/ghjk.ts b/ghjk.ts index 344495e9..945bacc7 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -14,5 +14,6 @@ await run(); */ export { ghjk } from "./mod.ts"; +import node from "./plugs/node.ts"; -console.log("config"); +node({}); diff --git a/install.ts b/install.ts index 95c4aca1..2bf06e97 100644 --- a/install.ts +++ b/install.ts @@ -1,5 +1,5 @@ // this is only a shortcut for the first install -import { install } from "./cli/core/hooks.ts"; +import { install } from "./cli/hooks.ts"; await install(); diff --git a/mod.ts b/mod.ts index 7559c433..641596a9 100644 --- a/mod.ts +++ b/mod.ts @@ -1,2 +1,60 @@ +//! This module is intended to be re-exported by `ghjk.ts` config scripts. Please +//! avoid importing elsewhere at it has side-ffects. + +import { log } from "./deps/common.ts"; + +import { type GhjkCtx } from "./core/mod.ts"; // this is only a shortcut for the cli -export * from "./cli/mod.ts"; +import { runCli } from "./cli/mod.ts"; +import logger from "./core/logger.ts"; + +declare global { + interface Window { + ghjk: GhjkCtx; + } +} + +self.ghjk = { + plugs: new Map(), + installs: [], +}; + +log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("DEBUG", { + formatter: (lr) => { + let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), + }, + + loggers: { + // configure default logger available via short-hand methods above. + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + plug: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); + +export const ghjk = { + runCli: (args: string[]) => runCli(args, self.ghjk), + cx: self.ghjk, +}; + +export { logger }; diff --git a/plug.ts b/plug.ts new file mode 100644 index 00000000..b1ccfdbd --- /dev/null +++ b/plug.ts @@ -0,0 +1,34 @@ +import { + addInstall, + type GhjkCtx, + type InstallConfig, + type PlugManifestBase, + registerPlug, +} from "./core/mod.ts"; + +export * from "./core/mod.ts"; +export { denoWorkerPlug } from "./core/worker.ts"; +export type * from "./core/mod.ts"; + +declare global { + interface Window { + ghjk: GhjkCtx; + } +} + +export function registerPlugGlobal( + manifestUnclean: PlugManifestBase, +) { + // make sure we're not running in a Worker first + if (!self.name) { + registerPlug(self.ghjk, manifestUnclean); + } +} + +export function addInstallGlobal( + config: InstallConfig, +) { + if (!self.name) { + addInstall(self.ghjk, config); + } +} diff --git a/tools/jco.ts b/plugs/jco.ts similarity index 95% rename from tools/jco.ts rename to plugs/jco.ts index 42b41344..89a74b16 100644 --- a/tools/jco.ts +++ b/plugs/jco.ts @@ -4,11 +4,11 @@ import { InstallEnv, ListAllEnv, ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; + Plug, +} from "../plug.ts"; export function jco() { - return new class extends Tool { + return new class extends Plug { name = "jco"; dependencies = ["node"]; diff --git a/tools/node.ts b/plugs/node.ts similarity index 73% rename from tools/node.ts rename to plugs/node.ts index b7d72aa4..6b094161 100644 --- a/tools/node.ts +++ b/plugs/node.ts @@ -1,15 +1,25 @@ import { + addInstallGlobal, + denoWorkerPlug, DownloadEnv, ExecPathEnv, + type InstallConfigBase, InstallEnv, ListAllEnv, ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; + Plug, + registerPlugGlobal, +} from "../plug.ts"; import { runOrExit } from "../cli/utils.ts"; -export function node({ version }: { version: string }) { - return new class extends Tool { +const manifest = { + name: "node", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +denoWorkerPlug( + new class extends Plug { name = "node"; dependencies = []; @@ -34,11 +44,11 @@ export function node({ version }: { version: string }) { const versions = metadata.map((v: any) => v.version); versions.sort(); - console.log(versions); return versions; } async download(env: DownloadEnv) { + // TODO: download file const infoRequest = await fetch( `https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-arm64.tar.gz`, ); @@ -56,5 +66,14 @@ export function node({ version }: { version: string }) { env.ASDF_INSTALL_PATH, ); } - }(); + }(), +); + +registerPlugGlobal(manifest); + +export default function node({ version }: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + version, + }); } diff --git a/tools/rust.ts b/plugs/rust.ts similarity index 90% rename from tools/rust.ts rename to plugs/rust.ts index 4f38461d..ab3b9588 100644 --- a/tools/rust.ts +++ b/plugs/rust.ts @@ -4,11 +4,11 @@ import { InstallEnv, ListAllEnv, ListBinPathsEnv, - Tool, -} from "../cli/core/tools.ts"; + Plug, +} from "../plug.ts"; export function rust({ version }: { version: string }) { - return new class extends Tool { + return new class extends Plug { name = "rust"; dependencies = []; diff --git a/tests/dev_deps.ts b/tests/dev_deps.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index ea84d76b..2910e00b 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -5,9 +5,8 @@ ENV SHELL=/bin/bash WORKDIR /ghjk COPY deno.lock ./ -COPY tests/dev_deps.ts ./tests/ -COPY cli/deps.ts ./cli/ -RUN deno cache cli/deps.ts tests/dev_deps.ts +COPY deps/* ./deps/ +RUN deno cache deps/* COPY . ./ RUN deno run -A /ghjk/install.ts diff --git a/tests/utils.ts b/tests/utils.ts index 460a7079..c38a31e2 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,15 +1,14 @@ import { runOrExit } from "../cli/utils.ts"; -import { node } from "../tools/node.ts"; +// import node from "../plugs/node.ts"; type TestCase = { name: string; imports: string; - confFn: () => Promise; + confFn: string | (() => Promise); envs?: Record; epoint: string; }; - async function dockerTest(cases: TestCase[]) { // const socket = Deno.env.get("DOCKER_SOCK") ?? "/var/run/docker.sock"; // const docker = new Docker(socket); @@ -29,7 +28,7 @@ async function dockerTest(cases: TestCase[]) { ...defaultEnvs, ...testEnvs, }; - const configFile = `export { ghjk } from "/ghjk/cli/mod.ts"; + const configFile = `export { ghjk } from "/ghjk/mod.ts"; ${imports.replaceAll("$ghjk", "/ghjk/")} await (${confFn.toString()})()`; @@ -69,17 +68,16 @@ await (${confFn.toString()})()`; await dockerTest([{ name: "a", - imports: `import { node } from "$ghjk/tools/node.ts"`, - confFn: async () => { - // node({ version: "lts" }); - }, + imports: `import node from "$ghjk/plugs/node.ts"`, + confFn: `async () => { + node({ version: "lts" }); + }`, epoint: `echo yes`, -},{ +}, { name: "b", - imports: `import { node } from "$ghjk/tools/node.ts"`, - confFn: async () => { + imports: `import { node } from "$ghjk/plugs/node.ts"`, + confFn: `async () => { // node({ version: "lts" }); - }, + }`, epoint: `echo yes`, -}, -]); +}]); From 40da885a5b31c89ab87414331458db8762a52b0e Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 12 Nov 2023 23:52:32 +0300 Subject: [PATCH 03/43] wip: installation --- README.md | 1 + cli/hooks.ts | 14 ++-- cli/sync.ts | 147 +++++++++++++++++++++++---------- cli/utils.ts | 29 +++++-- core/mod.ts | 26 ++++-- core/types.ts | 50 ++++++++++-- core/worker.ts | 217 ++++++++++++++++++++++++++++++++++--------------- deps/cli.ts | 9 +- mod.ts | 4 - play.ts | 10 ++- plug.ts | 48 +++++++++-- plugs/jco.ts | 4 +- plugs/node.ts | 49 +++++++---- plugs/rust.ts | 4 +- tests/utils.ts | 15 +--- 15 files changed, 440 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 5288f664..aa03e093 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ and looks as follows (abstracting away some implementation details): - pnpm - mold({ if: Deno.build.os === "Macos" }) - hash verifiable dependencies (timestamp) +- hide the `Deno` object in an abstraction ## design considerations diff --git a/cli/hooks.ts b/cli/hooks.ts index fb3cb58e..2bf29f94 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -1,4 +1,4 @@ -import { basename, dirname, resolve } from "../deps/cli.ts"; +import { std_path } from "../deps/cli.ts"; import { dirs, runAndReturn } from "./utils.ts"; // null means it should be removed (for cleaning up old versions) @@ -111,18 +111,18 @@ async function detectShell(): Promise { return envShell; }) .trimEnd(); - return basename(path, ".exe").toLowerCase(); + return std_path.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); + const path = std_path.resolve(baseDir, subpath); if (content === null) { await Deno.remove(path); } else { - await Deno.mkdir(dirname(path), { recursive: true }); + await Deno.mkdir(std_path.dirname(path), { recursive: true }); await Deno.writeTextFile(path, content.trim()); } } @@ -135,7 +135,7 @@ async function filterAddFile( ) { const file = await Deno.readTextFile(path).catch(async (err) => { if (err instanceof Deno.errors.NotFound) { - await Deno.mkdir(dirname(path), { recursive: true }); + await Deno.mkdir(std_path.dirname(path), { recursive: true }); return ""; } throw err; @@ -165,13 +165,13 @@ export async function install() { if (shell === "fish") { await filterAddFile( - resolve(homeDir, ".config/fish/config.fish"), + std_path.resolve(homeDir, ".config/fish/config.fish"), /\.local\/share\/ghjk\/hooks\/hook.fish/, "source $HOME/.local/share/ghjk/hooks/hook.fish", ); } else if (shell === "bash") { await filterAddFile( - resolve(homeDir, ".bashrc"), + std_path.resolve(homeDir, ".bashrc"), /\.local\/share\/ghjk\/hooks\/hook.sh/, "source $HOME/.local/share/ghjk/hooks/hook.sh", ); diff --git a/cli/sync.ts b/cli/sync.ts index cd338e39..941376dd 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,29 +1,34 @@ +import { Command, fs, std_path } from "../deps/cli.ts"; +import logger from "../core/logger.ts"; import { DenoWorkerPlugManifestX, GhjkCtx } from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; -import { Command, dirname, exists, resolve } from "../deps/cli.ts"; -import { dirs } from "./utils.ts"; +import { AVAIL_CONCURRENCY, dbg, dirs } from "./utils.ts"; async function findConfig(path: string): Promise { let current = path; while (current !== "/") { const location = `${path}/ghjk.ts`; - if (await exists(location)) { + if (await fs.exists(location)) { return location; } - current = dirname(current); + current = std_path.dirname(current); } return null; } -function shimFromConfig(config: string): string { +function envDirFromConfig(config: string): string { const { shareDir } = dirs(); - return resolve(shareDir, "shims", dirname(config).replaceAll("/", ".")); + return std_path.resolve( + shareDir, + "envs", + std_path.dirname(config).replaceAll("/", "."), + ); } -async function writeLoader(shim: string, env: Record) { - await Deno.mkdir(shim, { recursive: true }); +async function writeLoader(envDir: string, env: Record) { + await Deno.mkdir(envDir, { recursive: true }); await Deno.writeTextFile( - `${shim}/loader.fish`, + `${envDir}/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"), @@ -45,56 +50,108 @@ export class SyncCommand extends Command { return; } - const shim = shimFromConfig(config); - console.log(shim); + const envDir = envDirFromConfig(config); + logger().debug({ envDir }); - for (const [name, manifest] of cx.plugs) { - if ("moduleSpecifier" in manifest) { + /* for (const [name, { ty, manifest }] of cx.plugs) { + if (ty == "denoWorker") { const plug = new DenoWorkerPlug( manifest as DenoWorkerPlugManifestX, ); const versions = await plug.listAll({}); console.log(name, { versions }); - plug.terminate(); + } else { + throw Error( + `unsupported plugin type "${ty}": ${JSON.stringify(manifest)}`, + ); } - } + } */ // 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 - return; - /* - 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" }, + let env = {}; + for (const inst of cx.installs) { + const regPlug = cx.plugs.get(inst.plugName); + if (!regPlug) { + throw Error( + `unable to find pluign "${inst.plugName}" specified by install ${ + JSON.stringify(inst) + }`, + ); + } + const { ty: plugType, manifest } = regPlug; + let plug; + if (plugType == "denoWorker") { + plug = new DenoWorkerPlug( + manifest as DenoWorkerPlugManifestX, + ); + } else { + throw Error( + `unsupported plugin type "${plugType}": ${ + JSON.stringify(manifest) + }`, + ); + } + const installVersion = inst.version ?? await plug.latestStable({}); + const installPath = std_path.resolve( + envDir, + "installs", + plug.name, + installVersion, ); + const downloadPath = std_path.resolve( + envDir, + "downloads", + plug.name, + installVersion, + ); + await Promise.allSettled( + [installPath, downloadPath].map((path) => + Deno.mkdir(path, { recursive: true }) + ), + ); + // logger().info(`downloading ${inst.plugName}:${installVersion}`); + // await plug.download({ + // ASDF_INSTALL_PATH: installPath, + // ASDF_INSTALL_TYPE: "version", + // ASDF_INSTALL_VERSION: installVersion, + // ASDF_DOWNLOAD_PATH: downloadPath, + // }); + logger().info(`installing ${inst.plugName}:${installVersion}`); + await plug.install({ + ASDF_INSTALL_PATH: installPath, + ASDF_INSTALL_TYPE: "version", + ASDF_INSTALL_VERSION: installVersion, + ASDF_CONCURRENCY: AVAIL_CONCURRENCY, + ASDF_DOWNLOAD_PATH: downloadPath, + }); + for ( + const bin of dbg( + await plug.listBinPaths({ + ASDF_INSTALL_PATH: installPath, + ASDF_INSTALL_TYPE: "version", + ASDF_INSTALL_VERSION: installVersion, + }), + ) + ) { + const binPath = std_path.resolve(installPath, bin); + const binName = std_path.basename(binPath); // TODO: aliases + const shimPath = std_path.resolve(envDir, "shims", binName); + await Deno.remove(shimPath); + await Deno.symlink(binPath, shimPath, { type: "file" }); + } + env = { + ...env, + ...await plug.execEnv({ + ASDF_INSTALL_PATH: installPath, + ASDF_INSTALL_TYPE: "version", + ASDF_INSTALL_VERSION: installVersion, + }), + }; } - - // write shim if config changes or does not exists - const env = await nodeTool.execEnv({ ASDF_INSTALL_PATH } as any); - await writeLoader(shim, env);*/ + await writeLoader(envDir, env); }); } } diff --git a/cli/utils.ts b/cli/utils.ts index 36c5b9db..65df4380 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -1,14 +1,15 @@ import { Err, Ok, Result } from "../deps/cli.ts"; +import logger from "../core/logger.ts"; export function dbg(val: T) { - console.log("[dbg] ", val); + logger().debug("inline", val); return val; } export async function runAndReturn( cmd: string[], cwd?: string, - env: Record = Deno.env.toObject(), + env: Record = {}, ): Promise> { try { const output = await new Deno.Command(cmd[0], { @@ -27,7 +28,7 @@ export async function runAndReturn( } } -export async function runOrExit( +export async function spawn( cmd: string[], options: { cwd?: string; @@ -37,9 +38,8 @@ export async function runOrExit( ) { const { cwd, env, pipeInput } = { ...options, - env: options.env ?? Deno.env.toObject(), }; - console.log(cmd); + logger().debug("spawning", cmd); const p = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, @@ -49,9 +49,12 @@ export async function runOrExit( 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 }); + if (self.name) { + } else { + // 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 }); + } if (pipeInput) { const writer = p.stdin.getWriter(); @@ -61,7 +64,7 @@ export async function runOrExit( await p.stdin.close(); const { code, success } = await p.status; if (!success) { - Deno.exit(code); + throw Error(`child failed with code ${code}`); } } @@ -84,3 +87,11 @@ export function dirs() { } 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 Error(`Value of DENO_JOBS is NAN: ${Deno.env.get("DENO_JOBS")}`); +} diff --git a/core/mod.ts b/core/mod.ts index ce1d9b74..8ccf2ef2 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,19 +1,31 @@ export * from "./types.ts"; import { semver } from "../deps/common.ts"; import { + type DenoWorkerPlugManifest, type GhjkCtx, type InstallConfig, - type PlugManifest, + type PlugManifestX, } from "./types.ts"; import validators from "./validators.ts"; import logger from "./logger.ts"; +export const Ghjk = { + cwd: Deno.cwd, +}; + +export function registerDenoPlug( + cx: GhjkCtx, + manifestUnclean: DenoWorkerPlugManifest, +) { + const manifest = validators.denoWorkerPlugManifest.parse(manifestUnclean); + registerPlug(cx, "denoWorker", manifest); +} export function registerPlug( cx: GhjkCtx, - manifestUnclean: PlugManifest, + ty: "denoWorker", + manifest: PlugManifestX, ) { - const manifest = validators.plugManifestBase.parse(manifestUnclean); - const conflict = cx.plugs.get(manifest.name); + const conflict = cx.plugs.get(manifest.name)?.manifest; if (conflict) { if ( conflict.conflictResolution == "override" && @@ -34,7 +46,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, manifest); + cx.plugs.set(manifest.name, { ty, manifest }); } else if ( semver.compare(manifest.version, conflict.version) == 0 ) { @@ -49,7 +61,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, manifest); + cx.plugs.set(manifest.name, { ty, manifest }); } else { logger().debug("plug rejected due after defer", { retained: conflict, @@ -58,7 +70,7 @@ export function registerPlug( } } else { logger().debug("plug registered", manifest); - cx.plugs.set(manifest.name, manifest); + cx.plugs.set(manifest.name, { ty, manifest }); } } diff --git a/core/types.ts b/core/types.ts index 19ce7ea7..0d2c762b 100644 --- a/core/types.ts +++ b/core/types.ts @@ -1,5 +1,7 @@ import { zod } from "../deps/common.ts"; import validators from "./validators.ts"; +import logger from "./logger.ts"; +import { std_path } from "../deps/cli.ts"; // Describes the plugin itself export type PlugManifestBase = zod.input; @@ -18,7 +20,11 @@ export type DenoWorkerPlugManifestX = zod.infer< // This is the transformed version of PlugManifest, ready for consumption export type PlugManifestX = PlugManifestBaseX | DenoWorkerPlugManifestX; -export type RegisteredPlugs = Map; +type RegisteredPlug = { + ty: "denoWorker"; + manifest: PlugManifestX; +}; +export type RegisteredPlugs = Map; export interface InstallConfigBase { version?: string; @@ -38,15 +44,45 @@ export abstract class Plug { abstract name: string; abstract dependencies: string[]; - abstract execEnv( - env: ExecPathEnv, - ): Promise> | Record; + execEnv( + env: ExecEnvEnv, + ): Promise> | Record { + return {}; + } - abstract listBinPaths( + listBinPaths( env: ListBinPathsEnv, - ): Promise> | Record; + ): Promise | string[] { + return [std_path.resolve(env.ASDF_INSTALL_PATH, "bin")]; + /* + const defaultBinPath = std_path.resolve(env.ASDF_INSTALL_PATH, "bin"); + const dirsToCheck = [defaultBinPath]; + while (dirsToCheck.length > 0) { + const dirPath = dirsToCheck.pop()!; + for await (const { isFile, name } of Deno.readDir(dirPath)) { + const path = std_path.resolve(dirPath, name); + if (!isFile) { + dirsToCheck.push(path); + continue; + } + Deno. + } + } */ + } abstract listAll(env: ListAllEnv): Promise | string[]; + latestStable(env: ListAllEnv): Promise | string { + return (async () => { + logger().warning( + `using default implementation of latestStable for plug ${this.name}`, + ); + const allVers = await this.listAll(env); + if (allVers.length == 0) { + throw Error("no versions found"); + } + return allVers[allVers.length - 1]; + })(); + } abstract download(env: DownloadEnv): Promise | void; @@ -77,7 +113,7 @@ export interface ListAllEnv { export interface ListBinPathsEnv extends BinDefaultEnv { } -export interface ExecPathEnv extends BinDefaultEnv { +export interface ExecEnvEnv extends BinDefaultEnv { } export interface DownloadEnv extends BinDefaultEnv { diff --git a/core/worker.ts b/core/worker.ts index 4d94a906..979d80f2 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -1,41 +1,55 @@ //// /// +import { semver } from "../deps/common.ts"; import logger from "./logger.ts"; import { type DenoWorkerPlugManifestX, type DownloadEnv, - type ExecPathEnv, + type ExecEnvEnv, type InstallEnv, type ListAllEnv, type ListBinPathsEnv, Plug, } from "./types.ts"; -type WorkerReq = - & { - ty: string; - id: string; - } - & ({ - ty: "listAll"; - arg: ListAllEnv; - } | { - ty: "download"; - } | { - ty: "install"; - }); +type WorkerReq = { + ty: "listAll"; + arg: ListAllEnv; +} | { + ty: "latestStable"; + arg: ListAllEnv; +} | { + ty: "execEnv"; + arg: ExecEnvEnv; +} | { + ty: "download"; + arg: DownloadEnv; +} | { + ty: "install"; + arg: InstallEnv; +} | { + ty: "listBinPaths"; + arg: ListBinPathsEnv; +}; -type WorkerResp = - & { - id: string; - } - & ({ - ty: "listAll"; - payload: string[]; - } | { - ty: "download"; - }); +type WorkerResp = { + ty: "listAll"; + payload: string[]; +} | { + ty: "latestStable"; + payload: string; +} | { + ty: "listBinPaths"; + 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 @@ -50,10 +64,35 @@ export function denoWorkerPlug

(plug: P) { let res: WorkerResp; if (req.ty == "listAll") { res = { - ty: "listAll", - id: req.id, + ty: req.ty, + // id: req.id, payload: await plug.listAll(req.arg), }; + } else if (req.ty === "latestStable") { + res = { + ty: req.ty, + payload: await plug.latestStable(req.arg), + }; + } else if (req.ty === "execEnv") { + res = { + ty: req.ty, + payload: await plug.execEnv(req.arg), + }; + } else if (req.ty === "listBinPaths") { + res = { + ty: req.ty, + payload: await plug.listBinPaths(req.arg), + }; + } else if (req.ty === "download") { + await plug.download(req.arg), + res = { + ty: req.ty, + }; + } else if (req.ty === "install") { + await plug.install(req.arg), + res = { + ty: req.ty, + }; } else { logger().error("unrecognized worker request type", req); throw Error("unrecognized worker request type"); @@ -62,69 +101,117 @@ export function denoWorkerPlug

(plug: P) { }; } } +// type MethodKeys = { +// [P in keyof T]-?: T[P] extends Function ? P : never; +// }[keyof T]; export class DenoWorkerPlug extends Plug { name: string; dependencies: string[]; - worker: Worker; - eventListenrs: Map void> = new Map(); + constructor(public manifest: DenoWorkerPlugManifestX) { super(); this.name = manifest.name; this.dependencies = []; // TODO - this.worker = new Worker(manifest.moduleSpecifier, { - name: `${manifest.name}:${manifest.version}`, + } + + /// This creates a new worker on every call + async call( + req: WorkerReq, + ): Promise { + const worker = new Worker(this.manifest.moduleSpecifier, { + name: `${this.manifest.name}:${semver.format(this.manifest.version)}`, type: "module", }); - this.worker.onmessage = (evt: MessageEvent) => { - const res = evt.data; - if (!res.id) { - logger().error("invalid worker response", res); - throw Error("unrecognized worker request type"); - } - const listener = this.eventListenrs.get(res.id); - if (listener) { - listener(res); - } else { - logger().error("worker response has no listeners", res); - throw Error("recieved worker response has no listeners"); - } - }; - } - terminate() { - this.worker.terminate(); + 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: ListAllEnv): Promise { - const id = crypto.randomUUID(); const req: WorkerReq = { ty: "listAll", - id, + // id: crypto.randomUUID(), arg: env, }; - const res = await new Promise((resolve) => { - this.eventListenrs.set(id, (res) => resolve(res)); - this.worker.postMessage(req); - }); - this.eventListenrs.delete(id); + const res = await this.call(req); if (res.ty == "listAll") { return res.payload; } throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - execEnv( - env: ExecPathEnv, - ): Record | Promise> { - throw new Error("Method not implemented."); + + async latestStable(env: ListAllEnv): Promise { + const req: WorkerReq = { + ty: "latestStable", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "latestStable") { + return res.payload; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - listBinPaths( + + async execEnv( + env: ExecEnvEnv, + ): Promise> { + const req: WorkerReq = { + ty: "execEnv", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "execEnv") { + return res.payload; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + async listBinPaths( env: ListBinPathsEnv, - ): Record | Promise> { - throw new Error("Method not implemented."); + ): Promise { + const req: WorkerReq = { + ty: "listBinPaths", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "listBinPaths") { + return res.payload; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - download(env: DownloadEnv): void | Promise { - throw new Error("Method not implemented."); + async download(env: DownloadEnv): Promise { + const req: WorkerReq = { + ty: "download", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "download") { + return; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - install(env: InstallEnv): void | Promise { - throw new Error("Method not implemented."); + async install(env: InstallEnv): Promise { + const req: WorkerReq = { + ty: "install", + arg: env, + }; + const res = await this.call(req); + if (res.ty == "install") { + return; + } + throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } } diff --git a/deps/cli.ts b/deps/cli.ts index c46713ac..2d251f90 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -4,12 +4,9 @@ export * from "./common.ts"; 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 * as std_path from "https://deno.land/std@0.205.0/path/mod.ts"; +export * as fs from "https://deno.land/std@0.205.0/fs/mod.ts"; +export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; export { Command, type CommandResult, diff --git a/mod.ts b/mod.ts index 641596a9..42d65ad4 100644 --- a/mod.ts +++ b/mod.ts @@ -45,10 +45,6 @@ log.setup({ level: "DEBUG", handlers: ["console"], }, - plug: { - level: "DEBUG", - handlers: ["console"], - }, }, }); diff --git a/play.ts b/play.ts index f1d0f7a3..f18b8f4c 100644 --- a/play.ts +++ b/play.ts @@ -1,5 +1,11 @@ -import { runOrExit } from "./cli/utils.ts"; +import { spawn } from "./cli/utils.ts"; -await runOrExit(["docker", "buildx", "build", "-t", "test", "-"], { +await spawn(["tar", "--help"], { pipeInput: "RUN echo heyya", }); + +const b = () => { + console.log("calling b"); + return "b"; +}; +const a = "a" ?? b(); diff --git a/plug.ts b/plug.ts index b1ccfdbd..59354852 100644 --- a/plug.ts +++ b/plug.ts @@ -1,27 +1,65 @@ import { addInstall, + type DenoWorkerPlugManifest, type GhjkCtx, type InstallConfig, - type PlugManifestBase, - registerPlug, + registerDenoPlug, } from "./core/mod.ts"; +import { log } from "./deps/plug.ts"; export * from "./core/mod.ts"; +export { default as logger } from "./core/logger.ts"; export { denoWorkerPlug } from "./core/worker.ts"; export type * from "./core/mod.ts"; +log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("DEBUG", { + formatter: (lr) => { + let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + // if (lr.args.length > 0) { + // msg += JSON.stringify(lr.args); + // } + + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), + }, + + loggers: { + // configure default logger available via short-hand methods above. + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + [self.name]: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); + declare global { interface Window { ghjk: GhjkCtx; } } -export function registerPlugGlobal( - manifestUnclean: PlugManifestBase, +export function registerDenoPlugGlobal( + manifestUnclean: DenoWorkerPlugManifest, ) { // make sure we're not running in a Worker first if (!self.name) { - registerPlug(self.ghjk, manifestUnclean); + registerDenoPlug(self.ghjk, manifestUnclean); } } diff --git a/plugs/jco.ts b/plugs/jco.ts index 89a74b16..10ba34fc 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -1,6 +1,6 @@ import { DownloadEnv, - ExecPathEnv, + ExecEnvEnv, InstallEnv, ListAllEnv, ListBinPathsEnv, @@ -12,7 +12,7 @@ export function jco() { name = "jco"; dependencies = ["node"]; - execEnv(env: ExecPathEnv) { + execEnv(env: ExecEnvEnv) { throw new Error("Method not implemented."); return {}; } diff --git a/plugs/node.ts b/plugs/node.ts index 6b094161..d5b4af5a 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -2,15 +2,17 @@ import { addInstallGlobal, denoWorkerPlug, DownloadEnv, - ExecPathEnv, + ExecEnvEnv, type InstallConfigBase, InstallEnv, ListAllEnv, ListBinPathsEnv, + logger, Plug, - registerPlugGlobal, + registerDenoPlugGlobal, } from "../plug.ts"; -import { runOrExit } from "../cli/utils.ts"; +import { spawn } from "../cli/utils.ts"; +import { std_path } from "../deps/cli.ts"; const manifest = { name: "node", @@ -23,18 +25,18 @@ denoWorkerPlug( name = "node"; dependencies = []; - execEnv(env: ExecPathEnv) { + execEnv(env: ExecEnvEnv) { return { NODE_PATH: env.ASDF_INSTALL_PATH, }; } listBinPaths(env: ListBinPathsEnv) { - return { - "bin/node": "node", - "bin/npm": "npm", - "bin/npx": "npx", - }; + return [ + "bin/node", + "bin/npm", + "bin/npx", + ]; } async listAll(env: ListAllEnv) { @@ -49,18 +51,35 @@ denoWorkerPlug( async download(env: DownloadEnv) { // TODO: download file - const infoRequest = await fetch( + const resp = 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!, + const dest = await Deno.open( + std_path.resolve( + env.ASDF_DOWNLOAD_PATH, + "node-v21.1.0-darwin-arm64.tar.gz", + ), + { create: true, truncate: true, write: true }, ); + try { + await resp.body!.pipeTo(dest.writable); + } finally { + dest.close(); + } } 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 spawn(["ls", env.ASDF_DOWNLOAD_PATH]); + await spawn([ + "tar", + "xf", + std_path.resolve( + env.ASDF_DOWNLOAD_PATH, + "node-v21.1.0-darwin-arm64.tar.gz", + ), + `--directory=${std_path.resolve(env.ASDF_INSTALL_PATH)}`, + ]); await Deno.rename( "node-v21.1.0-darwin-arm64", env.ASDF_INSTALL_PATH, @@ -69,7 +88,7 @@ denoWorkerPlug( }(), ); -registerPlugGlobal(manifest); +registerDenoPlugGlobal(manifest); export default function node({ version }: InstallConfigBase = {}) { addInstallGlobal({ diff --git a/plugs/rust.ts b/plugs/rust.ts index ab3b9588..17b47e79 100644 --- a/plugs/rust.ts +++ b/plugs/rust.ts @@ -1,6 +1,6 @@ import { DownloadEnv, - ExecPathEnv, + ExecEnvEnv, InstallEnv, ListAllEnv, ListBinPathsEnv, @@ -12,7 +12,7 @@ export function rust({ version }: { version: string }) { name = "rust"; dependencies = []; - execEnv(env: ExecPathEnv) { + execEnv(env: ExecEnvEnv) { throw new Error("Method not implemented."); return {}; } diff --git a/tests/utils.ts b/tests/utils.ts index c38a31e2..54966c2f 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,4 +1,4 @@ -import { runOrExit } from "../cli/utils.ts"; +import { spawn } from "../cli/utils.ts"; // import node from "../plugs/node.ts"; type TestCase = { @@ -37,7 +37,7 @@ await (${confFn.toString()})()`; templateStrings.addConfig, configFile, ); - await runOrExit([ + await spawn([ ...dockerCmd, "buildx", "build", @@ -46,7 +46,7 @@ await (${confFn.toString()})()`; "-f-", ".", ], { env, pipeInput: dFile }); - await runOrExit([ + await spawn([ ...dockerCmd, "run", "--rm", @@ -57,7 +57,7 @@ await (${confFn.toString()})()`; tag, ...epoint.split(/\s/), ], { env }); - await runOrExit([ + await spawn([ ...dockerCmd, "rmi", tag, @@ -73,11 +73,4 @@ await dockerTest([{ node({ version: "lts" }); }`, epoint: `echo yes`, -}, { - name: "b", - imports: `import { node } from "$ghjk/plugs/node.ts"`, - confFn: `async () => { - // node({ version: "lts" }); - }`, - epoint: `echo yes`, }]); From 98fea71fec4233e8a7e4128b398351eef7f7678a Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:14:56 +0300 Subject: [PATCH 04/43] wip: shimmy shim shim --- .vscode/settings.json | 5 ++- cli/hooks.ts | 57 ++++++++++++++++------------- cli/sync.ts | 85 ++++++++++++++++++++++++++++--------------- cli/utils.ts | 14 +++++++ core/types.ts | 2 + core/worker.ts | 2 +- deno.lock | 7 ++++ deps/cli.ts | 2 - deps/common.ts | 3 ++ play.ts | 5 +++ plug.ts | 1 + plugs/node.ts | 46 ++++++++++++++--------- 12 files changed, 153 insertions(+), 76 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 97c886e6..408ccda1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,8 @@ "deno.codeLens.referencesAllFunctions": true, "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" - } + }, + "cSpell.words": [ + "ghjk" + ] } diff --git a/cli/hooks.ts b/cli/hooks.ts index 2bf29f94..fa1d236a 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -25,31 +25,35 @@ ghjk_hook() { eval $GHJK_CLEANUP unset GHJK_CLEANUP fi - current_dir=$PWD - while [ "$current_dir" != "/" ]; do - if [ -e "$current_dir/ghjk.ts" ]; then - shim="$HOME/.local/share/ghjk/shims/$(echo "$current_dir" | tr '/' '.')" - if [ -d "$shim" ]; then - PATH="$shim:$(echo "$PATH" | grep -v "^$HOME\/\.local\/share\/ghjk\/shim")" - source "$shim/loader.fish" - if [ "$shim/loader.fish" -ot "$current_dir/ghjk.ts" ]; then + cur_dir=$PWD + while [ "$cur_dir" != "/" ]; do + if [ -e "$cur_dir/ghjk.ts" ]; then + envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" + if [ -d "$envDir" ]; then + PATH="$envDir/shims:$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':')" + PATH="$\{PATH%:\}" + source "$envDir/loader.sh" + if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then echo -e "\e[38;2;255;69;0m[ghjk] Detected changes, please sync...\e[0m" fi else echo -e "\e[38;2;255;69;0m[ghjk] Uninstalled runtime found, please sync...\e[0m" - echo "$shim" + echo "$envDir" fi - alias ghjk="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $current_dir/ghjk.ts" + alias ghjk="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return fi - cur_dir="$(dirname "$current_dir")" + cur_dir="$(dirname "$cur_dir")" done - alias ghjk "echo 'No ghjk.ts config found.'" + if [[ $PATH =~ ^$HOME\/\.local\/share\/ghjk\/envs ]]; then + PATH=$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + fi + alias ghjk="echo 'No ghjk.ts config found.'" } trap 'ghjk_hook' DEBUG -set_again() { +set_hook_flag() { ghjk_already_run=false } @@ -57,7 +61,7 @@ if [[ -n "$PROMPT_COMMAND" ]]; then PROMPT_COMMAND+=";" fi -PROMPT_COMMAND+="set_again;" +PROMPT_COMMAND+="set_hook_flag;" `, "hooks/hook.fish": ` function ghjk_hook --on-variable PWD @@ -65,14 +69,14 @@ function ghjk_hook --on-variable PWD 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 --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 --global PATH $envDir/shims $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) + 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 @@ -80,13 +84,16 @@ function ghjk_hook --on-variable PWD else set_color FF4500 echo "[ghjk] Uninstalled runtime found, please sync..." - echo $shim + echo $envDir set_color normal end - alias ghjk "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $current_dir/ghjk.ts" + alias ghjk "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return end - set current_dir (dirname $current_dir) + set cur_dir (dirname $cur_dir) + end + if string match -q --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH + set --global PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) end alias ghjk "echo 'No ghjk.ts config found.'" end diff --git a/cli/sync.ts b/cli/sync.ts index 941376dd..7c0981c6 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,4 +1,4 @@ -import { Command, fs, std_path } from "../deps/cli.ts"; +import { Command, std_fs, std_path } from "../deps/cli.ts"; import logger from "../core/logger.ts"; import { DenoWorkerPlugManifestX, GhjkCtx } from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; @@ -8,7 +8,7 @@ async function findConfig(path: string): Promise { let current = path; while (current !== "/") { const location = `${path}/ghjk.ts`; - if (await fs.exists(location)) { + if (await std_fs.exists(location)) { return location; } current = std_path.dirname(current); @@ -30,9 +30,16 @@ async function writeLoader(envDir: string, env: Record) { await Deno.writeTextFile( `${envDir}/loader.fish`, Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP "set --global --export ${k} '$k';"; set --global --export ${k} '${v}'` + `set --global --append GHJK_CLEANUP "set --global --export ${k} '$${k}';";\nset --global --export ${k} '${v}';` ).join("\n"), ); + await Deno.writeTextFile( + `${envDir}/loader.sh`, + `export GHJK_CLEANUP="";\n` + + Object.entries(env).map(([k, v]) => + `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` + ).join("\n"), + ); } export class SyncCommand extends Command { @@ -66,17 +73,12 @@ export class SyncCommand extends Command { ); } } */ - // 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 - let env = {}; for (const inst of cx.installs) { const regPlug = cx.plugs.get(inst.plugName); if (!regPlug) { throw Error( - `unable to find pluign "${inst.plugName}" specified by install ${ + `unable to find plugin "${inst.plugName}" specified by install ${ JSON.stringify(inst) }`, ); @@ -107,26 +109,44 @@ export class SyncCommand extends Command { plug.name, installVersion, ); + logger().debug("creating dirs", { installPath, downloadPath }); await Promise.allSettled( - [installPath, downloadPath].map((path) => - Deno.mkdir(path, { recursive: true }) - ), + [ + Deno.mkdir(installPath, { recursive: true }), + Deno.mkdir(downloadPath, { recursive: true }), + ], ); - // logger().info(`downloading ${inst.plugName}:${installVersion}`); - // await plug.download({ - // ASDF_INSTALL_PATH: installPath, - // ASDF_INSTALL_TYPE: "version", - // ASDF_INSTALL_VERSION: installVersion, - // ASDF_DOWNLOAD_PATH: downloadPath, - // }); - logger().info(`installing ${inst.plugName}:${installVersion}`); - await plug.install({ - ASDF_INSTALL_PATH: installPath, - ASDF_INSTALL_TYPE: "version", - ASDF_INSTALL_VERSION: installVersion, - ASDF_CONCURRENCY: AVAIL_CONCURRENCY, - ASDF_DOWNLOAD_PATH: downloadPath, - }); + if (false) { + logger().info(`downloading ${inst.plugName}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_download_${inst.plugName}@${installVersion}_`, + }); + await plug.download({ + ASDF_INSTALL_PATH: installPath, + ASDF_INSTALL_TYPE: "version", + ASDF_INSTALL_VERSION: installVersion, + ASDF_DOWNLOAD_PATH: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + { + logger().info(`installing ${inst.plugName}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_install_${inst.plugName}@${installVersion}_`, + }); + await plug.install({ + ASDF_INSTALL_PATH: installPath, + ASDF_INSTALL_TYPE: "version", + ASDF_INSTALL_VERSION: installVersion, + ASDF_CONCURRENCY: AVAIL_CONCURRENCY, + ASDF_DOWNLOAD_PATH: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + const shimDir = std_path.resolve(envDir, "shims"); + await Deno.mkdir(shimDir, { recursive: true }); for ( const bin of dbg( await plug.listBinPaths({ @@ -138,10 +158,17 @@ export class SyncCommand extends Command { ) { const binPath = std_path.resolve(installPath, bin); const binName = std_path.basename(binPath); // TODO: aliases - const shimPath = std_path.resolve(envDir, "shims", binName); - await Deno.remove(shimPath); + const shimPath = std_path.resolve(shimDir, binName); + try { + await Deno.remove(shimPath); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) { + throw error; + } + } await Deno.symlink(binPath, shimPath, { type: "file" }); } + // FIXME: prevent malicious env manipulations env = { ...env, ...await plug.execEnv({ diff --git a/cli/utils.ts b/cli/utils.ts index 65df4380..99fe1444 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -50,6 +50,20 @@ export async function spawn( }).spawn(); if (self.name) { + p.stdout.pipeTo( + new WritableStream({ + write(chunk) { + console.log(new TextDecoder().decode(chunk)); + }, + }), + ); + p.stderr.pipeTo( + new WritableStream({ + write(chunk) { + console.error(new TextDecoder().decode(chunk)); + }, + }), + ); } else { // keep pipe asynchronous till the command exists void p.stdout.pipeTo(Deno.stdout.writable, { preventClose: true }); diff --git a/core/types.ts b/core/types.ts index 0d2c762b..1f9965d0 100644 --- a/core/types.ts +++ b/core/types.ts @@ -118,9 +118,11 @@ export interface ExecEnvEnv extends BinDefaultEnv { export interface DownloadEnv extends BinDefaultEnv { ASDF_DOWNLOAD_PATH: string; + tmpDirPath: string; } export interface InstallEnv extends BinDefaultEnv { ASDF_CONCURRENCY: number; ASDF_DOWNLOAD_PATH: string; + tmpDirPath: string; } diff --git a/core/worker.ts b/core/worker.ts index 979d80f2..b4ba356b 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -120,7 +120,7 @@ export class DenoWorkerPlug extends Plug { req: WorkerReq, ): Promise { const worker = new Worker(this.manifest.moduleSpecifier, { - name: `${this.manifest.name}:${semver.format(this.manifest.version)}`, + name: `${this.manifest.name}@${semver.format(this.manifest.version)}`, type: "module", }); const promise = new Promise((resolve, reject) => { diff --git a/deno.lock b/deno.lock index f5a28b87..af6a76fc 100644 --- a/deno.lock +++ b/deno.lock @@ -138,6 +138,13 @@ "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", diff --git a/deps/cli.ts b/deps/cli.ts index 2d251f90..7fe574f7 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -4,8 +4,6 @@ export * from "./common.ts"; 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 * as std_path from "https://deno.land/std@0.205.0/path/mod.ts"; -export * as fs from "https://deno.land/std@0.205.0/fs/mod.ts"; export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; export { Command, diff --git a/deps/common.ts b/deps/common.ts index 2c73b7c2..04cc2acd 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -4,3 +4,6 @@ export * from "./common.ts"; 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_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"; diff --git a/play.ts b/play.ts index f18b8f4c..60636ab0 100644 --- a/play.ts +++ b/play.ts @@ -1,5 +1,10 @@ import { spawn } from "./cli/utils.ts"; +// await Deno.mkdir( +// "/home/asdf/.local/share/ghjk/envs/.home.asdf.Source.ecma.ghjk/installs/node/v9.9.0", +// { recursive: true }, +// ); + await spawn(["tar", "--help"], { pipeInput: "RUN echo heyya", }); diff --git a/plug.ts b/plug.ts index 59354852..aec6bf51 100644 --- a/plug.ts +++ b/plug.ts @@ -8,6 +8,7 @@ import { import { log } from "./deps/plug.ts"; export * from "./core/mod.ts"; +export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; export { denoWorkerPlug } from "./core/worker.ts"; export type * from "./core/mod.ts"; diff --git a/plugs/node.ts b/plugs/node.ts index d5b4af5a..6d84446d 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -10,9 +10,11 @@ import { logger, Plug, registerDenoPlugGlobal, + std_fs, + std_path, + std_url, } from "../plug.ts"; import { spawn } from "../cli/utils.ts"; -import { std_path } from "../deps/cli.ts"; const manifest = { name: "node", @@ -51,37 +53,45 @@ denoWorkerPlug( async download(env: DownloadEnv) { // TODO: download file - const resp = await fetch( - `https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-arm64.tar.gz`, + const url = + `https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-arm64.tar.gz`; + const fileName = std_url.basename(url); + + const tmpFilePath = std_path.resolve( + env.tmpDirPath, + fileName, ); + + const resp = await fetch(url); const dest = await Deno.open( - std_path.resolve( - env.ASDF_DOWNLOAD_PATH, - "node-v21.1.0-darwin-arm64.tar.gz", - ), + tmpFilePath, { create: true, truncate: true, write: true }, ); - try { - await resp.body!.pipeTo(dest.writable); - } finally { - dest.close(); - } + await resp.body!.pipeTo(dest.writable, { preventClose: false }); + await Deno.copyFile( + tmpFilePath, + std_path.resolve(env.ASDF_DOWNLOAD_PATH, fileName), + ); } async install(env: InstallEnv) { - await Deno.remove(env.ASDF_INSTALL_PATH, { recursive: true }); - await spawn(["ls", env.ASDF_DOWNLOAD_PATH]); + const fileName = "node-v21.1.0-darwin-arm64.tar.gz"; await spawn([ "tar", "xf", std_path.resolve( env.ASDF_DOWNLOAD_PATH, - "node-v21.1.0-darwin-arm64.tar.gz", + fileName, ), - `--directory=${std_path.resolve(env.ASDF_INSTALL_PATH)}`, + `--directory=${env.tmpDirPath}`, ]); - await Deno.rename( - "node-v21.1.0-darwin-arm64", + await Deno.remove(env.ASDF_INSTALL_PATH, { recursive: true }); + // FIXME: use Deno.rename when https://github.com/denoland/deno/pull/19879 is merged + await std_fs.copy( + std_path.resolve( + env.tmpDirPath, + fileName.replace(/\.tar\.gz$/, ""), + ), env.ASDF_INSTALL_PATH, ); } From 084d51975be64c46e3e8cafd1ad8a31c806cc882 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 17 Nov 2023 04:24:42 +0300 Subject: [PATCH 05/43] feat: ambient access plugs --- cli/hooks.ts | 45 +++++++++++---------- cli/mod.ts | 5 ++- cli/sync.ts | 6 +-- cli/utils.ts | 65 +++++++++++++++++++------------ core/ambient.ts | 80 ++++++++++++++++++++++++++++++++++++++ core/mod.ts | 32 +++++++++++---- core/types.ts | 55 +++++++++++++++----------- core/validators.ts | 28 +++++++++++-- core/worker.ts | 12 ++---- deno.jsonc | 2 +- deno.lock | 29 ++++++++++++++ deps/dev.ts | 1 + mod.ts | 25 +++++++++--- plug.ts | 28 +++++++++---- plugs/node.ts | 3 +- plugs/tar.ts | 21 ++++++++++ tests/ambient.ts | 24 ++++++++++++ tests/{utils.ts => e2e.ts} | 15 ++++--- 18 files changed, 362 insertions(+), 114 deletions(-) create mode 100644 core/ambient.ts create mode 100644 plugs/tar.ts create mode 100644 tests/ambient.ts rename tests/{utils.ts => e2e.ts} (85%) diff --git a/cli/hooks.ts b/cli/hooks.ts index fa1d236a..41bca575 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -1,8 +1,9 @@ import { std_path } from "../deps/cli.ts"; -import { dirs, runAndReturn } from "./utils.ts"; +import { ChildError, dirs, runAndReturn } from "./utils.ts"; // null means it should be removed (for cleaning up old versions) const vfs = { + // the script executed when users use the ghjk command "hooks/entrypoint.ts": ` const log = console.log; console.log = (...args) => { @@ -10,8 +11,10 @@ console.log = (...args) => { }; const mod = await import(Deno.args[0]); console.log = log; -mod.ghjk.runCli(Deno.args.slice(1)); +mod.ghjk.runCli(Deno.args.slice(1), mod.options); `, + + // the hook run before every prompt draw in bash "hooks/hook.sh": ` ghjk_already_run=false @@ -63,6 +66,8 @@ fi PROMPT_COMMAND+="set_hook_flag;" `, + + // the hook run before every prompt draw in fish "hooks/hook.fish": ` function ghjk_hook --on-variable PWD if set --query GHJK_CLEANUP @@ -102,23 +107,24 @@ ghjk_hook }; async function detectShell(): Promise { - const parent = await runAndReturn([ - "ps", - "-p", - String(Deno.ppid), - "-o", - "comm=", - ]); - const path = parent - .unwrapOrElse((e) => { - const envShell = Deno.env.get("SHELL"); - if (!envShell) { - throw new Error(`cannot get parent process name: ${e}`); - } - return envShell; - }) - .trimEnd(); - return std_path.basename(path, ".exe").toLowerCase(); + let path; + + try { + path = await runAndReturn([ + "ps", + "-p", + String(Deno.ppid), + "-o", + "comm=", + ]); + } catch (err) { + const envShell = Deno.env.get("SHELL"); + if (!envShell) { + throw new Error(`cannot get parent process name: ${err}`); + } + path = envShell; + } + return std_path.basename(path, ".exe").toLowerCase().trim(); } async function unpackVFS(baseDir: string): Promise { @@ -169,7 +175,6 @@ export async function install() { const { homeDir, shareDir } = dirs(); await unpackVFS(shareDir); const shell = await detectShell(); - if (shell === "fish") { await filterAddFile( std_path.resolve(homeDir, ".config/fish/config.fish"), diff --git a/cli/mod.ts b/cli/mod.ts index b9b00ac4..273547d7 100644 --- a/cli/mod.ts +++ b/cli/mod.ts @@ -6,7 +6,10 @@ import { OutdatedCommand } from "./outdated.ts"; import { CleanupCommand } from "./cleanup.ts"; import { type GhjkCtx } from "../core/mod.ts"; -export function runCli(args: string[], cx: GhjkCtx): Promise { +export function runCli( + args: string[], + cx: GhjkCtx, +): Promise { return new Command() .name("ghjk") .version("0.1.0") diff --git a/cli/sync.ts b/cli/sync.ts index 7c0981c6..3a9a35dc 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -100,13 +100,13 @@ export class SyncCommand extends Command { const installPath = std_path.resolve( envDir, "installs", - plug.name, + plug.manifest.name, installVersion, ); const downloadPath = std_path.resolve( envDir, "downloads", - plug.name, + plug.manifest.name, installVersion, ); logger().debug("creating dirs", { installPath, downloadPath }); @@ -116,7 +116,7 @@ export class SyncCommand extends Command { Deno.mkdir(downloadPath, { recursive: true }), ], ); - if (false) { + { logger().info(`downloading ${inst.plugName}:${installVersion}`); const tmpDirPath = await Deno.makeTempDir({ prefix: `ghjk_download_${inst.plugName}@${installVersion}_`, diff --git a/cli/utils.ts b/cli/utils.ts index 99fe1444..238571d0 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -6,26 +6,37 @@ export function dbg(val: T) { return val; } +export class ChildError extends Error { + constructor( + public code: number, + public output: string, + ) { + super(`ChildError - ${code} - ${output}`); + } +} + export async function runAndReturn( cmd: string[], - cwd?: string, - env: Record = {}, -): Promise> { - try { - const output = await new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - env, - }).output(); + options: { + cwd?: string; + env?: Record; + } = {}, +): Promise { + const { cwd, env } = { + ...options, + }; + 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)); - } catch (err) { - return Err(err.toString()); + if (output.success) { + return new TextDecoder().decode(output.stdout); } + throw new ChildError(output.code, new TextDecoder().decode(output.stderr)); } export async function spawn( @@ -40,24 +51,28 @@ export async function spawn( ...options, }; logger().debug("spawning", cmd); - const p = new Deno.Command(cmd[0], { + const child = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, stdout: "piped", stderr: "piped", - stdin: "piped", + ...(pipeInput + ? { + stdin: "piped", + } + : {}), env, }).spawn(); if (self.name) { - p.stdout.pipeTo( + child.stdout.pipeTo( new WritableStream({ write(chunk) { console.log(new TextDecoder().decode(chunk)); }, }), ); - p.stderr.pipeTo( + child.stderr.pipeTo( new WritableStream({ write(chunk) { console.error(new TextDecoder().decode(chunk)); @@ -66,17 +81,17 @@ export async function spawn( ); } else { // 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 }); + void child.stdout.pipeTo(Deno.stdout.writable, { preventClose: true }); + void child.stderr.pipeTo(Deno.stderr.writable, { preventClose: true }); } if (pipeInput) { - const writer = p.stdin.getWriter(); + const writer = child.stdin.getWriter(); await writer.write(new TextEncoder().encode(pipeInput)); writer.releaseLock(); + await child.stdin.close(); } - await p.stdin.close(); - const { code, success } = await p.status; + const { code, success } = await child.status; if (!success) { throw Error(`child failed with code ${code}`); } diff --git a/core/ambient.ts b/core/ambient.ts new file mode 100644 index 00000000..b3a9f2eb --- /dev/null +++ b/core/ambient.ts @@ -0,0 +1,80 @@ +import { + type AmbientAccessPlugManifest, + type DownloadEnv, + type InstallEnv, + type ListAllEnv, + type ListBinPathsEnv, + Plug, +} from "./types.ts"; +import { ChildError, runAndReturn } from "../cli/utils.ts"; + +export class AmbientAccessPlug extends Plug { + constructor(public manifest: AmbientAccessPlugManifest) { + super(); + if (manifest.deps && manifest.deps.length > 0) { + throw new Error( + `ambient access plugin has deps ${JSON.stringify(manifest)}`, + ); + } + } + async listAll(_env: ListAllEnv): Promise { + const execPath = await this.pathToExec(); + let versionOut; + try { + versionOut = await runAndReturn([ + 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 listBinPaths( + _env: ListBinPathsEnv, + ): Promise { + return [await this.pathToExec()]; + } + + download(_env: DownloadEnv): void | Promise { + // no op + } + install(_env: InstallEnv): void | Promise { + // no op + } + async pathToExec(): Promise { + try { + const output = await runAndReturn(["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/core/mod.ts b/core/mod.ts index 8ccf2ef2..1db8b83a 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,8 +1,9 @@ export * from "./types.ts"; import { semver } from "../deps/common.ts"; import { + type AmbientAccessPlugManifest, type DenoWorkerPlugManifest, - type GhjkCtx, + type GhjkConfig, type InstallConfig, type PlugManifestX, } from "./types.ts"; @@ -14,15 +15,24 @@ export const Ghjk = { }; export function registerDenoPlug( - cx: GhjkCtx, + cx: GhjkConfig, manifestUnclean: DenoWorkerPlugManifest, ) { const manifest = validators.denoWorkerPlugManifest.parse(manifestUnclean); registerPlug(cx, "denoWorker", manifest); } + +export function registerAmbientPlug( + cx: GhjkConfig, + manifestUnclean: AmbientAccessPlugManifest, +) { + const manifest = validators.ambientAccessPlugManifest.parse(manifestUnclean); + registerPlug(cx, "ambientAccess", manifest); +} + export function registerPlug( - cx: GhjkCtx, - ty: "denoWorker", + cx: GhjkConfig, + ty: "denoWorker" | "ambientAccess", manifest: PlugManifestX, ) { const conflict = cx.plugs.get(manifest.name)?.manifest; @@ -48,14 +58,20 @@ export function registerPlug( }); cx.plugs.set(manifest.name, { ty, manifest }); } else if ( - semver.compare(manifest.version, conflict.version) == 0 + semver.compare( + semver.parse(manifest.version), + semver.parse(conflict.version), + ) == 0 ) { throw Error( `Two instances of the plug "${manifest.name}" found with an identical version` + - `and bothboth set to "deferToNewer" conflictResolution.`, + `and both set to "deferToNewer" conflictResolution.`, ); } else if ( - semver.compare(manifest.version, conflict.version) > 0 + semver.compare( + semver.parse(manifest.version), + semver.parse(conflict.version), + ) > 0 ) { logger().debug("plug replaced after version defer", { new: manifest, @@ -75,7 +91,7 @@ export function registerPlug( } export function addInstall( - cx: GhjkCtx, + cx: GhjkConfig, config: InstallConfig, ) { if (!cx.plugs.has(config.plugName)) { diff --git a/core/types.ts b/core/types.ts index 1f9965d0..01cc51d0 100644 --- a/core/types.ts +++ b/core/types.ts @@ -10,18 +10,32 @@ export type DenoWorkerPlugManifest = zod.input< typeof validators.denoWorkerPlugManifest >; +export type AmbientAccessPlugManifest = zod.input< + typeof validators.ambientAccessPlugManifest +>; + // Describes the plugin itself -export type PlugManifest = PlugManifestBase | DenoWorkerPlugManifest; +export type PlugManifest = + | PlugManifestBase + | DenoWorkerPlugManifest + | AmbientAccessPlugManifest; +export type PlugDep = zod.infer; export type PlugManifestBaseX = zod.infer; export type DenoWorkerPlugManifestX = zod.infer< typeof validators.denoWorkerPlugManifest >; +export type AmbientAccessPlugManifestX = zod.infer< + typeof validators.ambientAccessPlugManifest +>; // This is the transformed version of PlugManifest, ready for consumption -export type PlugManifestX = PlugManifestBaseX | DenoWorkerPlugManifestX; +export type PlugManifestX = + | PlugManifestBaseX + | DenoWorkerPlugManifestX + | AmbientAccessPlugManifestX; type RegisteredPlug = { - ty: "denoWorker"; + ty: "denoWorker" | "ambientAccess"; manifest: PlugManifestX; }; export type RegisteredPlugs = Map; @@ -35,17 +49,25 @@ export type InstallConfig = InstallConfigBase & { plugName: string; }; -export interface GhjkCtx { +export interface GhjkConfig { plugs: RegisteredPlugs; installs: InstallConfig[]; } +/// 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 interface GhjkSecureConfig { + allowedPluginDeps: PlugDep[]; +} + +export type GhjkCtx = GhjkConfig & GhjkSecureConfig; + export abstract class Plug { - abstract name: string; - abstract dependencies: string[]; + abstract manifest: PlugManifest; execEnv( - env: ExecEnvEnv, + _env: ExecEnvEnv, ): Promise> | Record { return {}; } @@ -54,27 +76,12 @@ export abstract class Plug { env: ListBinPathsEnv, ): Promise | string[] { return [std_path.resolve(env.ASDF_INSTALL_PATH, "bin")]; - /* - const defaultBinPath = std_path.resolve(env.ASDF_INSTALL_PATH, "bin"); - const dirsToCheck = [defaultBinPath]; - while (dirsToCheck.length > 0) { - const dirPath = dirsToCheck.pop()!; - for await (const { isFile, name } of Deno.readDir(dirPath)) { - const path = std_path.resolve(dirPath, name); - if (!isFile) { - dirsToCheck.push(path); - continue; - } - Deno. - } - } */ } - abstract listAll(env: ListAllEnv): Promise | string[]; latestStable(env: ListAllEnv): Promise | string { return (async () => { logger().warning( - `using default implementation of latestStable for plug ${this.name}`, + `using default implementation of latestStable for plug ${this.manifest.name}`, ); const allVers = await this.listAll(env); if (allVers.length == 0) { @@ -84,6 +91,8 @@ export abstract class Plug { })(); } + abstract listAll(env: ListAllEnv): Promise | string[]; + abstract download(env: DownloadEnv): Promise | void; abstract install(env: InstallEnv): Promise | void; diff --git a/core/validators.ts b/core/validators.ts index 05f9b51d..ad42a71a 100644 --- a/core/validators.ts +++ b/core/validators.ts @@ -1,16 +1,20 @@ import { semver, zod } from "../deps/common.ts"; +const plugDep = zod.object({ + id: zod.string(), +}); + const plugManifestBase = zod.object({ name: zod.string().min(1), version: zod.string() .refine((str) => semver.parse(str), { - message: "not a valid semver", - }) - .transform(semver.parse), + message: "invalid semver string", + }), conflictResolution: zod .enum(["deferToNewer", "override"]) .nullish() .default("deferToNewer"), + deps: zod.array(plugDep).nullish(), }).passthrough(); const denoWorkerPlugManifest = plugManifestBase.merge( @@ -19,7 +23,25 @@ const denoWorkerPlugManifest = plugManifestBase.merge( }), ); +const ambientAccessPlugManifest = plugManifestBase.merge( + zod.object({ + execName: zod.string().min(1), + versionExtractFlag: zod.enum(["version", "-v", "--version", "-v"]), + versionExtractRegex: zod.string().refine((str) => new RegExp(str), { + message: "invalid RegExp string", + }), + versionExtractRegexFlags: zod.string().refine( + (str) => new RegExp("", str), + { + message: "invalid RegExp flags", + }, + ), + }), +); + export default { + plugDep, plugManifestBase, denoWorkerPlugManifest, + ambientAccessPlugManifest, }; diff --git a/core/worker.ts b/core/worker.ts index b4ba356b..e36efb84 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -1,7 +1,6 @@ //// /// -import { semver } from "../deps/common.ts"; import logger from "./logger.ts"; import { type DenoWorkerPlugManifestX, @@ -106,13 +105,10 @@ export function denoWorkerPlug

(plug: P) { // }[keyof T]; export class DenoWorkerPlug extends Plug { - name: string; - dependencies: string[]; - - constructor(public manifest: DenoWorkerPlugManifestX) { + constructor( + public manifest: DenoWorkerPlugManifestX, + ) { super(); - this.name = manifest.name; - this.dependencies = []; // TODO } /// This creates a new worker on every call @@ -120,7 +116,7 @@ export class DenoWorkerPlug extends Plug { req: WorkerReq, ): Promise { const worker = new Worker(this.manifest.moduleSpecifier, { - name: `${this.manifest.name}@${semver.format(this.manifest.version)}`, + name: `${this.manifest.name}@${this.manifest.version}`, type: "module", }); const promise = new Promise((resolve, reject) => { diff --git a/deno.jsonc b/deno.jsonc index 806f38e0..51879748 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 --allow-read --allow-env tests/*", + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker,which,ls,tar --allow-read --allow-env tests/*", "cache": "deno cache deps/*" } } diff --git a/deno.lock b/deno.lock index af6a76fc..7ddd4cbe 100644 --- a/deno.lock +++ b/deno.lock @@ -7,8 +7,37 @@ "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.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", diff --git a/deps/dev.ts b/deps/dev.ts index b3c0b3f2..59af4329 100644 --- a/deps/dev.ts +++ b/deps/dev.ts @@ -1,3 +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/mod.ts b/mod.ts index 42d65ad4..d56e2c37 100644 --- a/mod.ts +++ b/mod.ts @@ -1,16 +1,21 @@ //! This module is intended to be re-exported by `ghjk.ts` config scripts. Please -//! avoid importing elsewhere at it has side-ffects. +//! avoid importing elsewhere at it has side-effects. import { log } from "./deps/common.ts"; -import { type GhjkCtx } from "./core/mod.ts"; +import { type GhjkConfig } from "./core/mod.ts"; // this is only a shortcut for the cli import { runCli } from "./cli/mod.ts"; import logger from "./core/logger.ts"; +import { GhjkSecureConfig } from "./plug.ts"; +// we need to use global variables to allow +// plugins to access the config object. +// module imports wouldn't work as plugins might +// import a different version. declare global { interface Window { - ghjk: GhjkCtx; + ghjk: GhjkConfig; } } @@ -48,9 +53,17 @@ log.setup({ }, }); -export const ghjk = { - runCli: (args: string[]) => runCli(args, self.ghjk), +// freeze the object to prevent malicious tampering of the secureConfig +export const ghjk = Object.freeze({ + runCli: Object.freeze( + (args: string[], secureConfig: GhjkSecureConfig | undefined) => { + runCli(args, { + ...self.ghjk, + ...(secureConfig ?? { allowedPluginDeps: [] }), + }); + }, + ), cx: self.ghjk, -}; +}); export { logger }; diff --git a/plug.ts b/plug.ts index aec6bf51..c73e48e2 100644 --- a/plug.ts +++ b/plug.ts @@ -1,8 +1,10 @@ import { addInstall, + type AmbientAccessPlugManifest, type DenoWorkerPlugManifest, - type GhjkCtx, + type GhjkConfig, type InstallConfig, + registerAmbientPlug, registerDenoPlug, } from "./core/mod.ts"; import { log } from "./deps/plug.ts"; @@ -35,15 +37,15 @@ log.setup({ loggers: { // configure default logger available via short-hand methods above. default: { - level: "DEBUG", + level: "INFO", handlers: ["console"], }, ghjk: { - level: "DEBUG", + level: "INFO", handlers: ["console"], }, [self.name]: { - level: "DEBUG", + level: "INFO", handlers: ["console"], }, }, @@ -51,23 +53,33 @@ log.setup({ declare global { interface Window { - ghjk: GhjkCtx; + // this is null except when from from `ghjk.ts` + // i.e. a deno worker plug context won't have it avail + ghjk: GhjkConfig; } } export function registerDenoPlugGlobal( manifestUnclean: DenoWorkerPlugManifest, ) { - // make sure we're not running in a Worker first - if (!self.name) { + if (self.ghjk) { + if (self.name) throw new Error("impossible"); registerDenoPlug(self.ghjk, manifestUnclean); } } +export function registerAmbientPlugGlobal( + manifestUnclean: AmbientAccessPlugManifest, +) { + if (self.ghjk) { + registerAmbientPlug(self.ghjk, manifestUnclean); + } +} + export function addInstallGlobal( config: InstallConfig, ) { - if (!self.name) { + if (self.ghjk) { addInstall(self.ghjk, config); } } diff --git a/plugs/node.ts b/plugs/node.ts index 6d84446d..67711681 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -24,8 +24,7 @@ const manifest = { denoWorkerPlug( new class extends Plug { - name = "node"; - dependencies = []; + manifest = manifest; execEnv(env: ExecEnvEnv) { return { diff --git a/plugs/tar.ts b/plugs/tar.ts new file mode 100644 index 00000000..49f808b5 --- /dev/null +++ b/plugs/tar.ts @@ -0,0 +1,21 @@ +import { + addInstallGlobal, + type AmbientAccessPlugManifest, + registerAmbientPlugGlobal, +} from "../plug.ts"; + +export const manifest: AmbientAccessPlugManifest = { + name: "tar_aa", + version: "0.1.0", + execName: "tar", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPlugGlobal(manifest); +export default function tar() { + addInstallGlobal({ + plugName: manifest.name, + }); +} diff --git a/tests/ambient.ts b/tests/ambient.ts new file mode 100644 index 00000000..f2e51cbb --- /dev/null +++ b/tests/ambient.ts @@ -0,0 +1,24 @@ +import { std_assert } from "../deps/dev.ts"; +import { AmbientAccessPlug } from "../core/ambient.ts"; +import { type AmbientAccessPlugManifest } from "../core/types.ts"; + +import * as tar from "../plugs/tar.ts"; + +const manifests = [ + { + name: "ls", + execName: "ls", + version: "0.1.0", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", + }, + tar.manifest, +]; +for (const manifest of manifests) { + Deno.test(`ambient access ${manifest.name}`, async () => { + const plug = new AmbientAccessPlug(manifest as AmbientAccessPlugManifest); + const versions = await plug.listAll({}); + std_assert.assertEquals(versions.length, 1); + }); +} diff --git a/tests/utils.ts b/tests/e2e.ts similarity index 85% rename from tests/utils.ts rename to tests/e2e.ts index 54966c2f..e9ac3609 100644 --- a/tests/utils.ts +++ b/tests/e2e.ts @@ -6,14 +6,14 @@ type TestCase = { imports: string; confFn: string | (() => Promise); envs?: Record; - epoint: string; + ePoint: string; }; async function dockerTest(cases: TestCase[]) { // 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( + const dFileTemplate = await Deno.readTextFile( import.meta.resolve("./test.Dockerfile").slice(6), ); const templateStrings = { @@ -21,7 +21,7 @@ async function dockerTest(cases: TestCase[]) { }; const defaultEnvs: Record = {}; - for (const { name, envs: testEnvs, confFn, epoint, imports } of cases) { + for (const { name, envs: testEnvs, confFn, ePoint, imports } of cases) { Deno.test(`dockerTest - ${name}`, async () => { const tag = `ghjk_test_${name}`; const env = { @@ -33,7 +33,7 @@ ${imports.replaceAll("$ghjk", "/ghjk/")} await (${confFn.toString()})()`; - const dFile = dfileTemplate.replaceAll( + const dFile = dFileTemplate.replaceAll( templateStrings.addConfig, configFile, ); @@ -55,7 +55,10 @@ await (${confFn.toString()})()`; ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) .flat(), tag, - ...epoint.split(/\s/), + "bash", + "-c", + "-i", + ...ePoint.split(/\s/), ], { env }); await spawn([ ...dockerCmd, @@ -72,5 +75,5 @@ await dockerTest([{ confFn: `async () => { node({ version: "lts" }); }`, - epoint: `echo yes`, + ePoint: `node --version`, }]); From 40c106ac4510e79d9a6270220b29fe9639c86613 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 20 Nov 2023 01:42:16 +0300 Subject: [PATCH 06/43] feat: plugin deps --- README.md | 1 + cli/hooks.ts | 3 +- cli/sync.ts | 415 ++++++++++++++++++++++++++++++++------------- cli/utils.ts | 97 ----------- core/ambient.ts | 23 ++- core/mod.ts | 29 ++-- core/types.ts | 60 ++++--- core/utils.ts | 91 ++++++++++ core/validators.ts | 1 + core/worker.ts | 52 ++++-- mod.ts | 35 +++- plug.ts | 124 ++++++++++---- plugs/jco.ts | 16 +- plugs/node.ts | 137 +++++++++------ plugs/rust.ts | 16 +- std.ts | 30 ++++ tests/e2e.ts | 4 +- 17 files changed, 756 insertions(+), 378 deletions(-) create mode 100644 core/utils.ts create mode 100644 std.ts diff --git a/README.md b/README.md index aa03e093..258a33db 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ and looks as follows (abstracting away some implementation details): - mold({ if: Deno.build.os === "Macos" }) - hash verifiable dependencies (timestamp) - hide the `Deno` object in an abstraction +- support windows ## design considerations diff --git a/cli/hooks.ts b/cli/hooks.ts index 41bca575..13086a06 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -1,5 +1,6 @@ import { std_path } from "../deps/cli.ts"; -import { ChildError, dirs, runAndReturn } from "./utils.ts"; +import { dirs } from "./utils.ts"; +import { runAndReturn } from "../core/utils.ts"; // null means it should be removed (for cleaning up old versions) const vfs = { diff --git a/cli/sync.ts b/cli/sync.ts index 3a9a35dc..c5ff39e0 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,8 +1,18 @@ import { Command, std_fs, std_path } from "../deps/cli.ts"; import logger from "../core/logger.ts"; -import { DenoWorkerPlugManifestX, GhjkCtx } from "../core/mod.ts"; +import { + type AmbientAccessPlugManifestX, + type DenoWorkerPlugManifestX, + type DepShims, + getInstallId, + type GhjkCtx, + type InstallConfig, + type PlugArgsBase, + type RegisteredPlug, +} from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; -import { AVAIL_CONCURRENCY, dbg, dirs } from "./utils.ts"; +import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; +import { AmbientAccessPlug } from "../core/ambient.ts"; async function findConfig(path: string): Promise { let current = path; @@ -49,18 +59,22 @@ export class SyncCommand extends Command { 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; - } + .action(() => sync(cx)); + } +} + +export async function sync(cx: GhjkCtx) { + const config = await findConfig(Deno.cwd()); + console.log(config); + if (!config) { + console.log("ghjk did not find any `ghjk.ts` config."); + return; + } - const envDir = envDirFromConfig(config); - logger().debug({ envDir }); + const envDir = envDirFromConfig(config); + logger().debug({ envDir }); - /* for (const [name, { ty, manifest }] of cx.plugs) { + /* for (const [name, { ty, manifest }] of cx.plugs) { if (ty == "denoWorker") { const plug = new DenoWorkerPlug( manifest as DenoWorkerPlugManifestX, @@ -73,112 +87,283 @@ export class SyncCommand extends Command { ); } } */ - let env = {}; - for (const inst of cx.installs) { - const regPlug = cx.plugs.get(inst.plugName); - if (!regPlug) { - throw Error( - `unable to find plugin "${inst.plugName}" specified by install ${ - JSON.stringify(inst) - }`, - ); - } - const { ty: plugType, manifest } = regPlug; - let plug; - if (plugType == "denoWorker") { - plug = new DenoWorkerPlug( - manifest as DenoWorkerPlugManifestX, - ); - } else { + + 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 regPlug = cx.plugs.get(inst.plugName) ?? + cx.allowedDeps.get(inst.plugName)!; + const { manifest } = regPlug; + 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 depPlug = cx.allowedDeps.get(depId.id)!; + const depInstall = { + plugName: depPlug.manifest.name, + }; + const depInstallId = getInstallId(depInstall); + const depArtifacts = artifacts.get(depInstallId); + if (!depArtifacts) { + throw Error( + `artifacts not found for plug dep "${depInstallId}" when installing "${installId}"`, + ); + } + const depShimDir = std_path.resolve(depShimsRootPath, installId); + await Deno.mkdir(depShimDir); + const { binPaths, installPath } = depArtifacts; + depShims[depId.id] = await shimLinkPaths( + binPaths, + installPath, + depShimDir, + ); + } + + const thisArtifacts = await doInstall(envDir, inst, regPlug, depShims); + 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); + } + } + } + // create shims for the environment + const shimDir = std_path.resolve(envDir, "shims"); + await Deno.mkdir(shimDir, { recursive: true }); + for (const instId of installs.user) { + const { binPaths, installPath } = artifacts.get(instId)!; + void await shimLinkPaths(binPaths, installPath, shimDir); + } + + // 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 Error( + `duplicate env var found ${key} from installs ${instId} & ${ + conflict[1] + }`, + ); + } + env[key] = [val, instId]; + } + } + // FIXME: prevent malicious env manipulations + await writeLoader( + envDir, + Object.fromEntries( + Object.entries(env).map(([key, [val, _]]) => [key, val]), + ), + ); +} +function buildInstallGraph(cx: GhjkCtx) { + 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 Error(`duplicate install found by plugin ${inst.plugName}`); + } + installs.user.add(instId); + foundInstalls.push(inst); + } + + while (foundInstalls.length > 0) { + const inst = foundInstalls.pop()!; + const regPlug = cx.plugs.get(inst.plugName) ?? + cx.allowedDeps.get(inst.plugName); + if (!regPlug) { + throw Error( + `unable to find plugin "${inst.plugName}" 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); + + const { manifest } = regPlug; + if (!manifest.deps || manifest.deps.length == 0) { + installs.indie.push(installId); + } else { + const deps = []; + for (const depId of manifest.deps) { + const depPlug = cx.allowedDeps.get(depId.id); + if (!depPlug) { + throw Error( + `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, + ); + } + const depInstall = { + plugName: depPlug.manifest.name, + }; + const depInstallId = getInstallId(depInstall); + + // check for cycles + { + const thisDeps = installs.revDepEdges.get(installId); + if (thisDeps && thisDeps.includes(depInstallId)) { throw Error( - `unsupported plugin type "${plugType}": ${ - JSON.stringify(manifest) - }`, + `cyclic dependency detected between "${installId}" and "${depInstallId}"`, ); } - const installVersion = inst.version ?? await plug.latestStable({}); - const installPath = std_path.resolve( - envDir, - "installs", - plug.manifest.name, - installVersion, - ); - const downloadPath = std_path.resolve( - envDir, - "downloads", - plug.manifest.name, - installVersion, - ); - logger().debug("creating dirs", { installPath, downloadPath }); - await Promise.allSettled( - [ - Deno.mkdir(installPath, { recursive: true }), - Deno.mkdir(downloadPath, { recursive: true }), - ], - ); - { - logger().info(`downloading ${inst.plugName}:${installVersion}`); - const tmpDirPath = await Deno.makeTempDir({ - prefix: `ghjk_download_${inst.plugName}@${installVersion}_`, - }); - await plug.download({ - ASDF_INSTALL_PATH: installPath, - ASDF_INSTALL_TYPE: "version", - ASDF_INSTALL_VERSION: installVersion, - ASDF_DOWNLOAD_PATH: downloadPath, - tmpDirPath, - }); - void Deno.remove(tmpDirPath, { recursive: true }); - } - { - logger().info(`installing ${inst.plugName}:${installVersion}`); - const tmpDirPath = await Deno.makeTempDir({ - prefix: `ghjk_install_${inst.plugName}@${installVersion}_`, - }); - await plug.install({ - ASDF_INSTALL_PATH: installPath, - ASDF_INSTALL_TYPE: "version", - ASDF_INSTALL_VERSION: installVersion, - ASDF_CONCURRENCY: AVAIL_CONCURRENCY, - ASDF_DOWNLOAD_PATH: downloadPath, - tmpDirPath, - }); - void Deno.remove(tmpDirPath, { recursive: true }); - } - const shimDir = std_path.resolve(envDir, "shims"); - await Deno.mkdir(shimDir, { recursive: true }); - for ( - const bin of dbg( - await plug.listBinPaths({ - ASDF_INSTALL_PATH: installPath, - ASDF_INSTALL_TYPE: "version", - ASDF_INSTALL_VERSION: installVersion, - }), - ) - ) { - const binPath = std_path.resolve(installPath, bin); - const binName = std_path.basename(binPath); // TODO: aliases - const shimPath = std_path.resolve(shimDir, binName); - try { - await Deno.remove(shimPath); - } catch (error) { - if (!(error instanceof Deno.errors.NotFound)) { - throw error; - } - } - await Deno.symlink(binPath, shimPath, { type: "file" }); - } - // FIXME: prevent malicious env manipulations - env = { - ...env, - ...await plug.execEnv({ - ASDF_INSTALL_PATH: installPath, - ASDF_INSTALL_TYPE: "version", - ASDF_INSTALL_VERSION: installVersion, - }), - }; } - await writeLoader(envDir, env); - }); + + 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( + binPaths: string[], + installPath: string, + shimDir: string, +) { + const shims: Record = {}; + for ( + const bin of binPaths + ) { + const binPath = std_path.resolve(installPath, bin); + const binName = std_path.basename(binPath); // TODO: aliases + const shimPath = std_path.resolve(shimDir, binName); + try { + await Deno.remove(shimPath); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) { + throw error; + } + } + await Deno.symlink(binPath, shimPath, { type: "file" }); + shims[binName] = shimPath; + } + return shims; +} + +type DePromisify = T extends Promise ? Inner : T; +type InstallArtifacts = DePromisify>; + +async function doInstall( + envDir: string, + inst: InstallConfig, + regPlug: RegisteredPlug, + depShims: DepShims, +) { + const { ty: plugType, manifest } = regPlug; + let plug; + if (plugType == "denoWorker") { + plug = new DenoWorkerPlug( + manifest as DenoWorkerPlugManifestX, + ); + } else if (plugType == "ambientAccess") { + plug = new AmbientAccessPlug( + manifest as AmbientAccessPlugManifestX, + ); + } else { + throw Error( + `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, + ); + } + const installVersion = inst.version ?? await plug.latestStable({}); + const installPath = std_path.resolve( + envDir, + "installs", + plug.manifest.name, + installVersion, + ); + const downloadPath = std_path.resolve( + envDir, + "downloads", + plug.manifest.name, + installVersion, + ); + const baseArgs: PlugArgsBase = { + installPath: installPath, + // installType: "version", + installVersion: installVersion, + depShims, + platform: Deno.build, + }; + { + logger().info(`downloading ${inst.plugName}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_download_${inst.plugName}@${installVersion}_`, + }); + await plug.download({ + ...baseArgs, + downloadPath: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + { + logger().info(`installing ${inst.plugName}:${installVersion}`); + const tmpDirPath = await Deno.makeTempDir({ + prefix: `ghjk_install_${inst.plugName}@${installVersion}_`, + }); + await plug.install({ + ...baseArgs, + availConcurrency: AVAIL_CONCURRENCY, + downloadPath: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); } + const binPaths = await plug.listBinPaths({ + ...baseArgs, + }); + const env = await plug.execEnv({ + ...baseArgs, + }); + return { env, binPaths, installPath, downloadPath }; } diff --git a/cli/utils.ts b/cli/utils.ts index 238571d0..e8df1e09 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -1,101 +1,4 @@ import { Err, Ok, Result } from "../deps/cli.ts"; -import logger from "../core/logger.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 async function runAndReturn( - cmd: string[], - options: { - cwd?: string; - env?: Record; - } = {}, -): Promise { - const { cwd, env } = { - ...options, - }; - const output = await new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - env, - }).output(); - - if (output.success) { - return new TextDecoder().decode(output.stdout); - } - throw new ChildError(output.code, new TextDecoder().decode(output.stderr)); -} - -export async function spawn( - cmd: string[], - options: { - cwd?: string; - env?: Record; - pipeInput?: string; - } = {}, -) { - const { cwd, env, pipeInput } = { - ...options, - }; - logger().debug("spawning", cmd); - const child = new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - ...(pipeInput - ? { - stdin: "piped", - } - : {}), - env, - }).spawn(); - - if (self.name) { - child.stdout.pipeTo( - new WritableStream({ - write(chunk) { - console.log(new TextDecoder().decode(chunk)); - }, - }), - ); - child.stderr.pipeTo( - new WritableStream({ - write(chunk) { - console.error(new TextDecoder().decode(chunk)); - }, - }), - ); - } else { - // keep pipe asynchronous till the command exists - void child.stdout.pipeTo(Deno.stdout.writable, { preventClose: true }); - void child.stderr.pipeTo(Deno.stderr.writable, { preventClose: true }); - } - - 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 Error(`child failed with code ${code}`); - } -} function home_dir(): string | null { switch (Deno.build.os) { diff --git a/core/ambient.ts b/core/ambient.ts index b3a9f2eb..82145dc7 100644 --- a/core/ambient.ts +++ b/core/ambient.ts @@ -1,12 +1,12 @@ import { type AmbientAccessPlugManifest, - type DownloadEnv, - type InstallEnv, + type DownloadArgs, + type InstallArgs, type ListAllEnv, - type ListBinPathsEnv, + type ListBinPathsArgs, Plug, } from "./types.ts"; -import { ChildError, runAndReturn } from "../cli/utils.ts"; +import { ChildError, runAndReturn } from "./utils.ts"; export class AmbientAccessPlug extends Plug { constructor(public manifest: AmbientAccessPlugManifest) { @@ -17,7 +17,7 @@ export class AmbientAccessPlug extends Plug { ); } } - async listAll(_env: ListAllEnv): Promise { + async latestStable(_env: ListAllEnv): Promise { const execPath = await this.pathToExec(); let versionOut; try { @@ -46,19 +46,24 @@ export class AmbientAccessPlug extends Plug { `error trying extract version for "${this.manifest.name}@${this.manifest.version}" using regex ${extractionRegex} from output: ${versionOut}`, ); } - return [matches[0]]; + + return matches[0]; + } + + async listAll(env: ListAllEnv): Promise { + return [await this.latestStable(env)]; } async listBinPaths( - _env: ListBinPathsEnv, + _env: ListBinPathsArgs, ): Promise { return [await this.pathToExec()]; } - download(_env: DownloadEnv): void | Promise { + download(_env: DownloadArgs): void | Promise { // no op } - install(_env: InstallEnv): void | Promise { + install(_env: InstallArgs): void | Promise { // no op } async pathToExec(): Promise { diff --git a/core/mod.ts b/core/mod.ts index 1db8b83a..7956c8a7 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,11 +1,11 @@ export * from "./types.ts"; import { semver } from "../deps/common.ts"; -import { - type AmbientAccessPlugManifest, - type DenoWorkerPlugManifest, - type GhjkConfig, - type InstallConfig, - type PlugManifestX, +import type { + AmbientAccessPlugManifest, + DenoWorkerPlugManifest, + GhjkConfig, + InstallConfig, + RegisteredPlug, } from "./types.ts"; import validators from "./validators.ts"; import logger from "./logger.ts"; @@ -14,12 +14,15 @@ export const Ghjk = { cwd: Deno.cwd, }; +export function getInstallId(install: InstallConfig) { + return install.plugName; +} export function registerDenoPlug( cx: GhjkConfig, manifestUnclean: DenoWorkerPlugManifest, ) { const manifest = validators.denoWorkerPlugManifest.parse(manifestUnclean); - registerPlug(cx, "denoWorker", manifest); + registerPlug(cx, { ty: "denoWorker", manifest }); } export function registerAmbientPlug( @@ -27,14 +30,14 @@ export function registerAmbientPlug( manifestUnclean: AmbientAccessPlugManifest, ) { const manifest = validators.ambientAccessPlugManifest.parse(manifestUnclean); - registerPlug(cx, "ambientAccess", manifest); + registerPlug(cx, { ty: "ambientAccess", manifest }); } export function registerPlug( cx: GhjkConfig, - ty: "denoWorker" | "ambientAccess", - manifest: PlugManifestX, + plug: RegisteredPlug, ) { + const { manifest } = plug; const conflict = cx.plugs.get(manifest.name)?.manifest; if (conflict) { if ( @@ -56,7 +59,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, { ty, manifest }); + cx.plugs.set(manifest.name, plug); } else if ( semver.compare( semver.parse(manifest.version), @@ -77,7 +80,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, { ty, manifest }); + cx.plugs.set(manifest.name, plug); } else { logger().debug("plug rejected due after defer", { retained: conflict, @@ -86,7 +89,7 @@ export function registerPlug( } } else { logger().debug("plug registered", manifest); - cx.plugs.set(manifest.name, { ty, manifest }); + cx.plugs.set(manifest.name, plug); } } diff --git a/core/types.ts b/core/types.ts index 01cc51d0..6f71331d 100644 --- a/core/types.ts +++ b/core/types.ts @@ -34,10 +34,14 @@ export type PlugManifestX = | DenoWorkerPlugManifestX | AmbientAccessPlugManifestX; -type RegisteredPlug = { - ty: "denoWorker" | "ambientAccess"; - manifest: PlugManifestX; +export type RegisteredPlug = { + ty: "ambientAccess"; + manifest: AmbientAccessPlugManifestX; +} | { + ty: "denoWorker"; + manifest: DenoWorkerPlugManifestX; }; + export type RegisteredPlugs = Map; export interface InstallConfigBase { @@ -50,6 +54,7 @@ export type InstallConfig = InstallConfigBase & { }; export interface GhjkConfig { + /// Plugs explicitly added by the user plugs: RegisteredPlugs; installs: InstallConfig[]; } @@ -58,24 +63,27 @@ export interface GhjkConfig { /// from the config script instead of the global variable approach the /// main [`GhjkConfig`] can take. export interface GhjkSecureConfig { - allowedPluginDeps: PlugDep[]; + allowedPluginDeps?: PlugDep[]; } -export type GhjkCtx = GhjkConfig & GhjkSecureConfig; +export type GhjkCtx = GhjkConfig & { + /// Standard plugs allowed to be use as deps by other plugs + allowedDeps: RegisteredPlugs; +}; export abstract class Plug { abstract manifest: PlugManifest; execEnv( - _env: ExecEnvEnv, + _env: ExecEnvArgs, ): Promise> | Record { return {}; } listBinPaths( - env: ListBinPathsEnv, + env: ListBinPathsArgs, ): Promise | string[] { - return [std_path.resolve(env.ASDF_INSTALL_PATH, "bin")]; + return [std_path.resolve(env.installPath, "bin")]; } latestStable(env: ListAllEnv): Promise | string { @@ -93,9 +101,9 @@ export abstract class Plug { abstract listAll(env: ListAllEnv): Promise | string[]; - abstract download(env: DownloadEnv): Promise | void; + abstract download(env: DownloadArgs): Promise | void; - abstract install(env: InstallEnv): Promise | void; + abstract install(env: InstallArgs): Promise | void; } interface ASDF_CONFIG_EXAMPLE { @@ -110,28 +118,38 @@ interface ASDF_CONFIG_EXAMPLE { 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 type DepShims = Record< + string, + Record +>; + +export type PlatformInfo = Omit; + +export interface PlugArgsBase { + // installType: "version" | "ref"; + installVersion: string; + installPath: string; + depShims: DepShims; + platform: PlatformInfo; } export interface ListAllEnv { } -export interface ListBinPathsEnv extends BinDefaultEnv { +export interface ListBinPathsArgs extends PlugArgsBase { } -export interface ExecEnvEnv extends BinDefaultEnv { +export interface ExecEnvArgs extends PlugArgsBase { } -export interface DownloadEnv extends BinDefaultEnv { - ASDF_DOWNLOAD_PATH: string; +export interface DownloadArgs extends PlugArgsBase { + downloadPath: string; tmpDirPath: string; } -export interface InstallEnv extends BinDefaultEnv { - ASDF_CONCURRENCY: number; - ASDF_DOWNLOAD_PATH: string; +export interface InstallArgs extends PlugArgsBase { + availConcurrency: number; + downloadPath: string; tmpDirPath: string; } diff --git a/core/utils.ts b/core/utils.ts new file mode 100644 index 00000000..99ddb50f --- /dev/null +++ b/core/utils.ts @@ -0,0 +1,91 @@ +import logger from "./logger.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 async function runAndReturn( + cmd: string[], + options: { + cwd?: string; + env?: Record; + } = {}, +): Promise { + const { cwd, env } = { + ...options, + }; + const output = await new Deno.Command(cmd[0], { + args: cmd.slice(1), + cwd, + stdout: "piped", + stderr: "piped", + env, + }).output(); + + if (output.success) { + return new TextDecoder().decode(output.stdout); + } + throw new ChildError( + output.code, + new TextDecoder().decode(output.stdout) + "\n" + + new TextDecoder().decode(output.stderr), + ); +} + +export type SpawnOptions = { + cwd?: string; + env?: Record; + pipeInput?: string; + pipeOut?: WritableStream; + pipeErr?: WritableStream; +}; + +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, + stdout: "piped", + stderr: "piped", + ...(pipeInput + ? { + stdin: "piped", + } + : {}), + env, + }).spawn(); + + // keep pipe asynchronous till the command exists + void child.stdout.pipeTo(options.pipeOut ?? Deno.stdout.writable, { + preventClose: true, + }); + void child.stderr.pipeTo(options.pipeErr ?? Deno.stderr.writable, { + preventClose: true, + }); + + 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 Error(`child failed with code ${code}`); + } +} diff --git a/core/validators.ts b/core/validators.ts index ad42a71a..67047e19 100644 --- a/core/validators.ts +++ b/core/validators.ts @@ -36,6 +36,7 @@ const ambientAccessPlugManifest = plugManifestBase.merge( message: "invalid RegExp flags", }, ), + // TODO: custom shell shims }), ); diff --git a/core/worker.ts b/core/worker.ts index e36efb84..191a295c 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -4,14 +4,42 @@ import logger from "./logger.ts"; import { type DenoWorkerPlugManifestX, - type DownloadEnv, - type ExecEnvEnv, - type InstallEnv, + type DownloadArgs, + type ExecEnvArgs, + type InstallArgs, type ListAllEnv, - type ListBinPathsEnv, + type ListBinPathsArgs, Plug, } from "./types.ts"; +import { spawn, type SpawnOptions } from "./utils.ts"; +export function isWorker() { + return !!self.name; +} + +export function workerSpawn( + cmd: string[], + options: Omit = {}, +) { + const outDecoder = new TextDecoderStream(); + const errDecoder = new TextDecoderStream(); + outDecoder.readable.pipeTo( + new WritableStream({ + write: console.log, + }), + ); + errDecoder.readable.pipeTo( + new WritableStream({ + write: console.error, + }), + ); + return spawn(cmd, { + ...options, + pipeOut: outDecoder.writable, + pipeErr: errDecoder.writable, + }); +} + type WorkerReq = { ty: "listAll"; arg: ListAllEnv; @@ -20,16 +48,16 @@ type WorkerReq = { arg: ListAllEnv; } | { ty: "execEnv"; - arg: ExecEnvEnv; + arg: ExecEnvArgs; } | { ty: "download"; - arg: DownloadEnv; + arg: DownloadArgs; } | { ty: "install"; - arg: InstallEnv; + arg: InstallArgs; } | { ty: "listBinPaths"; - arg: ListBinPathsEnv; + arg: ListBinPathsArgs; }; type WorkerResp = { @@ -163,7 +191,7 @@ export class DenoWorkerPlug extends Plug { } async execEnv( - env: ExecEnvEnv, + env: ExecEnvArgs, ): Promise> { const req: WorkerReq = { ty: "execEnv", @@ -176,7 +204,7 @@ export class DenoWorkerPlug extends Plug { throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } async listBinPaths( - env: ListBinPathsEnv, + env: ListBinPathsArgs, ): Promise { const req: WorkerReq = { ty: "listBinPaths", @@ -188,7 +216,7 @@ export class DenoWorkerPlug extends Plug { } throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async download(env: DownloadEnv): Promise { + async download(env: DownloadArgs): Promise { const req: WorkerReq = { ty: "download", arg: env, @@ -199,7 +227,7 @@ export class DenoWorkerPlug extends Plug { } throw Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async install(env: InstallEnv): Promise { + async install(env: InstallArgs): Promise { const req: WorkerReq = { ty: "install", arg: env, diff --git a/mod.ts b/mod.ts index d56e2c37..a6c383b1 100644 --- a/mod.ts +++ b/mod.ts @@ -8,6 +8,7 @@ import { type GhjkConfig } from "./core/mod.ts"; import { runCli } from "./cli/mod.ts"; import logger from "./core/logger.ts"; import { GhjkSecureConfig } from "./plug.ts"; +import * as std_plugs from "./std.ts"; // we need to use global variables to allow // plugins to access the config object. @@ -53,16 +54,34 @@ log.setup({ }, }); +function runCliShim( + args: string[], + secureConfig: GhjkSecureConfig | undefined, +) { + let allowedDeps; + if (secureConfig?.allowedPluginDeps) { + allowedDeps = new Map(); + for (const depId of secureConfig.allowedPluginDeps) { + const regPlug = std_plugs.map.get(depId.id); + if (!regPlug) { + throw Error( + `unrecognized dep "${depId.id}" found in "allowedPluginDeps"`, + ); + } + allowedDeps.set(depId.id, regPlug); + } + } else { + allowedDeps = new Map(std_plugs.map.entries()); + } + runCli(args, { + ...self.ghjk, + allowedDeps, + }); +} + // freeze the object to prevent malicious tampering of the secureConfig export const ghjk = Object.freeze({ - runCli: Object.freeze( - (args: string[], secureConfig: GhjkSecureConfig | undefined) => { - runCli(args, { - ...self.ghjk, - ...(secureConfig ?? { allowedPluginDeps: [] }), - }); - }, - ), + runCli: Object.freeze(runCliShim), cx: self.ghjk, }); diff --git a/plug.ts b/plug.ts index c73e48e2..39caf850 100644 --- a/plug.ts +++ b/plug.ts @@ -2,54 +2,61 @@ import { addInstall, type AmbientAccessPlugManifest, type DenoWorkerPlugManifest, + type DepShims, + type DownloadArgs, type GhjkConfig, type InstallConfig, + type PlugDep, registerAmbientPlug, registerDenoPlug, } from "./core/mod.ts"; -import { log } from "./deps/plug.ts"; +import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; +import { isWorker } from "./core/worker.ts"; +import logger from "./core/logger.ts"; export * from "./core/mod.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; -export { denoWorkerPlug } from "./core/worker.ts"; +export { denoWorkerPlug, isWorker, workerSpawn } from "./core/worker.ts"; export type * from "./core/mod.ts"; -log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("DEBUG", { - formatter: (lr) => { - let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; +if (isWorker()) { + log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("NOTSET", { + formatter: (lr) => { + let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; - lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; - }); - // if (lr.args.length > 0) { - // msg += JSON.stringify(lr.args); - // } + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + // if (lr.args.length > 0) { + // msg += JSON.stringify(lr.args); + // } - return msg; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), - }, - - loggers: { - // configure default logger available via short-hand methods above. - default: { - level: "INFO", - handlers: ["console"], - }, - ghjk: { - level: "INFO", - handlers: ["console"], + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), }, - [self.name]: { - level: "INFO", - handlers: ["console"], + + loggers: { + // configure default logger available via short-hand methods above. + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + [self.name]: { + level: "DEBUG", + handlers: ["console"], + }, }, - }, -}); + }); +} declare global { interface Window { @@ -63,7 +70,7 @@ export function registerDenoPlugGlobal( manifestUnclean: DenoWorkerPlugManifest, ) { if (self.ghjk) { - if (self.name) throw new Error("impossible"); + if (isWorker()) throw new Error("impossible"); registerDenoPlug(self.ghjk, manifestUnclean); } } @@ -83,3 +90,52 @@ export function addInstallGlobal( addInstall(self.ghjk, config); } } + +export function depBinShimPath( + dep: PlugDep, + binName: string, + depShims: DepShims, +) { + const shimPaths = depShims[dep.id]; + if (!shimPaths) { + throw Error(`unable to find shims for dep ${dep.id}`); + } + const path = shimPaths[binName]; + if (!path) { + throw Error( + `unable to find shim path for bin "${binName}" of dep ${dep.id}`, + ); + } + return path; +} + +/// This avoid re-downloading a file if it's already successfully downloaded before. +export async function downloadFile( + env: DownloadArgs, + url: string, + fileName = std_url.basename(url), +) { + 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); + const dest = await Deno.open( + tmpFilePath, + { create: true, truncate: true, write: true }, + ); + 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/plugs/jco.ts b/plugs/jco.ts index 10ba34fc..381bc69f 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -1,9 +1,9 @@ import { - DownloadEnv, - ExecEnvEnv, - InstallEnv, + DownloadArgs, + ExecEnvArgs, + InstallArgs, ListAllEnv, - ListBinPathsEnv, + ListBinPathsArgs, Plug, } from "../plug.ts"; @@ -12,12 +12,12 @@ export function jco() { name = "jco"; dependencies = ["node"]; - execEnv(env: ExecEnvEnv) { + execEnv(env: ExecEnvArgs) { throw new Error("Method not implemented."); return {}; } - listBinPaths(env: ListBinPathsEnv) { + listBinPaths(env: ListBinPathsArgs) { throw new Error("Method not implemented."); return {}; } @@ -34,11 +34,11 @@ export function jco() { return versions; } - download(env: DownloadEnv) { + download(env: DownloadArgs) { throw new Error("Method not implemented."); } - install(env: InstallEnv) { + install(env: InstallArgs) { /* npm install -g @bytecodealliance/jco or diff --git a/plugs/node.ts b/plugs/node.ts index 67711681..35c6c628 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -1,38 +1,60 @@ import { addInstallGlobal, denoWorkerPlug, - DownloadEnv, - ExecEnvEnv, + depBinShimPath, + DownloadArgs, + downloadFile, + ExecEnvArgs, + InstallArgs, type InstallConfigBase, - InstallEnv, ListAllEnv, - ListBinPathsEnv, + ListBinPathsArgs, logger, + type PlatformInfo, Plug, registerDenoPlugGlobal, + removeFile, std_fs, std_path, std_url, + workerSpawn, } from "../plug.ts"; -import { spawn } from "../cli/utils.ts"; +import * as std_plugs from "../std.ts"; const manifest = { name: "node", version: "0.1.0", moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], }; +// FIXME: improve multi platform support story +if (Deno.build.os != "darwin" && Deno.build.os != "linux") { + throw Error(`unsupported os: ${Deno.build.os}`); +} + +registerDenoPlugGlobal(manifest); + +export default function node({ version }: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + version, + }); +} + denoWorkerPlug( new class extends Plug { manifest = manifest; - execEnv(env: ExecEnvEnv) { + execEnv(args: ExecEnvArgs) { return { - NODE_PATH: env.ASDF_INSTALL_PATH, + NODE_PATH: args.installPath, }; } - listBinPaths(env: ListBinPathsEnv) { + listBinPaths(_args: ListBinPathsArgs) { return [ "bin/node", "bin/npm", @@ -40,68 +62,83 @@ denoWorkerPlug( ]; } - async listAll(env: ListAllEnv) { + async latestStable(_args: ListAllEnv): Promise { + const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); + const metadata = await metadataRequest.json(); + + if (!Array.isArray(metadata)) { + throw Error("invalid data received from index"); + } + return metadata.find((ver) => ver.lts).version; + } + + async 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(); + logger().debug(versions); return versions; } - async download(env: DownloadEnv) { - // TODO: download file - const url = - `https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-arm64.tar.gz`; - const fileName = std_url.basename(url); - - const tmpFilePath = std_path.resolve( - env.tmpDirPath, - fileName, - ); + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } - const resp = await fetch(url); - const dest = await Deno.open( - tmpFilePath, - { create: true, truncate: true, write: true }, - ); - await resp.body!.pipeTo(dest.writable, { preventClose: false }); - await Deno.copyFile( - tmpFilePath, - std_path.resolve(env.ASDF_DOWNLOAD_PATH, fileName), + async install(args: InstallArgs) { + const fileName = std_url.basename( + downloadUrl(args.installVersion, args.platform), ); - } + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - async install(env: InstallEnv) { - const fileName = "node-v21.1.0-darwin-arm64.tar.gz"; - await spawn([ - "tar", + await workerSpawn([ + depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", - std_path.resolve( - env.ASDF_DOWNLOAD_PATH, - fileName, - ), - `--directory=${env.tmpDirPath}`, + fileDwnPath, + `--directory=${args.tmpDirPath}`, ]); - await Deno.remove(env.ASDF_INSTALL_PATH, { recursive: true }); - // FIXME: use Deno.rename when https://github.com/denoland/deno/pull/19879 is merged + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } + await std_fs.copy( std_path.resolve( - env.tmpDirPath, - fileName.replace(/\.tar\.gz$/, ""), + args.tmpDirPath, + std_path.basename(fileDwnPath, ".tar.xz"), ), - env.ASDF_INSTALL_PATH, + args.installPath, ); } }(), ); -registerDenoPlugGlobal(manifest); - -export default function node({ version }: InstallConfigBase = {}) { - addInstallGlobal({ - plugName: manifest.name, - version, - }); +function downloadUrl(installVersion: string, platform: PlatformInfo) { + // TODO: download file + let arch; + let os; + switch (platform.arch) { + case "x86_64": + arch = "x64"; + break; + case "aarch64": + arch = "arm64"; + break; + default: + throw Error(`unsupported arch: ${platform.arch}`); + } + switch (platform.os) { + case "linux": + os = "linux"; + break; + case "darwin": + os = "darwin"; + break; + default: + throw Error(`unsupported os: ${platform.arch}`); + } + return `https://nodejs.org/dist/${installVersion}/node-${installVersion}-${os}-${arch}.tar.xz`; + // NOTE: we use xz archives which are smaller than gz archives } diff --git a/plugs/rust.ts b/plugs/rust.ts index 17b47e79..0766a8c1 100644 --- a/plugs/rust.ts +++ b/plugs/rust.ts @@ -1,9 +1,9 @@ import { - DownloadEnv, - ExecEnvEnv, - InstallEnv, + DownloadArgs, + ExecEnvArgs, + InstallArgs, ListAllEnv, - ListBinPathsEnv, + ListBinPathsArgs, Plug, } from "../plug.ts"; @@ -12,12 +12,12 @@ export function rust({ version }: { version: string }) { name = "rust"; dependencies = []; - execEnv(env: ExecEnvEnv) { + execEnv(env: ExecEnvArgs) { throw new Error("Method not implemented."); return {}; } - listBinPaths(env: ListBinPathsEnv) { + listBinPaths(env: ListBinPathsArgs) { throw new Error("Method not implemented."); return {}; } @@ -27,11 +27,11 @@ export function rust({ version }: { version: string }) { return []; } - download(env: DownloadEnv) { + download(env: DownloadArgs) { throw new Error("Method not implemented."); } - install(env: InstallEnv) { + install(env: InstallArgs) { throw new Error("Method not implemented."); } }(); diff --git a/std.ts b/std.ts new file mode 100644 index 00000000..64e41946 --- /dev/null +++ b/std.ts @@ -0,0 +1,30 @@ +//! This plugin exports the list of standard plugins other +//! plugins are allowed to depend on. +import { PlugDep, RegisteredPlug } from "./core/types.ts"; +import validators from "./core/validators.ts"; +import { manifest as man_tar_aa } from "./plugs/tar.ts"; + +const aaPlugs: RegisteredPlug[] = [ + man_tar_aa, +] + .map((man) => ({ + ty: "ambientAccess", + manifest: validators.ambientAccessPlugManifest.parse(man), + })); + +const denoPlugs: RegisteredPlug[] = [] + .map((man) => ({ + ty: "denoWorker", + manifest: validators.denoWorkerPlugManifest.parse(man), + })); + +export const map = Object.freeze( + new Map([ + ...aaPlugs, + ...denoPlugs, + ].map((plug) => [plug.manifest.name, plug])), +); + +export const tar_aa = Object.freeze({ + id: man_tar_aa.name, +} as PlugDep); diff --git a/tests/e2e.ts b/tests/e2e.ts index e9ac3609..eeaa2c4b 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,4 +1,4 @@ -import { spawn } from "../cli/utils.ts"; +import { spawn } from "../core/utils.ts"; // import node from "../plugs/node.ts"; type TestCase = { @@ -73,7 +73,7 @@ await dockerTest([{ name: "a", imports: `import node from "$ghjk/plugs/node.ts"`, confFn: `async () => { - node({ version: "lts" }); + node({ }); }`, ePoint: `node --version`, }]); From 5bf620f71d1128e133fd01898c4d2ac5b9ea6905 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Wed, 22 Nov 2023 06:32:41 +0300 Subject: [PATCH 07/43] feat: wasmedge, lib/include shims support and globs --- .pre-commit-config.yaml | 6 -- README.md | 3 + cli/hooks.ts | 64 ++++++++++++--- cli/sync.ts | 86 +++++++++++++++---- cli/utils.ts | 2 - core/ambient.ts | 6 +- core/types.ts | 21 ++++- core/utils.ts | 53 ++++++------ core/worker.ts | 51 ++++++++++++ ghjk.ts | 3 + play.ts | 34 +++++++- plug.ts | 1 + plugs/git.ts | 21 +++++ plugs/node.ts | 30 ++----- plugs/wasmedge.ts | 178 ++++++++++++++++++++++++++++++++++++++++ std.ts | 6 ++ tests/ambient.ts | 5 +- 17 files changed, 476 insertions(+), 94 deletions(-) create mode 100644 plugs/git.ts create mode 100644 plugs/wasmedge.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd957993..f77d8a28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,12 +73,6 @@ repos: - ts - tsx files: ^website/ - # - id: version - # name: "Lock versions" - # always_run: true - # language: system - # entry: bash -c 'deno run -A dev/lock.ts --check' - # pass_filenames: false - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: diff --git a/README.md b/README.md index 258a33db..996c7de7 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,13 @@ and looks as follows (abstracting away some implementation details): - multiple version of the same package (e.g. rust stable and rust nighted) - wasmedge +- jco - python with virtual env dir - poetry - pnpm - mold({ if: Deno.build.os === "Macos" }) +- wasm-tools +- cargo-insta - hash verifiable dependencies (timestamp) - hide the `Deno` object in an abstraction - support windows diff --git a/cli/hooks.ts b/cli/hooks.ts index 13086a06..30548bd9 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -1,6 +1,18 @@ import { std_path } from "../deps/cli.ts"; import { dirs } from "./utils.ts"; -import { runAndReturn } from "../core/utils.ts"; +import { spawnOutput } from "../core/utils.ts"; + +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}`); +} // null means it should be removed (for cleaning up old versions) const vfs = { @@ -19,6 +31,19 @@ mod.ghjk.runCli(Deno.args.slice(1), mod.options); "hooks/hook.sh": ` ghjk_already_run=false +clean_up_paths() { + PATH=$(echo "$PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') + PATH="$\{PATH%:\}" + LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') + LIBRARY_PATH="$\{LIBRARY_PATH%:\}" + ${LD_LIBRARY_ENV}=$(echo "$${LD_LIBRARY_ENV}" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') + ${LD_LIBRARY_ENV}="$\{${LD_LIBRARY_ENV}%:\}" + C_INCLUDE_PATH=$(echo "$C_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') + C_INCLUDE_PATH="$\{C_INCLUDE_PATH%:\}" + CPLUS_INCLUDE_PATH=$(echo "$CPLUS_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') + CPLUS_INCLUDE_PATH="$\{CPLUS_INCLUDE_PATH%:\}" +} + ghjk_hook() { # Check if the trap has already executed if [[ "$ghjk_already_run" = true ]]; then @@ -34,8 +59,14 @@ ghjk_hook() { if [ -e "$cur_dir/ghjk.ts" ]; then envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" if [ -d "$envDir" ]; then - PATH="$envDir/shims:$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':')" - PATH="$\{PATH%:\}" + clean_up_paths + + PATH="$envDir/shims/bin:$PATH" + LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" + ${LD_LIBRARY_ENV}="$envDir/shims/lib:$${LD_LIBRARY_ENV}" + C_INCLUDE_PATH="$envDir/shims/include:$C_INCLUDE_PATH" + CPLUS_INCLUDE_PATH="$envDir/shims/include:$CPLUS_INCLUDE_PATH" + source "$envDir/loader.sh" if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then echo -e "\e[38;2;255;69;0m[ghjk] Detected changes, please sync...\e[0m" @@ -49,9 +80,7 @@ ghjk_hook() { fi cur_dir="$(dirname "$cur_dir")" done - if [[ $PATH =~ ^$HOME\/\.local\/share\/ghjk\/envs ]]; then - PATH=$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - fi + clean_up_paths alias ghjk="echo 'No ghjk.ts config found.'" } @@ -70,6 +99,14 @@ PROMPT_COMMAND+="set_hook_flag;" // the hook run before every prompt draw in fish "hooks/hook.fish": ` +function clean_up_paths + set --global --path PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) + set --global --path LIBRARY_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) + set --global --path ${LD_LIBRARY_ENV} $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $${LD_LIBRARY_ENV}) + set --global --path C_INCLUDE_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) + set --global --path CPLUS_INCLUDE_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) +end + function ghjk_hook --on-variable PWD if set --query GHJK_CLEANUP eval $GHJK_CLEANUP @@ -80,7 +117,14 @@ function ghjk_hook --on-variable PWD 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 --global PATH $envDir/shims $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) + clean_up_paths + + set --global --prepend PATH $envDir/shims/bin + set --global --prepend LIBRARY_PATH $envDir/shims/lib + set --global --prepend ${LD_LIBRARY_ENV} $envDir/shims/lib + set --global --prepend C_INCLUDE_PATH $envDir/shims/include + set --global --prepend CPLUS_INCLUDE_PATH $envDir/shims/include + source $envDir/loader.fish if test $envDir/loader.fish -ot $cur_dir/ghjk.ts set_color FF4500 @@ -98,9 +142,7 @@ function ghjk_hook --on-variable PWD end set cur_dir (dirname $cur_dir) end - if string match -q --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH - set --global PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) - end + clean_up_paths alias ghjk "echo 'No ghjk.ts config found.'" end ghjk_hook @@ -111,7 +153,7 @@ async function detectShell(): Promise { let path; try { - path = await runAndReturn([ + path = await spawnOutput([ "ps", "-p", String(Deno.ppid), diff --git a/cli/sync.ts b/cli/sync.ts index c5ff39e0..197ed14d 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -116,8 +116,10 @@ export async function sync(cx: GhjkCtx) { `artifacts not found for plug dep "${depInstallId}" when installing "${installId}"`, ); } - const depShimDir = std_path.resolve(depShimsRootPath, 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, @@ -147,12 +149,40 @@ export async function sync(cx: GhjkCtx) { } } } - // create shims for the environment + const shimDir = std_path.resolve(envDir, "shims"); - await Deno.mkdir(shimDir, { recursive: true }); + 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, installPath } = artifacts.get(instId)!; - void await shimLinkPaths(binPaths, installPath, shimDir); + 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 @@ -266,17 +296,33 @@ function buildInstallGraph(cx: GhjkCtx) { } async function shimLinkPaths( - binPaths: string[], + targetPaths: string[], installPath: string, shimDir: string, ) { const shims: Record = {}; - for ( - const bin of binPaths - ) { - const binPath = std_path.resolve(installPath, bin); - const binName = std_path.basename(binPath); // TODO: aliases - const shimPath = std_path.resolve(shimDir, binName); + 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 }); + console.log({ file, glob }); + 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 Error( + `duplicate shim found when adding shim for file "${fileName}"`, + ); + } try { await Deno.remove(shimPath); } catch (error) { @@ -284,8 +330,8 @@ async function shimLinkPaths( throw error; } } - await Deno.symlink(binPath, shimPath, { type: "file" }); - shims[binName] = shimPath; + await Deno.symlink(filePath, shimPath, { type: "file" }); + shims[fileName] = shimPath; } return shims; } @@ -314,7 +360,9 @@ async function doInstall( `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, ); } - const installVersion = inst.version ?? await plug.latestStable({}); + const installVersion = inst.version ?? await plug.latestStable({ + depShims, + }); const installPath = std_path.resolve( envDir, "installs", @@ -362,8 +410,14 @@ async function doInstall( const binPaths = await plug.listBinPaths({ ...baseArgs, }); + const libPaths = await plug.listLibPaths({ + ...baseArgs, + }); + const includePaths = await plug.listIncludePaths({ + ...baseArgs, + }); const env = await plug.execEnv({ ...baseArgs, }); - return { env, binPaths, installPath, downloadPath }; + return { env, binPaths, libPaths, includePaths, installPath, downloadPath }; } diff --git a/cli/utils.ts b/cli/utils.ts index e8df1e09..deff61c6 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -1,5 +1,3 @@ -import { Err, Ok, Result } from "../deps/cli.ts"; - function home_dir(): string | null { switch (Deno.build.os) { case "linux": diff --git a/core/ambient.ts b/core/ambient.ts index 82145dc7..18c36641 100644 --- a/core/ambient.ts +++ b/core/ambient.ts @@ -6,7 +6,7 @@ import { type ListBinPathsArgs, Plug, } from "./types.ts"; -import { ChildError, runAndReturn } from "./utils.ts"; +import { ChildError, spawnOutput } from "./utils.ts"; export class AmbientAccessPlug extends Plug { constructor(public manifest: AmbientAccessPlugManifest) { @@ -21,7 +21,7 @@ export class AmbientAccessPlug extends Plug { const execPath = await this.pathToExec(); let versionOut; try { - versionOut = await runAndReturn([ + versionOut = await spawnOutput([ execPath, this.manifest.versionExtractFlag, ]); @@ -68,7 +68,7 @@ export class AmbientAccessPlug extends Plug { } async pathToExec(): Promise { try { - const output = await runAndReturn(["which", this.manifest.execName]); + const output = await spawnOutput(["which", this.manifest.execName]); return output.trim(); } catch (err) { if (err instanceof ChildError) { diff --git a/core/types.ts b/core/types.ts index 6f71331d..fd6c0d7d 100644 --- a/core/types.ts +++ b/core/types.ts @@ -83,7 +83,25 @@ export abstract class Plug { listBinPaths( env: ListBinPathsArgs, ): Promise | string[] { - return [std_path.resolve(env.installPath, "bin")]; + return [ + std_path.joinGlobs([std_path.resolve(env.installPath, "bin"), "*"]), + ]; + } + + listLibPaths( + env: ListBinPathsArgs, + ): Promise | string[] { + return [ + std_path.joinGlobs([std_path.resolve(env.installPath, "lib"), "*"]), + ]; + } + + listIncludePaths( + env: ListBinPathsArgs, + ): Promise | string[] { + return [ + std_path.joinGlobs([std_path.resolve(env.installPath, "include"), "*"]), + ]; } latestStable(env: ListAllEnv): Promise | string { @@ -135,6 +153,7 @@ export interface PlugArgsBase { } export interface ListAllEnv { + depShims: DepShims; } export interface ListBinPathsArgs extends PlugArgsBase { diff --git a/core/utils.ts b/core/utils.ts index 99ddb50f..63020fb6 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -13,34 +13,6 @@ export class ChildError extends Error { } } -export async function runAndReturn( - cmd: string[], - options: { - cwd?: string; - env?: Record; - } = {}, -): Promise { - const { cwd, env } = { - ...options, - }; - const output = await new Deno.Command(cmd[0], { - args: cmd.slice(1), - cwd, - stdout: "piped", - stderr: "piped", - env, - }).output(); - - if (output.success) { - return new TextDecoder().decode(output.stdout); - } - throw new ChildError( - output.code, - new TextDecoder().decode(output.stdout) + "\n" + - new TextDecoder().decode(output.stderr), - ); -} - export type SpawnOptions = { cwd?: string; env?: Record; @@ -89,3 +61,28 @@ export async function spawn( throw Error(`child failed with code ${code}`); } } + +export async function spawnOutput( + cmd: string[], + options: Omit = {}, +): Promise { + const { cwd, env } = { + ...options, + }; + const output = await new Deno.Command(cmd[0], { + args: cmd.slice(1), + cwd, + stdout: "piped", + stderr: "piped", + env, + }).output(); + + if (output.success) { + return new TextDecoder().decode(output.stdout); + } + throw new ChildError( + output.code, + new TextDecoder().decode(output.stdout) + "\n" + + new TextDecoder().decode(output.stderr), + ); +} diff --git a/core/worker.ts b/core/worker.ts index 191a295c..32b0812b 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -58,6 +58,12 @@ type WorkerReq = { } | { ty: "listBinPaths"; arg: ListBinPathsArgs; +} | { + ty: "listLibPaths"; + arg: ListBinPathsArgs; +} | { + ty: "listIncludePaths"; + arg: ListBinPathsArgs; }; type WorkerResp = { @@ -69,6 +75,12 @@ type WorkerResp = { } | { ty: "listBinPaths"; payload: string[]; +} | { + ty: "listLibPaths"; + payload: string[]; +} | { + ty: "listIncludePaths"; + payload: string[]; } | { ty: "execEnv"; payload: Record; @@ -110,6 +122,16 @@ export function denoWorkerPlug

(plug: P) { ty: req.ty, payload: await plug.listBinPaths(req.arg), }; + } else if (req.ty === "listLibPaths") { + res = { + ty: req.ty, + payload: await plug.listLibPaths(req.arg), + }; + } else if (req.ty === "listIncludePaths") { + res = { + ty: req.ty, + payload: await plug.listIncludePaths(req.arg), + }; } else if (req.ty === "download") { await plug.download(req.arg), res = { @@ -216,6 +238,35 @@ export class DenoWorkerPlug extends Plug { } throw 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 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 Error(`unexpected response from worker ${JSON.stringify(res)}`); + } + async download(env: DownloadArgs): Promise { const req: WorkerReq = { ty: "download", diff --git a/ghjk.ts b/ghjk.ts index 945bacc7..812907e2 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -15,5 +15,8 @@ await run(); export { ghjk } from "./mod.ts"; import node from "./plugs/node.ts"; +import wasmedge from "./plugs/wasmedge.ts"; node({}); + +wasmedge({}); diff --git a/play.ts b/play.ts index 60636ab0..41706d6c 100644 --- a/play.ts +++ b/play.ts @@ -1,13 +1,39 @@ -import { spawn } from "./cli/utils.ts"; +import { dbg, spawn, spawnOutput } from "./core/utils.ts"; // await Deno.mkdir( // "/home/asdf/.local/share/ghjk/envs/.home.asdf.Source.ecma.ghjk/installs/node/v9.9.0", // { recursive: true }, // ); -await spawn(["tar", "--help"], { - pipeInput: "RUN echo heyya", -}); +const fullOut = await spawnOutput([ + "git", + "ls-remote", + "--refs", + "--tags", + "https://github.com/wasmedge/wasmedge", +]); +console.log(fullOut); +const cutUp = fullOut + .split("\n") + .filter((str) => str.length > 0) + .map((line) => line.split("/")[2]); +console.log(cutUp); +// const deduped = [...new Set(cutUp).keys()]; +const deduped = cutUp.filter((str) => str.match(/^\d+.\d+.\d+/)); +console.log(deduped); +const hyphenated = deduped.map((ver) => ver.match(/-/) ? ver : `${ver}X`); +console.log("hyphenated", hyphenated); +const sorted = hyphenated.sort((a, b) => + a.localeCompare(b, undefined, { numeric: true }) +); +console.log("sorted", sorted); +const dehyphenated = sorted.map((ver) => ver.replace(/X$/, "")); + +console.log(dehyphenated); + +// await spawn(["tar", "--help"], { +// pipeInput: "RUN echo heyya", +// }); const b = () => { console.log("calling b"); diff --git a/plug.ts b/plug.ts index 39caf850..410ef6cc 100644 --- a/plug.ts +++ b/plug.ts @@ -15,6 +15,7 @@ import { isWorker } from "./core/worker.ts"; import logger from "./core/logger.ts"; export * from "./core/mod.ts"; +export * from "./core/utils.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; export { denoWorkerPlug, isWorker, workerSpawn } from "./core/worker.ts"; diff --git a/plugs/git.ts b/plugs/git.ts new file mode 100644 index 00000000..dcfa992e --- /dev/null +++ b/plugs/git.ts @@ -0,0 +1,21 @@ +import { + addInstallGlobal, + type AmbientAccessPlugManifest, + registerAmbientPlugGlobal, +} from "../plug.ts"; + +export const manifest: AmbientAccessPlugManifest = { + name: "git_aa", + version: "0.1.0", + execName: "git", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPlugGlobal(manifest); +export default function git() { + addInstallGlobal({ + plugName: manifest.name, + }); +} diff --git a/plugs/node.ts b/plugs/node.ts index 35c6c628..9c596baa 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -54,33 +54,19 @@ denoWorkerPlug( }; } - listBinPaths(_args: ListBinPathsArgs) { - return [ - "bin/node", - "bin/npm", - "bin/npx", - ]; - } - - async latestStable(_args: ListAllEnv): Promise { - const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); - const metadata = await metadataRequest.json(); - - if (!Array.isArray(metadata)) { - throw Error("invalid data received from index"); - } - return metadata.find((ver) => ver.lts).version; + listLibPaths(env: ListBinPathsArgs): string[] { + return []; } async 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(); + const metadata = await metadataRequest.json() as { version: string }[]; - logger().debug(versions); - return versions; + 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) { diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts new file mode 100644 index 00000000..228626f4 --- /dev/null +++ b/plugs/wasmedge.ts @@ -0,0 +1,178 @@ +import { + addInstallGlobal, + denoWorkerPlug, + depBinShimPath, + DownloadArgs, + downloadFile, + ExecEnvArgs, + InstallArgs, + type InstallConfigBase, + ListAllEnv, + type PlatformInfo, + Plug, + registerDenoPlugGlobal, + removeFile, + spawnOutput, + std_fs, + std_path, + std_url, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "wasmedge", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + std_plugs.git_aa, + ], +}; + +// FIXME: improve multi platform support story +if (Deno.build.os != "darwin" && Deno.build.os != "linux") { + throw Error(`unsupported os: ${Deno.build.os}`); +} + +registerDenoPlugGlobal(manifest); +/* +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 wasmedge(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoAddress = "https://github.com/WasmEdge/WasmEdge"; + +denoWorkerPlug( + new class extends Plug { + manifest = manifest; + + execEnv(args: ExecEnvArgs) { + return { + WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib"), + }; + } + + listLibPaths(): string[] { + return ["lib*/*"]; + } + + async listAll(args: ListAllEnv) { + const fullOut = await spawnOutput([ + depBinShimPath(std_plugs.git_aa, "git", args.depShims), + "ls-remote", + "--refs", + "--tags", + repoAddress, + ]); + + return fullOut + .split("\n") + .filter((str) => str.length > 0) + .map((line) => line.split("/")[2]) + // filter out tags that aren't wasmedge versions + .filter((str) => str.match(/^\d+\.\d+\.\d+/)) + // append X to versions with weird strings like 0.10.1-rc or 0.10.1-alpha + // to make sure they get sorted before the clean releases + .map((ver) => ver.match(/-/) ? ver : `${ver}X`) + // sort them numerically to make sure version 0.10.0 comes after 0.2.9 + .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) + // get rid of the X we appended + .map((ver) => ver.replace(/X$/, "")); + } + + 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 workerSpawn([ + depBinShimPath(std_plugs.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 = await Array + .fromAsync( + std_fs.expandGlob(std_path.joinGlobs([args.tmpDirPath, "*"])), + ); + if (dirs.length != 1 || !dirs[0].isDirectory) { + throw 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 Error(`unsupported arch: ${platform.arch}`); + } + return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${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 plugs + 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 Error(`unsupported arch: ${platform.arch}`); + } + // NOTE: xz archives are available for linux downloads + return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${installVersion}-${os}_${arch}.tar.xz`; + } else { + throw Error(`unsupported os: ${platform.os}`); + } +} diff --git a/std.ts b/std.ts index 64e41946..ca144924 100644 --- a/std.ts +++ b/std.ts @@ -3,9 +3,11 @@ import { PlugDep, RegisteredPlug } from "./core/types.ts"; import validators from "./core/validators.ts"; import { manifest as man_tar_aa } from "./plugs/tar.ts"; +import { manifest as man_git_aa } from "./plugs/git.ts"; const aaPlugs: RegisteredPlug[] = [ man_tar_aa, + man_git_aa, ] .map((man) => ({ ty: "ambientAccess", @@ -28,3 +30,7 @@ export const map = Object.freeze( export const tar_aa = Object.freeze({ id: man_tar_aa.name, } as PlugDep); + +export const git_aa = Object.freeze({ + id: man_git_aa.name, +} as PlugDep); diff --git a/tests/ambient.ts b/tests/ambient.ts index f2e51cbb..a4542ae3 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -3,6 +3,7 @@ import { AmbientAccessPlug } from "../core/ambient.ts"; import { type AmbientAccessPlugManifest } from "../core/types.ts"; import * as tar from "../plugs/tar.ts"; +import * as git from "../plugs/git.ts"; const manifests = [ { @@ -14,11 +15,13 @@ const manifests = [ versionExtractRegexFlags: "", }, tar.manifest, + git.manifest, ]; for (const manifest of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { const plug = new AmbientAccessPlug(manifest as AmbientAccessPlugManifest); - const versions = await plug.listAll({}); + const versions = await plug.listAll({ depShims: {} }); + console.log(versions); std_assert.assertEquals(versions.length, 1); }); } From 7cf84cb1cca82197459584402ad22c1f72844f1c Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 23 Nov 2023 00:08:43 +0300 Subject: [PATCH 08/43] feat(plug): pnpm --- cli/sync.ts | 65 +++++++++++++---------- cli/utils.ts | 2 +- core/mod.ts | 7 +-- core/types.ts | 2 +- core/utils.ts | 2 +- core/validators.ts | 2 + core/worker.ts | 20 +++---- deno.jsonc | 2 +- ghjk.ts | 7 +-- mod.ts | 2 +- plug.ts | 32 ++++++++++-- plugs/node.ts | 21 ++++---- plugs/pnpm.ts | 128 +++++++++++++++++++++++++++++++++++++++++++++ plugs/wasmedge.ts | 16 +++--- tests/e2e.ts | 18 ++++++- 15 files changed, 256 insertions(+), 70 deletions(-) create mode 100644 plugs/pnpm.ts diff --git a/cli/sync.ts b/cli/sync.ts index 197ed14d..a35769ee 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,4 +1,4 @@ -import { Command, std_fs, std_path } from "../deps/cli.ts"; +import { Command, std_fs, std_path, zod } from "../deps/cli.ts"; import logger from "../core/logger.ts"; import { type AmbientAccessPlugManifestX, @@ -9,6 +9,7 @@ import { type InstallConfig, type PlugArgsBase, type RegisteredPlug, + validators, } from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; @@ -65,9 +66,10 @@ export class SyncCommand extends Command { export async function sync(cx: GhjkCtx) { const config = await findConfig(Deno.cwd()); + logger().debug(config); console.log(config); if (!config) { - console.log("ghjk did not find any `ghjk.ts` config."); + logger().error("ghjk did not find any `ghjk.ts` config."); return; } @@ -82,7 +84,7 @@ export async function sync(cx: GhjkCtx) { const versions = await plug.listAll({}); console.log(name, { versions }); } else { - throw Error( + throw new Error( `unsupported plugin type "${ty}": ${JSON.stringify(manifest)}`, ); } @@ -112,7 +114,7 @@ export async function sync(cx: GhjkCtx) { const depInstallId = getInstallId(depInstall); const depArtifacts = artifacts.get(depInstallId); if (!depArtifacts) { - throw Error( + throw new Error( `artifacts not found for plug dep "${depInstallId}" when installing "${installId}"`, ); } @@ -191,7 +193,7 @@ export async function sync(cx: GhjkCtx) { for (const [key, val] of Object.entries(item.env)) { const conflict = env[key]; if (conflict) { - throw Error( + throw new Error( `duplicate env var found ${key} from installs ${instId} & ${ conflict[1] }`, @@ -223,7 +225,7 @@ function buildInstallGraph(cx: GhjkCtx) { const instId = getInstallId(inst); // FIXME: better support for multi installs if (installs.user.has(instId)) { - throw Error(`duplicate install found by plugin ${inst.plugName}`); + throw new Error(`duplicate install found by plugin ${inst.plugName}`); } installs.user.add(instId); foundInstalls.push(inst); @@ -234,7 +236,7 @@ function buildInstallGraph(cx: GhjkCtx) { const regPlug = cx.plugs.get(inst.plugName) ?? cx.allowedDeps.get(inst.plugName); if (!regPlug) { - throw Error( + throw new Error( `unable to find plugin "${inst.plugName}" specified by install ${ JSON.stringify(inst) }`, @@ -259,7 +261,7 @@ function buildInstallGraph(cx: GhjkCtx) { for (const depId of manifest.deps) { const depPlug = cx.allowedDeps.get(depId.id); if (!depPlug) { - throw Error( + throw new Error( `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, ); } @@ -272,7 +274,7 @@ function buildInstallGraph(cx: GhjkCtx) { { const thisDeps = installs.revDepEdges.get(installId); if (thisDeps && thisDeps.includes(depInstallId)) { - throw Error( + throw new Error( `cyclic dependency detected between "${installId}" and "${depInstallId}"`, ); } @@ -308,7 +310,6 @@ async function shimLinkPaths( const glob = file.startsWith("/") ? file : std_path.joinGlobs([installPath, file], { extended: true }); - console.log({ file, glob }); for await (const entry of std_fs.expandGlob(glob)) { foundTargetPaths.push(entry.path); } @@ -319,7 +320,7 @@ async function shimLinkPaths( const shimPath = std_path.resolve(shimDir, fileName); if (shims[fileName]) { - throw Error( + throw new Error( `duplicate shim found when adding shim for file "${fileName}"`, ); } @@ -356,13 +357,15 @@ async function doInstall( manifest as AmbientAccessPlugManifestX, ); } else { - throw Error( + throw new Error( `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, ); } - const installVersion = inst.version ?? await plug.latestStable({ - depShims, - }); + const installVersion = validators.string.parse( + inst.version ?? await plug.latestStable({ + depShims, + }), + ); const installPath = std_path.resolve( envDir, "installs", @@ -407,17 +410,25 @@ async function doInstall( }); void Deno.remove(tmpDirPath, { recursive: true }); } - const binPaths = await plug.listBinPaths({ - ...baseArgs, - }); - const libPaths = await plug.listLibPaths({ - ...baseArgs, - }); - const includePaths = await plug.listIncludePaths({ - ...baseArgs, - }); - const env = await plug.execEnv({ - ...baseArgs, - }); + const binPaths = validators.stringArray.parse( + await plug.listBinPaths({ + ...baseArgs, + }), + ); + const libPaths = validators.stringArray.parse( + await plug.listLibPaths({ + ...baseArgs, + }), + ); + const includePaths = validators.stringArray.parse( + await plug.listIncludePaths({ + ...baseArgs, + }), + ); + const env = zod.record(zod.string()).parse( + await plug.execEnv({ + ...baseArgs, + }), + ); return { env, binPaths, libPaths, includePaths, installPath, downloadPath }; } diff --git a/cli/utils.ts b/cli/utils.ts index deff61c6..ad62a57b 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -23,5 +23,5 @@ export const AVAIL_CONCURRENCY = Number.parseInt( ); if (Number.isNaN(AVAIL_CONCURRENCY)) { - throw Error(`Value of DENO_JOBS is NAN: ${Deno.env.get("DENO_JOBS")}`); + throw new Error(`Value of DENO_JOBS is NAN: ${Deno.env.get("DENO_JOBS")}`); } diff --git a/core/mod.ts b/core/mod.ts index 7956c8a7..95e7a3c3 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,4 +1,5 @@ export * from "./types.ts"; +export { default as validators } from "./validators.ts"; import { semver } from "../deps/common.ts"; import type { AmbientAccessPlugManifest, @@ -44,7 +45,7 @@ export function registerPlug( conflict.conflictResolution == "override" && manifest.conflictResolution == "override" ) { - throw Error( + throw new Error( `Two instances of plugin "${manifest.name}" found with ` + `both set to "${manifest.conflictResolution}" conflictResolution"`, ); @@ -66,7 +67,7 @@ export function registerPlug( semver.parse(conflict.version), ) == 0 ) { - throw Error( + throw new Error( `Two instances of the plug "${manifest.name}" found with an identical version` + `and both set to "deferToNewer" conflictResolution.`, ); @@ -98,7 +99,7 @@ export function addInstall( config: InstallConfig, ) { if (!cx.plugs.has(config.plugName)) { - throw Error( + throw new Error( `unrecognized plug "${config.plugName}" specified by install ${ JSON.stringify(config) }`, diff --git a/core/types.ts b/core/types.ts index fd6c0d7d..9bc87199 100644 --- a/core/types.ts +++ b/core/types.ts @@ -111,7 +111,7 @@ export abstract class Plug { ); const allVers = await this.listAll(env); if (allVers.length == 0) { - throw Error("no versions found"); + throw new Error("no versions found"); } return allVers[allVers.length - 1]; })(); diff --git a/core/utils.ts b/core/utils.ts index 63020fb6..f1458108 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -58,7 +58,7 @@ export async function spawn( } const { code, success } = await child.status; if (!success) { - throw Error(`child failed with code ${code}`); + throw new Error(`child failed with code ${code}`); } } diff --git a/core/validators.ts b/core/validators.ts index 67047e19..3393d9d0 100644 --- a/core/validators.ts +++ b/core/validators.ts @@ -45,4 +45,6 @@ export default { plugManifestBase, denoWorkerPlugManifest, ambientAccessPlugManifest, + string: zod.string(), + stringArray: zod.string().min(1).array(), }; diff --git a/core/worker.ts b/core/worker.ts index 32b0812b..53c8b3fd 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -98,7 +98,7 @@ export function denoWorkerPlug

(plug: P) { const req = msg.data; if (!req.ty) { logger().error("invalid worker request", req); - throw Error("unrecognized worker request type"); + throw new Error("unrecognized worker request type"); } let res: WorkerResp; if (req.ty == "listAll") { @@ -144,7 +144,7 @@ export function denoWorkerPlug

(plug: P) { }; } else { logger().error("unrecognized worker request type", req); - throw Error("unrecognized worker request type"); + throw new Error("unrecognized worker request type"); } self.postMessage(res); }; @@ -197,7 +197,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "listAll") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async latestStable(env: ListAllEnv): Promise { @@ -209,7 +209,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "latestStable") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async execEnv( @@ -223,7 +223,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "execEnv") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async listBinPaths( env: ListBinPathsArgs, @@ -236,7 +236,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "listBinPaths") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async listLibPaths( @@ -250,7 +250,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "listLibPaths") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async listIncludePaths( @@ -264,7 +264,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "listIncludePaths") { return res.payload; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async download(env: DownloadArgs): Promise { @@ -276,7 +276,7 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "download") { return; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } async install(env: InstallArgs): Promise { const req: WorkerReq = { @@ -287,6 +287,6 @@ export class DenoWorkerPlug extends Plug { if (res.ty == "install") { return; } - throw Error(`unexpected response from worker ${JSON.stringify(res)}`); + throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } } diff --git a/deno.jsonc b/deno.jsonc index 51879748..6ce6e4cb 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,which,ls,tar --allow-read --allow-env tests/*", + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker,which,ls,tar,git --allow-read --allow-env tests/*", "cache": "deno cache deps/*" } } diff --git a/ghjk.ts b/ghjk.ts index 812907e2..b4cc95b4 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -16,7 +16,8 @@ await run(); export { ghjk } from "./mod.ts"; import node from "./plugs/node.ts"; import wasmedge from "./plugs/wasmedge.ts"; +import pnpm from "./plugs/pnpm.ts"; -node({}); - -wasmedge({}); +// node({}); +// wasmedge({}); +pnpm({}); diff --git a/mod.ts b/mod.ts index a6c383b1..09aceefd 100644 --- a/mod.ts +++ b/mod.ts @@ -64,7 +64,7 @@ function runCliShim( for (const depId of secureConfig.allowedPluginDeps) { const regPlug = std_plugs.map.get(depId.id); if (!regPlug) { - throw Error( + throw new Error( `unrecognized dep "${depId.id}" found in "allowedPluginDeps"`, ); } diff --git a/plug.ts b/plug.ts index 410ef6cc..70397fe4 100644 --- a/plug.ts +++ b/plug.ts @@ -99,11 +99,11 @@ export function depBinShimPath( ) { const shimPaths = depShims[dep.id]; if (!shimPaths) { - throw Error(`unable to find shims for dep ${dep.id}`); + throw new Error(`unable to find shims for dep ${dep.id}`); } const path = shimPaths[binName]; if (!path) { - throw Error( + throw new Error( `unable to find shim path for bin "${binName}" of dep ${dep.id}`, ); } @@ -114,8 +114,16 @@ export function depBinShimPath( export async function downloadFile( env: DownloadArgs, url: string, - fileName = std_url.basename(url), + 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`); @@ -127,9 +135,25 @@ export async function downloadFile( ); 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 }, + { create: true, truncate: true, write: true, mode }, ); await resp.body!.pipeTo(dest.writable, { preventClose: false }); await std_fs.ensureDir(env.downloadPath); diff --git a/plugs/node.ts b/plugs/node.ts index 9c596baa..d828c936 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -14,6 +14,7 @@ import { Plug, registerDenoPlugGlobal, removeFile, + spawnOutput, std_fs, std_path, std_url, @@ -31,8 +32,9 @@ const manifest = { }; // FIXME: improve multi platform support story -if (Deno.build.os != "darwin" && Deno.build.os != "linux") { - throw Error(`unsupported os: ${Deno.build.os}`); +const supportedOs = ["linux", "darwin"]; +if (!supportedOs.includes(Deno.build.os)) { + throw new Error(`unsupported os: ${Deno.build.os}`); } registerDenoPlugGlobal(manifest); @@ -54,6 +56,8 @@ denoWorkerPlug( }; } + // we wan't to avoid adding libraries found by default at /lib + // to PATHs as they're just node_module sources listLibPaths(env: ListBinPathsArgs): string[] { return []; } @@ -70,15 +74,14 @@ denoWorkerPlug( } async download(args: DownloadArgs) { - await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + await downloadFile(args, artifactUrl(args.installVersion, args.platform)); } async install(args: InstallArgs) { const fileName = std_url.basename( - downloadUrl(args.installVersion, args.platform), + artifactUrl(args.installVersion, args.platform), ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", @@ -101,8 +104,8 @@ denoWorkerPlug( }(), ); -function downloadUrl(installVersion: string, platform: PlatformInfo) { - // TODO: download file +// 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) { @@ -113,7 +116,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { arch = "arm64"; break; default: - throw Error(`unsupported arch: ${platform.arch}`); + throw new Error(`unsupported arch: ${platform.arch}`); } switch (platform.os) { case "linux": @@ -123,7 +126,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { os = "darwin"; break; default: - throw Error(`unsupported os: ${platform.arch}`); + throw new Error(`unsupported os: ${platform.arch}`); } return `https://nodejs.org/dist/${installVersion}/node-${installVersion}-${os}-${arch}.tar.xz`; // NOTE: we use xz archives which are smaller than gz archives diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts new file mode 100644 index 00000000..39254a5d --- /dev/null +++ b/plugs/pnpm.ts @@ -0,0 +1,128 @@ +import { + addInstallGlobal, + denoWorkerPlug, + DownloadArgs, + downloadFile, + ExecEnvArgs, + InstallArgs, + type InstallConfigBase, + ListAllEnv, + type PlatformInfo, + Plug, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + std_url, +} from "../plug.ts"; + +const manifest = { + name: "pnpm", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +const supportedOs = ["linux", "darwin", "win"]; +if (!supportedOs.includes(Deno.build.os)) { + throw new Error(`unsupported os: ${Deno.build.os}`); +} + +registerDenoPlugGlobal(manifest); + +export default function node({ version }: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + version, + }); +} + +denoWorkerPlug( + new class extends Plug { + manifest = manifest; + + execEnv(args: ExecEnvArgs) { + return { + NODE_PATH: args.installPath, + }; + } + + async listAll(_env: ListAllEnv) { + 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/plugs/wasmedge.ts b/plugs/wasmedge.ts index 228626f4..21f0ec40 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -30,12 +30,14 @@ const manifest = { ], }; -// FIXME: improve multi platform support story -if (Deno.build.os != "darwin" && Deno.build.os != "linux") { - throw Error(`unsupported os: ${Deno.build.os}`); +const supportedOs = ["linux", "darwin"]; +if (!supportedOs.includes(Deno.build.os)) { + throw new Error(`unsupported os: ${Deno.build.os}`); } registerDenoPlugGlobal(manifest); + +// TODO: wasmedge extension and plugin support /* const supportedExtensions = ["tensorflow" as const, "image" as const]; @@ -131,7 +133,7 @@ denoWorkerPlug( std_fs.expandGlob(std_path.joinGlobs([args.tmpDirPath, "*"])), ); if (dirs.length != 1 || !dirs[0].isDirectory) { - throw Error("unexpected archive contents"); + throw new Error("unexpected archive contents"); } await std_fs.copy( dirs[0].path, @@ -152,7 +154,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { arch = "arm64"; break; default: - throw Error(`unsupported arch: ${platform.arch}`); + throw new Error(`unsupported arch: ${platform.arch}`); } return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${installVersion}-${platform.os}_${arch}.tar.gz`; } else if (platform.os == "linux") { @@ -168,11 +170,11 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { arch = "aarch64"; // NOTE: arch is different from darwin releases break; default: - throw Error(`unsupported arch: ${platform.arch}`); + throw new Error(`unsupported arch: ${platform.arch}`); } // NOTE: xz archives are available for linux downloads return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${installVersion}-${os}_${arch}.tar.xz`; } else { - throw Error(`unsupported os: ${platform.os}`); + throw new Error(`unsupported os: ${platform.os}`); } } diff --git a/tests/e2e.ts b/tests/e2e.ts index eeaa2c4b..838111cb 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -70,10 +70,24 @@ await (${confFn.toString()})()`; } await dockerTest([{ - name: "a", + name: "pnpm", + imports: `import pnpm from "$ghjk/plugs/pnpm.ts"`, + confFn: `async () => { + pnpm({ }); + }`, + ePoint: ` --version`, +}, { + name: "wasmedge", + imports: `import wasmedge from "$ghjk/plugs/wasmedge.ts"`, + confFn: `async () => { + wasmedge({ }); + }`, + ePoint: `wasmedge --version`, +}, { + name: "node", imports: `import node from "$ghjk/plugs/node.ts"`, confFn: `async () => { node({ }); }`, - ePoint: `node --version`, + ePoint: ` --version`, }]); From 88f1993d97fc37f26f2bd8b44025366a94374604 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 23 Nov 2023 05:23:27 +0300 Subject: [PATCH 09/43] feat(plug): cargo-instal and wasm-tools --- cli/sync.ts | 3 +- core/ambient.ts | 4 +- core/types.ts | 2 +- core/utils.ts | 21 +++--- core/worker.ts | 135 +++++++++++++++++++------------------- ghjk.ts | 8 ++- mod.ts | 36 +---------- plug.ts | 13 ++-- plugs/cargo-binstall.ts | 117 +++++++++++++++++++++++++++++++++ plugs/jco.ts | 4 +- plugs/node.ts | 115 +++++++++++++++------------------ plugs/pnpm.ts | 112 ++++++++++++++------------------ plugs/rust.ts | 38 ----------- plugs/tar.ts | 2 +- plugs/wasm-tools.ts | 92 ++++++++++++++++++++++++++ plugs/wasmedge.ts | 140 +++++++++++++++++++--------------------- setup_globals.ts | 43 ++++++++++++ std.ts | 9 ++- tests/e2e.ts | 99 +++++++++++++++++++++------- 19 files changed, 604 insertions(+), 389 deletions(-) create mode 100644 plugs/cargo-binstall.ts delete mode 100644 plugs/rust.ts create mode 100644 plugs/wasm-tools.ts create mode 100644 setup_globals.ts diff --git a/cli/sync.ts b/cli/sync.ts index a35769ee..18b0f7e0 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -66,12 +66,11 @@ export class SyncCommand extends Command { export async function sync(cx: GhjkCtx) { const config = await findConfig(Deno.cwd()); - logger().debug(config); - console.log(config); if (!config) { logger().error("ghjk did not find any `ghjk.ts` config."); return; } + logger().debug("syncnig", config); const envDir = envDirFromConfig(config); logger().debug({ envDir }); diff --git a/core/ambient.ts b/core/ambient.ts index 18c36641..17693120 100644 --- a/core/ambient.ts +++ b/core/ambient.ts @@ -4,11 +4,11 @@ import { type InstallArgs, type ListAllEnv, type ListBinPathsArgs, - Plug, + PlugBase, } from "./types.ts"; import { ChildError, spawnOutput } from "./utils.ts"; -export class AmbientAccessPlug extends Plug { +export class AmbientAccessPlug extends PlugBase { constructor(public manifest: AmbientAccessPlugManifest) { super(); if (manifest.deps && manifest.deps.length > 0) { diff --git a/core/types.ts b/core/types.ts index 9bc87199..07cfcfc0 100644 --- a/core/types.ts +++ b/core/types.ts @@ -71,7 +71,7 @@ export type GhjkCtx = GhjkConfig & { allowedDeps: RegisteredPlugs; }; -export abstract class Plug { +export abstract class PlugBase { abstract manifest: PlugManifest; execEnv( diff --git a/core/utils.ts b/core/utils.ts index f1458108..d572cbea 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -17,10 +17,11 @@ export type SpawnOptions = { cwd?: string; env?: Record; pipeInput?: string; - pipeOut?: WritableStream; - pipeErr?: WritableStream; + // pipeOut?: WritableStream; + // pipeErr?: WritableStream; }; +// FIXME: pita function responsible for test flakiness export async function spawn( cmd: string[], options: SpawnOptions = {}, @@ -32,8 +33,8 @@ export async function spawn( const child = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, - stdout: "piped", - stderr: "piped", + // stdout: "piped", + // stderr: "piped", ...(pipeInput ? { stdin: "piped", @@ -43,12 +44,12 @@ export async function spawn( }).spawn(); // keep pipe asynchronous till the command exists - void child.stdout.pipeTo(options.pipeOut ?? Deno.stdout.writable, { - preventClose: true, - }); - void child.stderr.pipeTo(options.pipeErr ?? Deno.stderr.writable, { - preventClose: true, - }); + // void child.stdout.pipeTo(options.pipeOut ?? Deno.stdout.writable, { + // preventClose: true, + // }); + // void child.stderr.pipeTo(options.pipeErr ?? Deno.stderr.writable, { + // preventClose: true, + // }); if (pipeInput) { const writer = child.stdin.getWriter(); diff --git a/core/worker.ts b/core/worker.ts index 53c8b3fd..2f5e10af 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -9,7 +9,7 @@ import { type InstallArgs, type ListAllEnv, type ListBinPathsArgs, - Plug, + PlugBase, } from "./types.ts"; import { spawn, type SpawnOptions } from "./utils.ts"; @@ -21,22 +21,22 @@ export function workerSpawn( cmd: string[], options: Omit = {}, ) { - const outDecoder = new TextDecoderStream(); - const errDecoder = new TextDecoderStream(); - outDecoder.readable.pipeTo( - new WritableStream({ - write: console.log, - }), - ); - errDecoder.readable.pipeTo( - new WritableStream({ - write: console.error, - }), - ); + // const outDecoder = new TextDecoderStream(); + // const errDecoder = new TextDecoderStream(); + // outDecoder.readable.pipeTo( + // new WritableStream({ + // write: console.log, + // }), + // ); + // errDecoder.readable.pipeTo( + // new WritableStream({ + // write: console.error, + // }), + // ); return spawn(cmd, { ...options, - pipeOut: outDecoder.writable, - pipeErr: errDecoder.writable, + // pipeOut: outDecoder.writable, + // pipeErr: errDecoder.writable, }); } @@ -92,69 +92,70 @@ type WorkerResp = { /// Make sure to call this before any `await` point or your /// plug might miss messages -export function denoWorkerPlug

(plug: P) { - if (self.name) { - 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 == "listAll") { - res = { - ty: req.ty, - // id: req.id, - payload: await plug.listAll(req.arg), - }; - } else if (req.ty === "latestStable") { - res = { - ty: req.ty, - payload: await plug.latestStable(req.arg), - }; - } else if (req.ty === "execEnv") { - res = { - ty: req.ty, - payload: await plug.execEnv(req.arg), - }; - } else if (req.ty === "listBinPaths") { - res = { - ty: req.ty, - payload: await plug.listBinPaths(req.arg), - }; - } else if (req.ty === "listLibPaths") { +export function initDenoWorkerPlug

(plugInit: () => P) { + if (!isWorker()) { + throw new Error("expecteing to be running not running in Worker"); + } + 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 == "listAll") { + res = { + ty: req.ty, + // id: req.id, + payload: await plugInit().listAll(req.arg), + }; + } else if (req.ty === "latestStable") { + res = { + ty: req.ty, + payload: await plugInit().latestStable(req.arg), + }; + } else if (req.ty === "execEnv") { + res = { + ty: req.ty, + payload: await plugInit().execEnv(req.arg), + }; + } else if (req.ty === "listBinPaths") { + res = { + ty: req.ty, + payload: await plugInit().listBinPaths(req.arg), + }; + } else if (req.ty === "listLibPaths") { + res = { + ty: req.ty, + payload: await plugInit().listLibPaths(req.arg), + }; + } else if (req.ty === "listIncludePaths") { + res = { + ty: req.ty, + payload: await plugInit().listIncludePaths(req.arg), + }; + } else if (req.ty === "download") { + await plugInit().download(req.arg), res = { ty: req.ty, - payload: await plug.listLibPaths(req.arg), }; - } else if (req.ty === "listIncludePaths") { + } else if (req.ty === "install") { + await plugInit().install(req.arg), res = { ty: req.ty, - payload: await plug.listIncludePaths(req.arg), }; - } else if (req.ty === "download") { - await plug.download(req.arg), - res = { - ty: req.ty, - }; - } else if (req.ty === "install") { - await plug.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 { + logger().error("unrecognized worker request type", req); + throw new Error("unrecognized worker request type"); + } + self.postMessage(res); + }; } // type MethodKeys = { // [P in keyof T]-?: T[P] extends Function ? P : never; // }[keyof T]; -export class DenoWorkerPlug extends Plug { +export class DenoWorkerPlug extends PlugBase { constructor( public manifest: DenoWorkerPlugManifestX, ) { diff --git a/ghjk.ts b/ghjk.ts index b4cc95b4..712c23e3 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -15,9 +15,13 @@ await run(); export { ghjk } from "./mod.ts"; import node from "./plugs/node.ts"; -import wasmedge from "./plugs/wasmedge.ts"; +import install from "./plugs/wasmedge.ts"; import pnpm from "./plugs/pnpm.ts"; +import cargo_binstall from "./plugs/cargo-binstall.ts"; +import wasm_tools from "./plugs/wasm-tools.ts"; // node({}); // wasmedge({}); -pnpm({}); +// pnpm({}); +// cargo_binstall({}); +wasm_tools({}); diff --git a/mod.ts b/mod.ts index 09aceefd..1097284e 100644 --- a/mod.ts +++ b/mod.ts @@ -1,7 +1,7 @@ //! This module is intended to be re-exported by `ghjk.ts` config scripts. Please //! avoid importing elsewhere at it has side-effects. -import { log } from "./deps/common.ts"; +import "./setup_globals.ts"; import { type GhjkConfig } from "./core/mod.ts"; // this is only a shortcut for the cli @@ -20,40 +20,6 @@ declare global { } } -self.ghjk = { - plugs: new Map(), - installs: [], -}; - -log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("DEBUG", { - formatter: (lr) => { - let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; - - lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; - }); - - return msg; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), - }, - - loggers: { - // configure default logger available via short-hand methods above. - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - }, -}); - function runCliShim( args: string[], secureConfig: GhjkSecureConfig | undefined, diff --git a/plug.ts b/plug.ts index 70397fe4..f0ea2150 100644 --- a/plug.ts +++ b/plug.ts @@ -6,19 +6,20 @@ import { type DownloadArgs, type GhjkConfig, type InstallConfig, + type PlugBase, type PlugDep, registerAmbientPlug, registerDenoPlug, } from "./core/mod.ts"; import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; -import { isWorker } from "./core/worker.ts"; +import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; import logger from "./core/logger.ts"; export * from "./core/mod.ts"; export * from "./core/utils.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; -export { denoWorkerPlug, isWorker, workerSpawn } from "./core/worker.ts"; +export { initDenoWorkerPlug, isWorker, workerSpawn } from "./core/worker.ts"; export type * from "./core/mod.ts"; if (isWorker()) { @@ -42,7 +43,6 @@ if (isWorker()) { }, loggers: { - // configure default logger available via short-hand methods above. default: { level: "DEBUG", handlers: ["console"], @@ -67,12 +67,15 @@ declare global { } } -export function registerDenoPlugGlobal( +export function registerDenoPlugGlobal

( manifestUnclean: DenoWorkerPlugManifest, + plugInit: () => P, ) { if (self.ghjk) { - if (isWorker()) throw new Error("impossible"); + if (isWorker()) throw new Error("literally impossible!"); registerDenoPlug(self.ghjk, manifestUnclean); + } else { + initDenoWorkerPlug(plugInit); } } diff --git a/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts new file mode 100644 index 00000000..083dc0ef --- /dev/null +++ b/plugs/cargo-binstall.ts @@ -0,0 +1,117 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + std_url, + workerSpawn, +} from "../plug.ts"; +// FIXME: find a better way to expose std_plug.plug_Id +// that allows standard plugs to depend on each other +// import * as std_plugs from "../std.ts"; + +const tar_aa_id = { + id: "tar_aa", +}; + +export const manifest = { + name: "cargo-binstall", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [tar_aa_id], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function cargo_binstall(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoAddress = "https://github.com/cargo-bins/cargo-binstall"; + +export class Plug extends PlugBase { + manifest = manifest; + + listBinPaths(): string[] { + return ["cargo-binstall", "detect-targets", "detect-wasi"]; + } + + 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 workerSpawn([ + 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( + 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 = "aarch64"; + break; + default: + throw new Error(`unsupported arch: ${platform.arch}`); + } + if (platform.os == "darwin") { + return `${repoAddress}/releases/download/v${installVersion}/cargo-binstall-${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 plugs + const os = "unknown-linux-musl"; + // NOTE: xz archives are available for linux downloads + return `${repoAddress}/releases/download/v${installVersion}/cargo-binstall-${arch}-${os}.full.tgz`; + } else { + throw new Error(`unsupported os: ${platform.os}`); + } +} diff --git a/plugs/jco.ts b/plugs/jco.ts index 381bc69f..922a5481 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -4,11 +4,11 @@ import { InstallArgs, ListAllEnv, ListBinPathsArgs, - Plug, + PlugBase, } from "../plug.ts"; export function jco() { - return new class extends Plug { + return new class extends PlugBase { name = "jco"; dependencies = ["node"]; diff --git a/plugs/node.ts b/plugs/node.ts index d828c936..406934a2 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - denoWorkerPlug, depBinShimPath, DownloadArgs, downloadFile, @@ -9,12 +8,10 @@ import { type InstallConfigBase, ListAllEnv, ListBinPathsArgs, - logger, type PlatformInfo, - Plug, + PlugBase, registerDenoPlugGlobal, removeFile, - spawnOutput, std_fs, std_path, std_url, @@ -22,7 +19,8 @@ import { } from "../plug.ts"; import * as std_plugs from "../std.ts"; -const manifest = { +// TODO: sanity check exports of all plugs +export const manifest = { name: "node", version: "0.1.0", moduleSpecifier: import.meta.url, @@ -31,78 +29,71 @@ const manifest = { ], }; -// FIXME: improve multi platform support story -const supportedOs = ["linux", "darwin"]; -if (!supportedOs.includes(Deno.build.os)) { - throw new Error(`unsupported os: ${Deno.build.os}`); -} +registerDenoPlugGlobal(manifest, () => new Plug()); -registerDenoPlugGlobal(manifest); - -export default function node({ version }: InstallConfigBase = {}) { +// FIXME: improve multi platform support story +export default function install({ version }: InstallConfigBase = {}) { addInstallGlobal({ plugName: manifest.name, version, }); } -denoWorkerPlug( - new class extends Plug { - manifest = manifest; +export class Plug extends PlugBase { + 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(env: ListBinPathsArgs): string[] { - return []; - } + execEnv(args: ExecEnvArgs) { + return { + NODE_PATH: args.installPath, + }; + } - async listAll(_env: ListAllEnv) { - const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); - const metadata = await metadataRequest.json() as { version: string }[]; + // we wan't to avoid adding libraries found by default at /lib + // to PATHs as they're just node_module sources + listLibPaths(env: ListBinPathsArgs): string[] { + return []; + } - 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 listAll(_env: ListAllEnv) { + const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); + const metadata = await metadataRequest.json() as { version: string }[]; - async download(args: DownloadArgs) { - await downloadFile(args, artifactUrl(args.installVersion, args.platform)); - } + 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 install(args: InstallArgs) { - const fileName = std_url.basename( - artifactUrl(args.installVersion, args.platform), - ); - const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + async download(args: DownloadArgs) { + await downloadFile(args, artifactUrl(args.installVersion, args.platform)); + } - if (await std_fs.exists(args.installPath)) { - await removeFile(args.installPath, { recursive: true }); - } + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + await workerSpawn([ + depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); - await std_fs.copy( - std_path.resolve( - args.tmpDirPath, - std_path.basename(fileDwnPath, ".tar.xz"), - ), - args.installPath, - ); + 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.xz"), + ), + 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) { diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts index 39254a5d..38f73ede 100644 --- a/plugs/pnpm.ts +++ b/plugs/pnpm.ts @@ -1,14 +1,12 @@ import { addInstallGlobal, - denoWorkerPlug, DownloadArgs, downloadFile, - ExecEnvArgs, InstallArgs, type InstallConfigBase, ListAllEnv, type PlatformInfo, - Plug, + PlugBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -21,81 +19,67 @@ const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, }; +registerDenoPlugGlobal(manifest, () => new Plug()); -const supportedOs = ["linux", "darwin", "win"]; -if (!supportedOs.includes(Deno.build.os)) { - throw new Error(`unsupported os: ${Deno.build.os}`); -} - -registerDenoPlugGlobal(manifest); - -export default function node({ version }: InstallConfigBase = {}) { +export default function install({ version }: InstallConfigBase = {}) { addInstallGlobal({ plugName: manifest.name, version, }); } -denoWorkerPlug( - new class extends Plug { - manifest = manifest; +class Plug extends PlugBase { + manifest = manifest; - execEnv(args: ExecEnvArgs) { - return { - NODE_PATH: args.installPath, - }; - } - - async listAll(_env: ListAllEnv) { - 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", - }, + async listAll(_env: ListAllEnv) { + 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; - } + }, + ); + const metadata = await metadataRequest.json() as { + versions: Record; + }; - async download(args: DownloadArgs) { - await downloadFile( - args, - artifactUrl(args.installVersion, args.platform), - { - mode: 0o700, - }, - ); - } + const versions = Object.keys(metadata.versions); + return versions; + } - async install(args: InstallArgs) { - const fileName = std_url.basename( - artifactUrl(args.installVersion, args.platform), - ); - const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + async download(args: DownloadArgs) { + await downloadFile( + args, + artifactUrl(args.installVersion, args.platform), + { + mode: 0o700, + }, + ); + } - if (await std_fs.exists(args.installPath)) { - await removeFile(args.installPath, { recursive: true }); - } + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - 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", - ), - ); + 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) { diff --git a/plugs/rust.ts b/plugs/rust.ts deleted file mode 100644 index 0766a8c1..00000000 --- a/plugs/rust.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - DownloadArgs, - ExecEnvArgs, - InstallArgs, - ListAllEnv, - ListBinPathsArgs, - Plug, -} from "../plug.ts"; - -export function rust({ version }: { version: string }) { - return new class extends Plug { - name = "rust"; - dependencies = []; - - execEnv(env: ExecEnvArgs) { - throw new Error("Method not implemented."); - return {}; - } - - listBinPaths(env: ListBinPathsArgs) { - throw new Error("Method not implemented."); - return {}; - } - - listAll(env: ListAllEnv) { - throw new Error("Method not implemented."); - return []; - } - - download(env: DownloadArgs) { - throw new Error("Method not implemented."); - } - - install(env: InstallArgs) { - throw new Error("Method not implemented."); - } - }(); -} diff --git a/plugs/tar.ts b/plugs/tar.ts index 49f808b5..c9e30fa5 100644 --- a/plugs/tar.ts +++ b/plugs/tar.ts @@ -14,7 +14,7 @@ export const manifest: AmbientAccessPlugManifest = { }; registerAmbientPlugGlobal(manifest); -export default function tar() { +export default function install() { addInstallGlobal({ plugName: manifest.name, }); diff --git a/plugs/wasm-tools.ts b/plugs/wasm-tools.ts new file mode 100644 index 00000000..0f1510c8 --- /dev/null +++ b/plugs/wasm-tools.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigBase, + logger, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "wasm-tools", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.cbin_deno, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +export class Plug extends PlugBase { + 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 workerSpawn([ + depBinShimPath(std_plugs.cbin_deno, "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, + ); + await std_fs.ensureDir(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/plugs/wasmedge.ts b/plugs/wasmedge.ts index 21f0ec40..3e0f8b3b 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -1,15 +1,15 @@ import { addInstallGlobal, - denoWorkerPlug, depBinShimPath, DownloadArgs, downloadFile, ExecEnvArgs, + initDenoWorkerPlug, InstallArgs, type InstallConfigBase, ListAllEnv, type PlatformInfo, - Plug, + PlugBase, registerDenoPlugGlobal, removeFile, spawnOutput, @@ -30,12 +30,7 @@ const manifest = { ], }; -const supportedOs = ["linux", "darwin"]; -if (!supportedOs.includes(Deno.build.os)) { - throw new Error(`unsupported os: ${Deno.build.os}`); -} - -registerDenoPlugGlobal(manifest); +registerDenoPlugGlobal(manifest, () => new Plug()); // TODO: wasmedge extension and plugin support /* @@ -60,7 +55,7 @@ const supportedPlugins = [ "wasmedge_bpf" as const, ]; */ -export default function wasmedge(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ plugName: manifest.name, ...config, @@ -68,80 +63,77 @@ export default function wasmedge(config: InstallConfigBase = {}) { } const repoAddress = "https://github.com/WasmEdge/WasmEdge"; +export class Plug extends PlugBase { + manifest = manifest; -denoWorkerPlug( - new class extends Plug { - manifest = manifest; + execEnv(args: ExecEnvArgs) { + return { + WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib"), + }; + } - execEnv(args: ExecEnvArgs) { - return { - WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib"), - }; - } + listLibPaths(): string[] { + return ["lib*/*"]; + } - listLibPaths(): string[] { - return ["lib*/*"]; - } + async listAll(args: ListAllEnv) { + const fullOut = await spawnOutput([ + depBinShimPath(std_plugs.git_aa, "git", args.depShims), + "ls-remote", + "--refs", + "--tags", + repoAddress, + ]); + + return fullOut + .split("\n") + .filter((str) => str.length > 0) + .map((line) => line.split("/")[2]) + // filter out tags that aren't wasmedge versions + .filter((str) => str.match(/^\d+\.\d+\.\d+/)) + // append X to versions with weird strings like 0.10.1-rc or 0.10.1-alpha + // to make sure they get sorted before the clean releases + .map((ver) => ver.match(/-/) ? ver : `${ver}X`) + // sort them numerically to make sure version 0.10.0 comes after 0.2.9 + .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) + // get rid of the X we appended + .map((ver) => ver.replace(/X$/, "")); + } - async listAll(args: ListAllEnv) { - const fullOut = await spawnOutput([ - depBinShimPath(std_plugs.git_aa, "git", args.depShims), - "ls-remote", - "--refs", - "--tags", - repoAddress, - ]); - - return fullOut - .split("\n") - .filter((str) => str.length > 0) - .map((line) => line.split("/")[2]) - // filter out tags that aren't wasmedge versions - .filter((str) => str.match(/^\d+\.\d+\.\d+/)) - // append X to versions with weird strings like 0.10.1-rc or 0.10.1-alpha - // to make sure they get sorted before the clean releases - .map((ver) => ver.match(/-/) ? ver : `${ver}X`) - // sort them numerically to make sure version 0.10.0 comes after 0.2.9 - .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) - // get rid of the X we appended - .map((ver) => ver.replace(/X$/, "")); - } + async download(args: DownloadArgs) { + await downloadFile(args, downloadUrl(args.installVersion, args.platform)); + } - 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 workerSpawn([ + depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); + + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); } - async install(args: InstallArgs) { - const fileName = std_url.basename( - downloadUrl(args.installVersion, args.platform), - ); - const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - - await workerSpawn([ - depBinShimPath(std_plugs.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 = await Array - .fromAsync( - std_fs.expandGlob(std_path.joinGlobs([args.tmpDirPath, "*"])), - ); - if (dirs.length != 1 || !dirs[0].isDirectory) { - throw new Error("unexpected archive contents"); - } - await std_fs.copy( - dirs[0].path, - args.installPath, + const dirs = await Array + .fromAsync( + std_fs.expandGlob(std_path.joinGlobs([args.tmpDirPath, "*"])), ); + 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") { diff --git a/setup_globals.ts b/setup_globals.ts new file mode 100644 index 00000000..c983f47e --- /dev/null +++ b/setup_globals.ts @@ -0,0 +1,43 @@ +import { log } from "./deps/common.ts"; +import type { GhjkConfig } from "./core/mod.ts"; + +declare global { + interface Window { + ghjk: GhjkConfig; + } +} + +self.ghjk = { + plugs: new Map(), + installs: [], +}; + +log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("NOTSET", { + formatter: (lr) => { + let msg = `[${lr.levelName}${ + lr.loggerName ? " " + lr.loggerName : "" + }] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), + }, + + loggers: { + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); diff --git a/std.ts b/std.ts index ca144924..cca20de8 100644 --- a/std.ts +++ b/std.ts @@ -4,6 +4,7 @@ import { PlugDep, RegisteredPlug } from "./core/types.ts"; import validators from "./core/validators.ts"; import { manifest as man_tar_aa } from "./plugs/tar.ts"; import { manifest as man_git_aa } from "./plugs/git.ts"; +import { manifest as man_cbin_deno } from "./plugs/cargo-binstall.ts"; const aaPlugs: RegisteredPlug[] = [ man_tar_aa, @@ -14,7 +15,9 @@ const aaPlugs: RegisteredPlug[] = [ manifest: validators.ambientAccessPlugManifest.parse(man), })); -const denoPlugs: RegisteredPlug[] = [] +const denoPlugs: RegisteredPlug[] = [ + man_cbin_deno, +] .map((man) => ({ ty: "denoWorker", manifest: validators.denoWorkerPlugManifest.parse(man), @@ -34,3 +37,7 @@ export const tar_aa = Object.freeze({ export const git_aa = Object.freeze({ id: man_git_aa.name, } as PlugDep); + +export const cbin_deno = Object.freeze({ + id: man_cbin_deno.name, +} as PlugDep); diff --git a/tests/e2e.ts b/tests/e2e.ts index 838111cb..c4c3eaa1 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,5 +1,34 @@ import { spawn } from "../core/utils.ts"; -// import node from "../plugs/node.ts"; +import { log } from "../deps/dev.ts"; + +log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("DEBUG", { + formatter: (lr) => { + let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), + }, + + loggers: { + // configure default logger available via short-hand methods above. + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); type TestCase = { name: string; @@ -50,15 +79,13 @@ await (${confFn.toString()})()`; ...dockerCmd, "run", "--rm", - "-v", - ".:/ghjk:ro", ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) .flat(), tag, "bash", "-c", "-i", - ...ePoint.split(/\s/), + ePoint, ], { env }); await spawn([ ...dockerCmd, @@ -69,25 +96,51 @@ await (${confFn.toString()})()`; } } -await dockerTest([{ - name: "pnpm", - imports: `import pnpm from "$ghjk/plugs/pnpm.ts"`, - confFn: `async () => { - pnpm({ }); +// order tests by download size to make failed runs less expensive +await dockerTest([ + // 7 megs + { + name: "cargo-binstall", + imports: `import plug from "$ghjk/plugs/cargo-binstall.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `cargo-binstall -V`, + }, + // 7 megs + { + name: "wasm-tools", + imports: `import plug from "$ghjk/plugs/wasm-tools.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `wasm-tools -V`, + }, + // 16 megs + { + name: "wasmedge", + imports: `import plug from "$ghjk/plugs/wasmedge.ts"`, + confFn: `async () => { + plug({ }); }`, - ePoint: ` --version`, -}, { - name: "wasmedge", - imports: `import wasmedge from "$ghjk/plugs/wasmedge.ts"`, - confFn: `async () => { - wasmedge({ }); + ePoint: `wasmedge --version`, + }, + // 56 megs + { + name: "pnpm", + imports: `import plug from "$ghjk/plugs/pnpm.ts"`, + confFn: `async () => { + plug({ }); }`, - ePoint: `wasmedge --version`, -}, { - name: "node", - imports: `import node from "$ghjk/plugs/node.ts"`, - confFn: `async () => { - node({ }); + ePoint: `pnpm --version`, + }, + // 25 megs + { + name: "node", + imports: `import plug from "$ghjk/plugs/node.ts"`, + confFn: `async () => { + plug({ }); }`, - ePoint: ` --version`, -}]); + ePoint: `node --version`, + }, +]); From 4dd100bd8a18e0e6df00d49e88e4021d215cdaee Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 23 Nov 2023 05:29:52 +0300 Subject: [PATCH 10/43] feat(plug): wasm-opt & cargo-insta --- ghjk.ts | 6 ++- plugs/cargo-binstall.ts | 4 +- plugs/cargo-insta.ts | 92 +++++++++++++++++++++++++++++++++++++++++ plugs/git.ts | 2 +- plugs/node.ts | 2 +- plugs/pnpm.ts | 2 +- plugs/tar.ts | 2 +- plugs/wasm-opt.ts | 92 +++++++++++++++++++++++++++++++++++++++++ plugs/wasm-tools.ts | 2 +- plugs/wasmedge.ts | 2 +- tests/e2e.ts | 18 ++++++++ 11 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 plugs/cargo-insta.ts create mode 100644 plugs/wasm-opt.ts diff --git a/ghjk.ts b/ghjk.ts index 712c23e3..fdf4a8b8 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -19,9 +19,13 @@ import install from "./plugs/wasmedge.ts"; import pnpm from "./plugs/pnpm.ts"; import cargo_binstall from "./plugs/cargo-binstall.ts"; import wasm_tools from "./plugs/wasm-tools.ts"; +import wasm_opt from "./plugs/wasm-opt.ts"; +import cargo_insta from "./plugs/cargo-insta.ts"; // node({}); // wasmedge({}); // pnpm({}); // cargo_binstall({}); -wasm_tools({}); +// wasm_tools({}); +wasm_opt({}); +cargo_insta({}); diff --git a/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts index 083dc0ef..93b444a4 100644 --- a/plugs/cargo-binstall.ts +++ b/plugs/cargo-binstall.ts @@ -19,11 +19,11 @@ import { // import * as std_plugs from "../std.ts"; const tar_aa_id = { - id: "tar_aa", + id: "tar@aa", }; export const manifest = { - name: "cargo-binstall", + name: "cargo-binstall@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [tar_aa_id], diff --git a/plugs/cargo-insta.ts b/plugs/cargo-insta.ts new file mode 100644 index 00000000..1159faa4 --- /dev/null +++ b/plugs/cargo-insta.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigBase, + logger, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "cargo-insta@cbinst", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.cbin_deno, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +export class Plug extends PlugBase { + 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 workerSpawn([ + depBinShimPath(std_plugs.cbin_deno, "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, + ); + await std_fs.ensureDir(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/plugs/git.ts b/plugs/git.ts index dcfa992e..04ccb14d 100644 --- a/plugs/git.ts +++ b/plugs/git.ts @@ -5,7 +5,7 @@ import { } from "../plug.ts"; export const manifest: AmbientAccessPlugManifest = { - name: "git_aa", + name: "git@aa", version: "0.1.0", execName: "git", versionExtractFlag: "--version", diff --git a/plugs/node.ts b/plugs/node.ts index 406934a2..cf0c9a73 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -21,7 +21,7 @@ import * as std_plugs from "../std.ts"; // TODO: sanity check exports of all plugs export const manifest = { - name: "node", + name: "node@org", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts index 38f73ede..f354de3b 100644 --- a/plugs/pnpm.ts +++ b/plugs/pnpm.ts @@ -15,7 +15,7 @@ import { } from "../plug.ts"; const manifest = { - name: "pnpm", + name: "pnpm@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; diff --git a/plugs/tar.ts b/plugs/tar.ts index c9e30fa5..070654be 100644 --- a/plugs/tar.ts +++ b/plugs/tar.ts @@ -5,7 +5,7 @@ import { } from "../plug.ts"; export const manifest: AmbientAccessPlugManifest = { - name: "tar_aa", + name: "tar@aa", version: "0.1.0", execName: "tar", versionExtractFlag: "--version", diff --git a/plugs/wasm-opt.ts b/plugs/wasm-opt.ts new file mode 100644 index 00000000..ef95cb0d --- /dev/null +++ b/plugs/wasm-opt.ts @@ -0,0 +1,92 @@ +import { + addInstallGlobal, + depBinShimPath, + type DownloadArgs, + InstallArgs, + type InstallConfigBase, + logger, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "wasm-opt@cbinst", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.cbin_deno, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +export class Plug extends PlugBase { + 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 workerSpawn([ + depBinShimPath(std_plugs.cbin_deno, "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, + ); + await std_fs.ensureDir(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/plugs/wasm-tools.ts b/plugs/wasm-tools.ts index 0f1510c8..affc239e 100644 --- a/plugs/wasm-tools.ts +++ b/plugs/wasm-tools.ts @@ -15,7 +15,7 @@ import { import * as std_plugs from "../std.ts"; const manifest = { - name: "wasm-tools", + name: "wasm-tools@cbinst", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 3e0f8b3b..6f080369 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -21,7 +21,7 @@ import { import * as std_plugs from "../std.ts"; const manifest = { - name: "wasmedge", + name: "wasmedge@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ diff --git a/tests/e2e.ts b/tests/e2e.ts index c4c3eaa1..bfc7ea50 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -108,6 +108,15 @@ await dockerTest([ ePoint: `cargo-binstall -V`, }, // 7 megs + { + name: "cargo-insta", + imports: `import plug from "$ghjk/plugs/cargo-insta.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `cargo-insta -V`, + }, + // 13 megs { name: "wasm-tools", imports: `import plug from "$ghjk/plugs/wasm-tools.ts"`, @@ -125,6 +134,15 @@ await dockerTest([ }`, ePoint: `wasmedge --version`, }, + // 22 megs + { + name: "wasm-opt", + imports: `import plug from "$ghjk/plugs/wasm-opt.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `wasm-opt --version`, + }, // 56 megs { name: "pnpm", From 56657b77ccd22e4a73de060a3a127e16dd0dfa49 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 02:47:01 +0000 Subject: [PATCH 11/43] feat(ci): e2e tests and some new plugs --- .github/pull_request_template.md | 25 +++++ .github/workflows/autoupdate.yml | 19 ++++ .github/workflows/pr-title-check.yml | 15 +++ .github/workflows/tests.yml | 42 ++++++++ README.md | 27 +++-- cli/hooks.ts | 12 +-- cli/sync.ts | 7 +- core/utils.ts | 31 ++++-- deno.jsonc | 2 +- ghjk.ts | 11 +- play.ts | 42 -------- plug.ts | 12 +++ plugs/act.ts | 131 ++++++++++++++++++++++++ plugs/cargo-binstall.ts | 2 +- plugs/cargo-insta.ts | 4 +- plugs/jco.ts | 148 +++++++++++++++++---------- plugs/mold.ts | 126 +++++++++++++++++++++++ plugs/node.ts | 18 ++-- plugs/pnpm.ts | 2 +- plugs/wasm-opt.ts | 4 +- plugs/wasm-tools.ts | 4 +- plugs/wasmedge.ts | 69 +++++++------ setup_globals.ts | 7 +- std.ts | 20 +++- tests/ambient.ts | 1 + tests/e2e.ts | 88 ++++++++-------- tests/setup_globals.ts | 30 ++++++ 27 files changed, 672 insertions(+), 227 deletions(-) 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 delete mode 100644 play.ts create mode 100644 plugs/act.ts create mode 100644 plugs/mold.ts create mode 100644 tests/setup_globals.ts 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..66de08bf --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,42 @@ +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 diff --git a/README.md b/README.md index 996c7de7..48d0052f 100644 --- a/README.md +++ b/README.md @@ -67,20 +67,19 @@ and looks as follows (abstracting away some implementation details): ## todo - multiple version of the same package (e.g. rust stable and rust nighted) -- wasmedge -- jco -- python with virtual env dir -- poetry -- pnpm -- mold({ if: Deno.build.os === "Macos" }) -- wasm-tools -- cargo-insta +- [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 - -## design considerations - -- keep interface with plugins KISS, open to consumption by others -- `ghjk.ts` scripts are executed in a secure sandbox and the plugins it declares - are each run in a separate worker and only given access to relevant dirs +- [ ] installation tools + - [ ] untar + - [ ] xz + - [ ] git diff --git a/cli/hooks.ts b/cli/hooks.ts index 30548bd9..58ab36bd 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -100,11 +100,11 @@ PROMPT_COMMAND+="set_hook_flag;" // the hook run before every prompt draw in fish "hooks/hook.fish": ` function clean_up_paths - set --global --path PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) - set --global --path LIBRARY_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) - set --global --path ${LD_LIBRARY_ENV} $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $${LD_LIBRARY_ENV}) - set --global --path C_INCLUDE_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) - set --global --path CPLUS_INCLUDE_PATH $(string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) + set --global --path PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) + set --global --path LIBRARY_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) + set --global --path ${LD_LIBRARY_ENV} (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $${LD_LIBRARY_ENV}) + set --global --path C_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) + set --global --path CPLUS_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) end function ghjk_hook --on-variable PWD @@ -115,7 +115,7 @@ function ghjk_hook --on-variable PWD 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 $HOME/.local/share/ghjk/envs/(string replace --all / . $cur_dir) if test -d $envDir clean_up_paths diff --git a/cli/sync.ts b/cli/sync.ts index 18b0f7e0..86f74e1e 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -129,7 +129,12 @@ export async function sync(cx: GhjkCtx) { ); } - const thisArtifacts = await doInstall(envDir, inst, regPlug, depShims); + let thisArtifacts; + try { + thisArtifacts = await doInstall(envDir, inst, regPlug, depShims); + } catch (err) { + throw new Error(`error installing ${installId}`, { cause: err }); + } artifacts.set(installId, thisArtifacts); void Deno.remove(depShimsRootPath, { recursive: true }); diff --git a/core/utils.ts b/core/utils.ts index d572cbea..e190cc8e 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -65,25 +65,36 @@ export async function spawn( export async function spawnOutput( cmd: string[], - options: Omit = {}, + options: Omit = {}, ): Promise { const { cwd, env } = { ...options, }; - const output = await new Deno.Command(cmd[0], { + logger().debug("spawning", cmd); + const child = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, stdout: "piped", stderr: "piped", + // ...(pipeInput + // ? { + // stdin: "piped", + // } + // : {}), env, - }).output(); + }).spawn(); - if (output.success) { - return new TextDecoder().decode(output.stdout); + // if (pipeInput) { + // const writer = child.stdin.getWriter(); + // await writer.write(new TextEncoder().encode(pipeInput)); + // writer.releaseLock(); + // await child.stdin.close(); + // } + const { code, success, stdout, stderr } = await child.output(); + if (!success) { + throw new Error( + `child failed with code ${code} - ${new TextDecoder().decode(stderr)}`, + ); } - throw new ChildError( - output.code, - new TextDecoder().decode(output.stdout) + "\n" + - new TextDecoder().decode(output.stderr), - ); + return new TextDecoder().decode(stdout); } diff --git a/deno.jsonc b/deno.jsonc index 6ce6e4cb..8c972308 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,which,ls,tar,git --allow-read --allow-env tests/*", + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker,sudo,which,ls,tar,git --allow-read --allow-env tests/*", "cache": "deno cache deps/*" } } diff --git a/ghjk.ts b/ghjk.ts index fdf4a8b8..b832d18f 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -18,14 +18,21 @@ import node from "./plugs/node.ts"; import install from "./plugs/wasmedge.ts"; import pnpm from "./plugs/pnpm.ts"; import cargo_binstall from "./plugs/cargo-binstall.ts"; +import wasmedge from "./plugs/wasmedge.ts"; import wasm_tools from "./plugs/wasm-tools.ts"; import wasm_opt from "./plugs/wasm-opt.ts"; import cargo_insta from "./plugs/cargo-insta.ts"; +import jco from "./plugs/jco.ts"; +// import mold from "./plugs/mold.ts"; +import act from "./plugs/act.ts"; // node({}); // wasmedge({}); // pnpm({}); // cargo_binstall({}); // wasm_tools({}); -wasm_opt({}); -cargo_insta({}); +// wasm_opt({}); +// cargo_insta({}); +// jco({}); +// mold({}); +act({}); diff --git a/play.ts b/play.ts deleted file mode 100644 index 41706d6c..00000000 --- a/play.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { dbg, spawn, spawnOutput } from "./core/utils.ts"; - -// await Deno.mkdir( -// "/home/asdf/.local/share/ghjk/envs/.home.asdf.Source.ecma.ghjk/installs/node/v9.9.0", -// { recursive: true }, -// ); - -const fullOut = await spawnOutput([ - "git", - "ls-remote", - "--refs", - "--tags", - "https://github.com/wasmedge/wasmedge", -]); -console.log(fullOut); -const cutUp = fullOut - .split("\n") - .filter((str) => str.length > 0) - .map((line) => line.split("/")[2]); -console.log(cutUp); -// const deduped = [...new Set(cutUp).keys()]; -const deduped = cutUp.filter((str) => str.match(/^\d+.\d+.\d+/)); -console.log(deduped); -const hyphenated = deduped.map((ver) => ver.match(/-/) ? ver : `${ver}X`); -console.log("hyphenated", hyphenated); -const sorted = hyphenated.sort((a, b) => - a.localeCompare(b, undefined, { numeric: true }) -); -console.log("sorted", sorted); -const dehyphenated = sorted.map((ver) => ver.replace(/X$/, "")); - -console.log(dehyphenated); - -// await spawn(["tar", "--help"], { -// pipeInput: "RUN echo heyya", -// }); - -const b = () => { - console.log("calling b"); - return "b"; -}; -const a = "a" ?? b(); diff --git a/plug.ts b/plug.ts index f0ea2150..d5abb667 100644 --- a/plug.ts +++ b/plug.ts @@ -95,6 +95,18 @@ export function addInstallGlobal( } } +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: PlugDep, binName: string, diff --git a/plugs/act.ts b/plugs/act.ts new file mode 100644 index 00000000..c212ca7f --- /dev/null +++ b/plugs/act.ts @@ -0,0 +1,131 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + std_url, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "act@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "nektos"; +const repoName = "act"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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 workerSpawn([ + depBinShimPath(std_plugs.tar_aa, "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( + 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/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts index 93b444a4..e6fdb14f 100644 --- a/plugs/cargo-binstall.ts +++ b/plugs/cargo-binstall.ts @@ -104,12 +104,12 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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}/cargo-binstall-${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 plugs const os = "unknown-linux-musl"; - // NOTE: xz archives are available for linux downloads return `${repoAddress}/releases/download/v${installVersion}/cargo-binstall-${arch}-${os}.full.tgz`; } else { throw new Error(`unsupported os: ${platform.os}`); diff --git a/plugs/cargo-insta.ts b/plugs/cargo-insta.ts index 1159faa4..cef9edba 100644 --- a/plugs/cargo-insta.ts +++ b/plugs/cargo-insta.ts @@ -19,7 +19,7 @@ const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_deno, + std_plugs.cbin_ghrel, ], }; @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await workerSpawn([ - depBinShimPath(std_plugs.cbin_deno, "cargo-binstall", args.depShims), + depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "cargo-insta", `--version`, args.installVersion, diff --git a/plugs/jco.ts b/plugs/jco.ts index 922a5481..cb59aa44 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -1,70 +1,114 @@ import { + addInstallGlobal, + depBinShimPath, DownloadArgs, - ExecEnvArgs, + downloadFile, InstallArgs, + type InstallConfigBase, ListAllEnv, - ListBinPathsArgs, + pathWithDepShims, + type PlatformInfo, PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + std_url, + workerSpawn, } from "../plug.ts"; +import node from "./node.ts"; +import * as std_plugs from "../std.ts"; -export function jco() { - return new class extends PlugBase { - name = "jco"; - dependencies = ["node"]; +const manifest = { + name: "jco@npm", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + std_plugs.node_org, + ], +}; +registerDenoPlugGlobal(manifest, () => new Plug()); - execEnv(env: ExecEnvArgs) { - throw new Error("Method not implemented."); - return {}; - } - - listBinPaths(env: ListBinPathsArgs) { - 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(); +export default function install({ version }: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + version, + }); + // FIXME: conflict flags for install configs + node({}); +} - const versions = Object.keys(metadata.versions); - versions.sort(); +class Plug extends PlugBase { + manifest = manifest; - console.log(versions); - return versions; - } + async listAll(_env: ListAllEnv) { + 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; + }; - download(env: DownloadArgs) { - throw new Error("Method not implemented."); - } + const versions = Object.keys(metadata.versions); + return versions; + } - install(env: InstallArgs) { - /* - npm install -g @bytecodealliance/jco - or + async download(args: DownloadArgs) { + await downloadFile( + args, + artifactUrl(args.installVersion, args.platform), + ); + } -PACKAGE=@bytecodealliance/jco -PACKAGE_INTERNAL=jco -BIN="jco" -BIN_PATH="${ASDF_INSTALL_PATH}/src/jco.js" + async install(args: InstallArgs) { + const fileName = std_url.basename( + artifactUrl(args.installVersion, args.platform), + ); + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); -if [[ "${ASDF_INSTALL_TYPE:-version}" == 'ref' ]]; then - echo >&2 "â›” This plugin does not support installing by ref." - exit 1 -fi + await workerSpawn([ + depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); -TARBALL_URL="https://registry.npmjs.org/${PACKAGE}/-/${PACKAGE_INTERNAL}-${ASDF_INSTALL_VERSION}.tgz" -echo "Downloading ${PACKAGE} v${ASDF_INSTALL_VERSION} from ${TARBALL_URL}" + if (await std_fs.exists(args.installPath)) { + await removeFile(args.installPath, { recursive: true }); + } -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}" + await std_fs.copy( + std_path.resolve( + args.tmpDirPath, + "package", + ), + args.installPath, + ); + await workerSpawn([ + depBinShimPath(std_plugs.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"), + ); + } +} -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."); - } - }(); +function artifactUrl(installVersion: string, _platform: PlatformInfo) { + return `https://registry.npmjs.org/@bytecodealliance/jco/-/jco-${installVersion}.tgz`; } diff --git a/plugs/mold.ts b/plugs/mold.ts new file mode 100644 index 00000000..8d16cc77 --- /dev/null +++ b/plugs/mold.ts @@ -0,0 +1,126 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, + std_url, + workerSpawn, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "mold@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "rui314"; +const repoName = "mold"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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 workerSpawn([ + depBinShimPath(std_plugs.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 == "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/plugs/node.ts b/plugs/node.ts index cf0c9a73..3affbbca 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -7,7 +7,6 @@ import { InstallArgs, type InstallConfigBase, ListAllEnv, - ListBinPathsArgs, type PlatformInfo, PlugBase, registerDenoPlugGlobal, @@ -17,7 +16,11 @@ import { std_url, workerSpawn, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; +// import * as std_plugs from "../std.ts"; + +const tar_aa_id = { + id: "tar@aa", +}; // TODO: sanity check exports of all plugs export const manifest = { @@ -25,7 +28,7 @@ export const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.tar_aa, + tar_aa_id, ], }; @@ -50,7 +53,7 @@ export class Plug extends PlugBase { // we wan't to avoid adding libraries found by default at /lib // to PATHs as they're just node_module sources - listLibPaths(env: ListBinPathsArgs): string[] { + listLibPaths(): string[] { return []; } @@ -75,7 +78,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); await workerSpawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + depBinShimPath(tar_aa_id, "tar", args.depShims), "xf", fileDwnPath, `--directory=${args.tmpDirPath}`, @@ -88,7 +91,7 @@ export class Plug extends PlugBase { await std_fs.copy( std_path.resolve( args.tmpDirPath, - std_path.basename(fileDwnPath, ".tar.xz"), + std_path.basename(fileDwnPath, ".tar.gz"), ), args.installPath, ); @@ -119,6 +122,5 @@ function artifactUrl(installVersion: string, platform: PlatformInfo) { default: throw new Error(`unsupported os: ${platform.arch}`); } - return `https://nodejs.org/dist/${installVersion}/node-${installVersion}-${os}-${arch}.tar.xz`; - // NOTE: we use xz archives which are smaller than gz archives + return `https://nodejs.org/dist/${installVersion}/node-${installVersion}-${os}-${arch}.tar.gz`; } diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts index f354de3b..611c493a 100644 --- a/plugs/pnpm.ts +++ b/plugs/pnpm.ts @@ -14,7 +14,7 @@ import { std_url, } from "../plug.ts"; -const manifest = { +export const manifest = { name: "pnpm@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, diff --git a/plugs/wasm-opt.ts b/plugs/wasm-opt.ts index ef95cb0d..816ff34c 100644 --- a/plugs/wasm-opt.ts +++ b/plugs/wasm-opt.ts @@ -19,7 +19,7 @@ const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_deno, + std_plugs.cbin_ghrel, ], }; @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await workerSpawn([ - depBinShimPath(std_plugs.cbin_deno, "cargo-binstall", args.depShims), + depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-opt", `--version`, args.installVersion, diff --git a/plugs/wasm-tools.ts b/plugs/wasm-tools.ts index affc239e..410cb70b 100644 --- a/plugs/wasm-tools.ts +++ b/plugs/wasm-tools.ts @@ -19,7 +19,7 @@ const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_deno, + std_plugs.cbin_ghrel, ], }; @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await workerSpawn([ - depBinShimPath(std_plugs.cbin_deno, "cargo-binstall", args.depShims), + depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-tools", `--version`, args.installVersion, diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 6f080369..94d021d7 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -4,15 +4,12 @@ import { DownloadArgs, downloadFile, ExecEnvArgs, - initDenoWorkerPlug, InstallArgs, type InstallConfigBase, - ListAllEnv, type PlatformInfo, PlugBase, registerDenoPlugGlobal, removeFile, - spawnOutput, std_fs, std_path, std_url, @@ -26,7 +23,6 @@ const manifest = { moduleSpecifier: import.meta.url, deps: [ std_plugs.tar_aa, - std_plugs.git_aa, ], }; @@ -62,7 +58,10 @@ export default function install(config: InstallConfigBase = {}) { }); } -const repoAddress = "https://github.com/WasmEdge/WasmEdge"; +const repoOwner = "WasmEdge"; +const repoName = "WasmEdge"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + export class Plug extends PlugBase { manifest = manifest; @@ -76,28 +75,29 @@ export class Plug extends PlugBase { return ["lib*/*"]; } - async listAll(args: ListAllEnv) { - const fullOut = await spawnOutput([ - depBinShimPath(std_plugs.git_aa, "git", args.depShims), - "ls-remote", - "--refs", - "--tags", - repoAddress, - ]); + async latestStable(): Promise { + const metadataRequest = await fetch( + `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`, + ); - return fullOut - .split("\n") - .filter((str) => str.length > 0) - .map((line) => line.split("/")[2]) - // filter out tags that aren't wasmedge versions - .filter((str) => str.match(/^\d+\.\d+\.\d+/)) - // append X to versions with weird strings like 0.10.1-rc or 0.10.1-alpha - // to make sure they get sorted before the clean releases - .map((ver) => ver.match(/-/) ? ver : `${ver}X`) - // sort them numerically to make sure version 0.10.0 comes after 0.2.9 - .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) - // get rid of the X we appended - .map((ver) => ver.replace(/X$/, "")); + 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) { @@ -121,10 +121,14 @@ export class Plug extends PlugBase { await removeFile(args.installPath, { recursive: true }); } - const dirs = await Array - .fromAsync( - std_fs.expandGlob(std_path.joinGlobs([args.tmpDirPath, "*"])), - ); + 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"); } @@ -148,7 +152,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { default: throw new Error(`unsupported arch: ${platform.arch}`); } - return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${installVersion}-${platform.os}_${arch}.tar.gz`; + 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 plugs @@ -164,8 +168,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { default: throw new Error(`unsupported arch: ${platform.arch}`); } - // NOTE: xz archives are available for linux downloads - return `${repoAddress}/releases/download/${installVersion}/WasmEdge-${installVersion}-${os}_${arch}.tar.xz`; + return `${repoAddress}/releases/download/${installVersion}/${repoName}-${installVersion}-${os}_${arch}.tar.gz`; } else { throw new Error(`unsupported os: ${platform.os}`); } diff --git a/setup_globals.ts b/setup_globals.ts index c983f47e..cc250b4d 100644 --- a/setup_globals.ts +++ b/setup_globals.ts @@ -16,9 +16,10 @@ log.setup({ handlers: { console: new log.handlers.ConsoleHandler("NOTSET", { formatter: (lr) => { - let msg = `[${lr.levelName}${ - lr.loggerName ? " " + lr.loggerName : "" - }] ${lr.msg}`; + const loggerName = lr.loggerName == "default" + ? " " + lr.loggerName + : ""; + let msg = `[${lr.levelName}${loggerName}] ${lr.msg}`; lr.args.forEach((arg, _index) => { msg += `, ${JSON.stringify(arg)}`; diff --git a/std.ts b/std.ts index cca20de8..16a9b6c0 100644 --- a/std.ts +++ b/std.ts @@ -4,7 +4,9 @@ import { PlugDep, RegisteredPlug } from "./core/types.ts"; import validators from "./core/validators.ts"; import { manifest as man_tar_aa } from "./plugs/tar.ts"; import { manifest as man_git_aa } from "./plugs/git.ts"; -import { manifest as man_cbin_deno } from "./plugs/cargo-binstall.ts"; +import { manifest as man_cbin_ghrel } from "./plugs/cargo-binstall.ts"; +import { manifest as man_node_org } from "./plugs/node.ts"; +import { manifest as man_pnpm_ghrel } from "./plugs/pnpm.ts"; const aaPlugs: RegisteredPlug[] = [ man_tar_aa, @@ -16,7 +18,9 @@ const aaPlugs: RegisteredPlug[] = [ })); const denoPlugs: RegisteredPlug[] = [ - man_cbin_deno, + man_cbin_ghrel, + man_node_org, + man_pnpm_ghrel, ] .map((man) => ({ ty: "denoWorker", @@ -38,6 +42,14 @@ export const git_aa = Object.freeze({ id: man_git_aa.name, } as PlugDep); -export const cbin_deno = Object.freeze({ - id: man_cbin_deno.name, +export const cbin_ghrel = Object.freeze({ + id: man_cbin_ghrel.name, +} as PlugDep); + +export const node_org = Object.freeze({ + id: man_node_org.name, +} as PlugDep); + +export const pnpm_ghrel = Object.freeze({ + id: man_pnpm_ghrel.name, } as PlugDep); diff --git a/tests/ambient.ts b/tests/ambient.ts index a4542ae3..c84a219b 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -1,3 +1,4 @@ +import "./setup_globals.ts"; import { std_assert } from "../deps/dev.ts"; import { AmbientAccessPlug } from "../core/ambient.ts"; import { type AmbientAccessPlugManifest } from "../core/types.ts"; diff --git a/tests/e2e.ts b/tests/e2e.ts index bfc7ea50..b8548ac5 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,34 +1,5 @@ +import "./setup_globals.ts"; import { spawn } from "../core/utils.ts"; -import { log } from "../deps/dev.ts"; - -log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("DEBUG", { - formatter: (lr) => { - let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; - - lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; - }); - - return msg; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), - }, - - loggers: { - // configure default logger available via short-hand methods above. - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - }, -}); type TestCase = { name: string; @@ -58,7 +29,7 @@ async function dockerTest(cases: TestCase[]) { ...testEnvs, }; const configFile = `export { ghjk } from "/ghjk/mod.ts"; -${imports.replaceAll("$ghjk", "/ghjk/")} +${imports.replaceAll("$ghjk", "/ghjk")} await (${confFn.toString()})()`; @@ -70,8 +41,12 @@ await (${confFn.toString()})()`; ...dockerCmd, "buildx", "build", - "-t", + "--tag", tag, + "--network=host", + // add to images list + "--output", + "type=docker", "-f-", ".", ], { env, pipeInput: dFile }); @@ -98,6 +73,15 @@ await (${confFn.toString()})()`; // order tests by download size to make failed runs less expensive await dockerTest([ + // 7 megs + { + name: "act", + imports: `import plug from "$ghjk/plugs/act.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `act --version`, + }, // 7 megs { name: "cargo-binstall", @@ -107,7 +91,25 @@ await dockerTest([ }`, ePoint: `cargo-binstall -V`, }, - // 7 megs + // 8 megs + { + name: "mold", + imports: `import plug from "$ghjk/plugs/mold.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `mold -V`, + }, + // 16 megs + { + name: "wasmedge", + imports: `import plug from "$ghjk/plugs/wasmedge.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `wasmedge --version`, + }, + // cargo binstall +7 megs { name: "cargo-insta", imports: `import plug from "$ghjk/plugs/cargo-insta.ts"`, @@ -116,7 +118,7 @@ await dockerTest([ }`, ePoint: `cargo-insta -V`, }, - // 13 megs + // cargo binsatll 13 megs { name: "wasm-tools", imports: `import plug from "$ghjk/plugs/wasm-tools.ts"`, @@ -125,16 +127,16 @@ await dockerTest([ }`, ePoint: `wasm-tools -V`, }, - // 16 megs + // 25 megs { - name: "wasmedge", - imports: `import plug from "$ghjk/plugs/wasmedge.ts"`, + name: "node", + imports: `import plug from "$ghjk/plugs/node.ts"`, confFn: `async () => { plug({ }); }`, - ePoint: `wasmedge --version`, + ePoint: `node --version`, }, - // 22 megs + // cargo-binstall + 22 megs { name: "wasm-opt", imports: `import plug from "$ghjk/plugs/wasm-opt.ts"`, @@ -152,13 +154,13 @@ await dockerTest([ }`, ePoint: `pnpm --version`, }, - // 25 megs + // pnpm + more megs { - name: "node", - imports: `import plug from "$ghjk/plugs/node.ts"`, + name: "jco", + imports: `import plug from "$ghjk/plugs/jco.ts"`, confFn: `async () => { plug({ }); }`, - ePoint: `node --version`, + ePoint: `jco --version`, }, ]); diff --git a/tests/setup_globals.ts b/tests/setup_globals.ts new file mode 100644 index 00000000..ed43aa8d --- /dev/null +++ b/tests/setup_globals.ts @@ -0,0 +1,30 @@ +import { log } from "../deps/dev.ts"; + +log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("DEBUG", { + formatter: (lr) => { + let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; + + lr.args.forEach((arg, _index) => { + msg += `, ${JSON.stringify(arg)}`; + }); + + return msg; + }, + // formatter: "[{loggerName}] - {levelName} {msg}", + }), + }, + + loggers: { + // configure default logger available via short-hand methods above. + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); From 10b11083eb2875676daf9d02d6938e90fc615a0e Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:19:17 +0300 Subject: [PATCH 12/43] wip: asdf plug support --- README.md | 1 + cli/sync.ts | 12 ++- core/ambient.ts | 6 +- core/asdf.ts | 132 ++++++++++++++++++++++++++++++++ core/mod.ts | 1 + core/types.ts | 41 +++++----- core/utils.ts | 32 ++++++++ core/validators.ts | 26 +++++++ core/worker.ts | 164 ++++++++++++++++++++-------------------- deno.lock | 143 ++++++++++++++++++++++++++++++++++- mod.ts | 1 - plug.ts | 44 ++++------- plugs/act.ts | 4 +- plugs/asdf.ts | 14 ++++ plugs/awk.ts | 21 +++++ plugs/cargo-binstall.ts | 6 +- plugs/cargo-insta.ts | 4 +- plugs/curl.ts | 21 +++++ plugs/jco.ts | 10 +-- plugs/mold.ts | 9 ++- plugs/node.ts | 8 +- plugs/pnpm.ts | 4 +- plugs/wasm-opt.ts | 4 +- plugs/wasm-tools.ts | 4 +- plugs/wasmedge.ts | 4 +- tests/ambient.ts | 4 + 26 files changed, 556 insertions(+), 164 deletions(-) create mode 100644 core/asdf.ts create mode 100644 plugs/asdf.ts create mode 100644 plugs/awk.ts create mode 100644 plugs/curl.ts diff --git a/README.md b/README.md index 48d0052f..af36c18d 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,4 @@ and looks as follows (abstracting away some implementation details): - [ ] untar - [ ] xz - [ ] git +- [ ] Find a way to make copy ops atomic diff --git a/cli/sync.ts b/cli/sync.ts index 86f74e1e..4497a8b4 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -7,6 +7,7 @@ import { getInstallId, type GhjkCtx, type InstallConfig, + InstallConfigX, type PlugArgsBase, type RegisteredPlug, validators, @@ -14,6 +15,7 @@ import { import { DenoWorkerPlug } from "../core/worker.ts"; import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; import { AmbientAccessPlug } from "../core/ambient.ts"; +import { AsdfPlug } from "../core/asdf.ts"; async function findConfig(path: string): Promise { let current = path; @@ -346,20 +348,27 @@ type InstallArtifacts = DePromisify>; async function doInstall( envDir: string, - inst: InstallConfig, + instUnclean: InstallConfig, regPlug: RegisteredPlug, depShims: DepShims, ) { const { ty: plugType, manifest } = regPlug; let plug; + let inst: InstallConfigX; if (plugType == "denoWorker") { + inst = validators.installConfig.parse(instUnclean); plug = new DenoWorkerPlug( manifest as DenoWorkerPlugManifestX, ); } else if (plugType == "ambientAccess") { + inst = validators.installConfig.parse(instUnclean); plug = new AmbientAccessPlug( manifest as AmbientAccessPlugManifestX, ); + } else if (plugType == "asdf") { + const asdfInst = validators.asdfInstallConfig.parse(instUnclean); + inst = asdfInst; + plug = await AsdfPlug.init(envDir, asdfInst, depShims); } else { throw new Error( `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, @@ -388,6 +397,7 @@ async function doInstall( installVersion: installVersion, depShims, platform: Deno.build, + config: inst, }; { logger().info(`downloading ${inst.plugName}:${installVersion}`); diff --git a/core/ambient.ts b/core/ambient.ts index 17693120..3c63bb45 100644 --- a/core/ambient.ts +++ b/core/ambient.ts @@ -2,7 +2,7 @@ import { type AmbientAccessPlugManifest, type DownloadArgs, type InstallArgs, - type ListAllEnv, + type ListAllArgs, type ListBinPathsArgs, PlugBase, } from "./types.ts"; @@ -17,7 +17,7 @@ export class AmbientAccessPlug extends PlugBase { ); } } - async latestStable(_env: ListAllEnv): Promise { + async latestStable(_env: ListAllArgs): Promise { const execPath = await this.pathToExec(); let versionOut; try { @@ -50,7 +50,7 @@ export class AmbientAccessPlug extends PlugBase { return matches[0]; } - async listAll(env: ListAllEnv): Promise { + async listAll(env: ListAllArgs): Promise { return [await this.latestStable(env)]; } diff --git a/core/asdf.ts b/core/asdf.ts new file mode 100644 index 00000000..10ab1b5c --- /dev/null +++ b/core/asdf.ts @@ -0,0 +1,132 @@ +import { + type AsdfInstallConfigX, + type DepShims, + DownloadArgs, + InstallArgs, + ListAllArgs, + ListBinPathsArgs, + PlugBase, +} from "./types.ts"; +import { + depBinShimPath, + pathWithDepShims, + spawn, + spawnOutput, +} from "./utils.ts"; +import * as std_plugs from "../std.ts"; +import { std_fs, std_path } from "../deps/common.ts"; + +export class AsdfPlug extends PlugBase { + manifest = { + name: "asdf@asdf", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [std_plugs.tar_aa, std_plugs.git_aa], + }; + constructor( + public pluginDir: string, + public config: AsdfInstallConfigX, + ) { + super(); + } + static async init( + envDir: string, + installConfig: AsdfInstallConfigX, + depShims: DepShims, + ) { + const asdfDir = std_path.resolve(envDir, "asdf"); + const url = new URL(installConfig.plugRepo); + const pluginId = `${url.hostname}~${url.pathname.replaceAll("/", ".")}`; + + const pluginDir = std_path.resolve(asdfDir, pluginId); + if (!await std_fs.exists(pluginDir)) { + const tmpCloneDirPath = await Deno.makeTempDir({ + prefix: `ghjk_asdf_clone_${pluginId}@$asdf_`, + }); + await spawn( + [ + depBinShimPath(std_plugs.git_aa, "git", depShims), + "clone", + installConfig.plugRepo, + "--depth", + "1", + tmpCloneDirPath, + ], + ); + await std_fs.copy( + tmpCloneDirPath, + pluginDir, + ); + void Deno.remove(tmpCloneDirPath, { recursive: true }); + } + return new AsdfPlug(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); + } + + 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); + } + async download(args: DownloadArgs): Promise { + 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): Promise { + 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/core/mod.ts b/core/mod.ts index 95e7a3c3..657801cd 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -18,6 +18,7 @@ export const Ghjk = { export function getInstallId(install: InstallConfig) { return install.plugName; } + export function registerDenoPlug( cx: GhjkConfig, manifestUnclean: DenoWorkerPlugManifest, diff --git a/core/types.ts b/core/types.ts index 07cfcfc0..3ec7362a 100644 --- a/core/types.ts +++ b/core/types.ts @@ -40,18 +40,20 @@ export type RegisteredPlug = { } | { ty: "denoWorker"; manifest: DenoWorkerPlugManifestX; +} | { + ty: "asdf"; + manifest: PlugManifestBaseX; }; export type RegisteredPlugs = Map; -export interface InstallConfigBase { - version?: string; -} +export type InstallConfigBase = zod.input; // Describes a single installation done by a specific plugin. -export type InstallConfig = InstallConfigBase & { - plugName: string; -}; +export type InstallConfig = zod.input; +export type InstallConfigX = zod.infer; +export type AsdfInstallConfig = zod.input; +export type AsdfInstallConfigX = zod.infer; export interface GhjkConfig { /// Plugs explicitly added by the user @@ -75,41 +77,41 @@ export abstract class PlugBase { abstract manifest: PlugManifest; execEnv( - _env: ExecEnvArgs, + _args: ExecEnvArgs, ): Promise> | Record { return {}; } listBinPaths( - env: ListBinPathsArgs, + args: ListBinPathsArgs, ): Promise | string[] { return [ - std_path.joinGlobs([std_path.resolve(env.installPath, "bin"), "*"]), + std_path.joinGlobs([std_path.resolve(args.installPath, "bin"), "*"]), ]; } listLibPaths( - env: ListBinPathsArgs, + args: ListBinPathsArgs, ): Promise | string[] { return [ - std_path.joinGlobs([std_path.resolve(env.installPath, "lib"), "*"]), + std_path.joinGlobs([std_path.resolve(args.installPath, "lib"), "*"]), ]; } listIncludePaths( - env: ListBinPathsArgs, + args: ListBinPathsArgs, ): Promise | string[] { return [ - std_path.joinGlobs([std_path.resolve(env.installPath, "include"), "*"]), + std_path.joinGlobs([std_path.resolve(args.installPath, "include"), "*"]), ]; } - latestStable(env: ListAllEnv): Promise | string { + latestStable(args: ListAllArgs): Promise | string { return (async () => { logger().warning( `using default implementation of latestStable for plug ${this.manifest.name}`, ); - const allVers = await this.listAll(env); + const allVers = await this.listAll(args); if (allVers.length == 0) { throw new Error("no versions found"); } @@ -117,11 +119,11 @@ export abstract class PlugBase { })(); } - abstract listAll(env: ListAllEnv): Promise | string[]; + abstract listAll(args: ListAllArgs): Promise | string[]; - abstract download(env: DownloadArgs): Promise | void; + abstract download(args: DownloadArgs): Promise | void; - abstract install(env: InstallArgs): Promise | void; + abstract install(args: InstallArgs): Promise | void; } interface ASDF_CONFIG_EXAMPLE { @@ -150,9 +152,10 @@ export interface PlugArgsBase { installPath: string; depShims: DepShims; platform: PlatformInfo; + config: InstallConfigX; } -export interface ListAllEnv { +export interface ListAllArgs { depShims: DepShims; } diff --git a/core/utils.ts b/core/utils.ts index e190cc8e..e5adb48b 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -1,4 +1,6 @@ +import { std_path } from "../deps/common.ts"; import logger from "./logger.ts"; +import { DepShims, PlugDep } from "./types.ts"; export function dbg(val: T) { logger().debug("inline", val); return val; @@ -98,3 +100,33 @@ export async function spawnOutput( } 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: PlugDep, + 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; +} diff --git a/core/validators.ts b/core/validators.ts index 3393d9d0..da520057 100644 --- a/core/validators.ts +++ b/core/validators.ts @@ -40,11 +40,37 @@ const ambientAccessPlugManifest = plugManifestBase.merge( }), ); +const installConfigBase = zod.object({ + version: zod.string() + .nullish(), + conflictResolution: zod + .enum(["deferToNewer", "override"]) + .nullish() + .default("deferToNewer"), +}).passthrough(); + +const installConfig = installConfigBase.merge( + zod.object({ + plugName: zod.string().min(1), + }), +); + +const asdfInstallConfig = installConfig.merge( + zod.object({ + plugRepo: zod.string().url(), + installType: zod + .enum(["version", "ref"]), + }), +); + export default { plugDep, plugManifestBase, denoWorkerPlugManifest, ambientAccessPlugManifest, string: zod.string(), + installConfigBase, + installConfig, + asdfInstallConfig, stringArray: zod.string().min(1).array(), }; diff --git a/core/worker.ts b/core/worker.ts index 2f5e10af..1f36237f 100644 --- a/core/worker.ts +++ b/core/worker.ts @@ -7,45 +7,26 @@ import { type DownloadArgs, type ExecEnvArgs, type InstallArgs, - type ListAllEnv, + type ListAllArgs, type ListBinPathsArgs, PlugBase, } from "./types.ts"; -import { spawn, type SpawnOptions } from "./utils.ts"; export function isWorker() { return !!self.name; } -export function workerSpawn( - cmd: string[], - options: Omit = {}, -) { - // const outDecoder = new TextDecoderStream(); - // const errDecoder = new TextDecoderStream(); - // outDecoder.readable.pipeTo( - // new WritableStream({ - // write: console.log, - // }), - // ); - // errDecoder.readable.pipeTo( - // new WritableStream({ - // write: console.error, - // }), - // ); - return spawn(cmd, { - ...options, - // pipeOut: outDecoder.writable, - // pipeErr: errDecoder.writable, - }); -} - type WorkerReq = { + ty: "assert"; + arg: { + moduleSpecifier: string; + }; +} | { ty: "listAll"; - arg: ListAllEnv; + arg: ListAllArgs; } | { ty: "latestStable"; - arg: ListAllEnv; + arg: ListAllArgs; } | { ty: "execEnv"; arg: ExecEnvArgs; @@ -67,6 +48,9 @@ type WorkerReq = { }; type WorkerResp = { + ty: "assert"; + // success +} | { ty: "listAll"; payload: string[]; } | { @@ -93,63 +77,83 @@ type WorkerResp = { /// Make sure to call this before any `await` point or your /// plug might miss messages export function initDenoWorkerPlug

(plugInit: () => P) { - if (!isWorker()) { - throw new Error("expecteing to be running not running in Worker"); - } - 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 == "listAll") { - res = { - ty: req.ty, - // id: req.id, - payload: await plugInit().listAll(req.arg), - }; - } else if (req.ty === "latestStable") { - res = { - ty: req.ty, - payload: await plugInit().latestStable(req.arg), - }; - } else if (req.ty === "execEnv") { - res = { - ty: req.ty, - payload: await plugInit().execEnv(req.arg), - }; - } else if (req.ty === "listBinPaths") { - res = { - ty: req.ty, - payload: await plugInit().listBinPaths(req.arg), - }; - } else if (req.ty === "listLibPaths") { - res = { - ty: req.ty, - payload: await plugInit().listLibPaths(req.arg), - }; - } else if (req.ty === "listIncludePaths") { - res = { - ty: req.ty, - payload: await plugInit().listIncludePaths(req.arg), - }; - } else if (req.ty === "download") { - await plugInit().download(req.arg), + if (isWorker()) { + // 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 plugInit().listAll(req.arg), }; - } else if (req.ty === "install") { - await plugInit().install(req.arg), + } else if (req.ty === "latestStable") { res = { ty: req.ty, + payload: await plugInit().latestStable(req.arg), }; - } else { - logger().error("unrecognized worker request type", req); - throw new Error("unrecognized worker request type"); - } - self.postMessage(res); - }; + } else if (req.ty === "execEnv") { + res = { + ty: req.ty, + payload: await plugInit().execEnv(req.arg), + }; + } else if (req.ty === "listBinPaths") { + res = { + ty: req.ty, + payload: await plugInit().listBinPaths(req.arg), + }; + } else if (req.ty === "listLibPaths") { + res = { + ty: req.ty, + payload: await plugInit().listLibPaths(req.arg), + }; + } else if (req.ty === "listIncludePaths") { + res = { + ty: req.ty, + payload: await plugInit().listIncludePaths(req.arg), + }; + } else if (req.ty === "download") { + await plugInit().download(req.arg), + res = { + ty: req.ty, + }; + } else if (req.ty === "install") { + await plugInit().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; @@ -188,7 +192,7 @@ export class DenoWorkerPlug extends PlugBase { return resp; } - async listAll(env: ListAllEnv): Promise { + async listAll(env: ListAllArgs): Promise { const req: WorkerReq = { ty: "listAll", // id: crypto.randomUUID(), @@ -201,7 +205,7 @@ export class DenoWorkerPlug extends PlugBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async latestStable(env: ListAllEnv): Promise { + async latestStable(env: ListAllArgs): Promise { const req: WorkerReq = { ty: "latestStable", arg: env, diff --git a/deno.lock b/deno.lock index 7ddd4cbe..53c72ab1 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,10 @@ { "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.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", @@ -249,6 +254,137 @@ "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/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", @@ -272,6 +408,11 @@ "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://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/mod.ts b/mod.ts index 1097284e..68e573cd 100644 --- a/mod.ts +++ b/mod.ts @@ -48,7 +48,6 @@ function runCliShim( // freeze the object to prevent malicious tampering of the secureConfig export const ghjk = Object.freeze({ runCli: Object.freeze(runCliShim), - cx: self.ghjk, }); export { logger }; diff --git a/plug.ts b/plug.ts index d5abb667..d74d9fcc 100644 --- a/plug.ts +++ b/plug.ts @@ -10,16 +10,19 @@ import { type PlugDep, registerAmbientPlug, registerDenoPlug, + registerPlug, + validators, } from "./core/mod.ts"; import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; +import * as asdf from "./core/asdf.ts"; import logger from "./core/logger.ts"; export * from "./core/mod.ts"; export * from "./core/utils.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; -export { initDenoWorkerPlug, isWorker, workerSpawn } from "./core/worker.ts"; +export { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; export type * from "./core/mod.ts"; if (isWorker()) { @@ -79,6 +82,15 @@ export function registerDenoPlugGlobal

( } } +export function registerAsdfPlug() { + if (self.ghjk) { + registerPlug(self.ghjk, { + ty: "asdf", + manifest: validators.plugManifestBase.parse(asdf.manifest), + }); + } +} + export function registerAmbientPlugGlobal( manifestUnclean: AmbientAccessPlugManifest, ) { @@ -95,36 +107,6 @@ export function addInstallGlobal( } } -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: PlugDep, - 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; -} - /// This avoid re-downloading a file if it's already successfully downloaded before. export async function downloadFile( env: DownloadArgs, diff --git a/plugs/act.ts b/plugs/act.ts index c212ca7f..becc53cf 100644 --- a/plugs/act.ts +++ b/plugs/act.ts @@ -9,10 +9,10 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -80,7 +80,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", fileDwnPath, diff --git a/plugs/asdf.ts b/plugs/asdf.ts new file mode 100644 index 00000000..d82b4a1f --- /dev/null +++ b/plugs/asdf.ts @@ -0,0 +1,14 @@ +import { + addInstallGlobal, + asdf, + AsdfInstallConfig, + registerAsdfPlug, +} from "../plug.ts"; + +registerAsdfPlug(); +export default function install(config: Omit) { + addInstallGlobal({ + plugName: asdf.manifest.name, + ...config, + }); +} diff --git a/plugs/awk.ts b/plugs/awk.ts new file mode 100644 index 00000000..ccf37366 --- /dev/null +++ b/plugs/awk.ts @@ -0,0 +1,21 @@ +import { + addInstallGlobal, + type AmbientAccessPlugManifest, + registerAmbientPlugGlobal, +} from "../plug.ts"; + +export const manifest: AmbientAccessPlugManifest = { + name: "awk@aa", + version: "0.1.0", + execName: "awk", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPlugGlobal(manifest); +export default function install() { + addInstallGlobal({ + plugName: manifest.name, + }); +} diff --git a/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts index e6fdb14f..aa56322b 100644 --- a/plugs/cargo-binstall.ts +++ b/plugs/cargo-binstall.ts @@ -9,10 +9,10 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; // FIXME: find a better way to expose std_plug.plug_Id // that allows standard plugs to depend on each other @@ -31,7 +31,7 @@ export const manifest = { registerDenoPlugGlobal(manifest, () => new Plug()); -export default function cargo_binstall(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ plugName: manifest.name, ...config, @@ -73,7 +73,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(tar_aa_id, "tar", args.depShims), "xf", fileDwnPath, diff --git a/plugs/cargo-insta.ts b/plugs/cargo-insta.ts index cef9edba..5610fec9 100644 --- a/plugs/cargo-insta.ts +++ b/plugs/cargo-insta.ts @@ -8,9 +8,9 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -61,7 +61,7 @@ export class Plug extends PlugBase { ); return; } - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "cargo-insta", `--version`, diff --git a/plugs/curl.ts b/plugs/curl.ts new file mode 100644 index 00000000..acd56196 --- /dev/null +++ b/plugs/curl.ts @@ -0,0 +1,21 @@ +import { + addInstallGlobal, + type AmbientAccessPlugManifest, + registerAmbientPlugGlobal, +} from "../plug.ts"; + +export const manifest: AmbientAccessPlugManifest = { + name: "curl@aa", + version: "0.1.0", + execName: "curl", + versionExtractFlag: "--version", + versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPlugGlobal(manifest); +export default function install() { + addInstallGlobal({ + plugName: manifest.name, + }); +} diff --git a/plugs/jco.ts b/plugs/jco.ts index cb59aa44..57d79fde 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -5,16 +5,16 @@ import { downloadFile, InstallArgs, type InstallConfigBase, - ListAllEnv, + ListAllArgs, pathWithDepShims, type PlatformInfo, PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; import node from "./node.ts"; import * as std_plugs from "../std.ts"; @@ -42,7 +42,7 @@ export default function install({ version }: InstallConfigBase = {}) { class Plug extends PlugBase { manifest = manifest; - async listAll(_env: ListAllEnv) { + async listAll(_env: ListAllArgs) { const metadataRequest = await fetch( `https://registry.npmjs.org/@bytecodealliance/jco`, { @@ -73,7 +73,7 @@ class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", fileDwnPath, @@ -91,7 +91,7 @@ class Plug extends PlugBase { ), args.installPath, ); - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.node_org, "npm", args.depShims), "install", "--no-fund", diff --git a/plugs/mold.ts b/plugs/mold.ts index 8d16cc77..525d264e 100644 --- a/plugs/mold.ts +++ b/plugs/mold.ts @@ -9,10 +9,10 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -75,7 +75,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", fileDwnPath, @@ -118,8 +118,9 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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`; + 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/plugs/node.ts b/plugs/node.ts index 3affbbca..d5c51fa4 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -6,15 +6,15 @@ import { ExecEnvArgs, InstallArgs, type InstallConfigBase, - ListAllEnv, + ListAllArgs, type PlatformInfo, PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; // import * as std_plugs from "../std.ts"; @@ -57,7 +57,7 @@ export class Plug extends PlugBase { return []; } - async listAll(_env: ListAllEnv) { + async listAll(_env: ListAllArgs) { const metadataRequest = await fetch(`https://nodejs.org/dist/index.json`); const metadata = await metadataRequest.json() as { version: string }[]; @@ -77,7 +77,7 @@ export class Plug extends PlugBase { artifactUrl(args.installVersion, args.platform), ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(tar_aa_id, "tar", args.depShims), "xf", fileDwnPath, diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts index 611c493a..22a6edaa 100644 --- a/plugs/pnpm.ts +++ b/plugs/pnpm.ts @@ -4,7 +4,7 @@ import { downloadFile, InstallArgs, type InstallConfigBase, - ListAllEnv, + ListAllArgs, type PlatformInfo, PlugBase, registerDenoPlugGlobal, @@ -31,7 +31,7 @@ export default function install({ version }: InstallConfigBase = {}) { class Plug extends PlugBase { manifest = manifest; - async listAll(_env: ListAllEnv) { + async listAll(_env: ListAllArgs) { const metadataRequest = await fetch( `https://registry.npmjs.org/@pnpm/exe`, { diff --git a/plugs/wasm-opt.ts b/plugs/wasm-opt.ts index 816ff34c..e272790d 100644 --- a/plugs/wasm-opt.ts +++ b/plugs/wasm-opt.ts @@ -8,9 +8,9 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -61,7 +61,7 @@ export class Plug extends PlugBase { ); return; } - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-opt", `--version`, diff --git a/plugs/wasm-tools.ts b/plugs/wasm-tools.ts index 410cb70b..37d33071 100644 --- a/plugs/wasm-tools.ts +++ b/plugs/wasm-tools.ts @@ -8,9 +8,9 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -61,7 +61,7 @@ export class Plug extends PlugBase { ); return; } - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-tools", `--version`, diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 94d021d7..778f4de8 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -10,10 +10,10 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - workerSpawn, } from "../plug.ts"; import * as std_plugs from "../std.ts"; @@ -110,7 +110,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await workerSpawn([ + await spawn([ depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), "xf", fileDwnPath, diff --git a/tests/ambient.ts b/tests/ambient.ts index c84a219b..54a58d72 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -5,6 +5,8 @@ import { type AmbientAccessPlugManifest } from "../core/types.ts"; import * as tar from "../plugs/tar.ts"; import * as git from "../plugs/git.ts"; +import * as curl from "../plugs/curl.ts"; +import * as awk from "../plugs/awk.ts"; const manifests = [ { @@ -17,6 +19,8 @@ const manifests = [ }, tar.manifest, git.manifest, + curl.manifest, + awk.manifest, ]; for (const manifest of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { From 612731cfbc9df254dc40d303033f7041edda373d Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:22:18 +0300 Subject: [PATCH 13/43] fix: missing changes --- core/asdf.ts | 14 ++++++++------ plug.ts | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/asdf.ts b/core/asdf.ts index 10ab1b5c..06bc0189 100644 --- a/core/asdf.ts +++ b/core/asdf.ts @@ -16,13 +16,15 @@ import { import * as std_plugs from "../std.ts"; import { std_fs, std_path } from "../deps/common.ts"; +export const manifest = { + name: "asdf@asdf", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [std_plugs.tar_aa, std_plugs.git_aa], +}; + export class AsdfPlug extends PlugBase { - manifest = { - name: "asdf@asdf", - version: "0.1.0", - moduleSpecifier: import.meta.url, - deps: [std_plugs.tar_aa, std_plugs.git_aa], - }; + manifest = manifest; constructor( public pluginDir: string, public config: AsdfInstallConfigX, diff --git a/plug.ts b/plug.ts index d74d9fcc..2e2a30a8 100644 --- a/plug.ts +++ b/plug.ts @@ -23,6 +23,7 @@ export * from "./core/utils.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; export { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; +export * as asdf from "./core/asdf.ts"; export type * from "./core/mod.ts"; if (isWorker()) { From 97d1c6f1f98021200fec37c1ecbf427f3c9d29c4 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 07:27:03 +0000 Subject: [PATCH 14/43] feat(core): asdf support --- cli/sync.ts | 29 ++++++++++++++-------------- core/asdf.ts | 44 +++++++++++++++++++++++++++++++------------ core/mod.ts | 14 +++++--------- core/utils.ts | 11 ++++++++++- core/validators.ts | 2 +- deno.jsonc | 2 +- ghjk.ts | 11 ++++++++--- plugs/awk.ts | 21 --------------------- std.ts | 7 +++++++ tests/ambient.ts | 2 -- tests/e2e.ts | 24 +++++++++++++++++++++++ tests/test.Dockerfile | 8 ++++++++ 12 files changed, 110 insertions(+), 65 deletions(-) delete mode 100644 plugs/awk.ts diff --git a/cli/sync.ts b/cli/sync.ts index 4497a8b4..9ecf940e 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -4,7 +4,6 @@ import { type AmbientAccessPlugManifestX, type DenoWorkerPlugManifestX, type DepShims, - getInstallId, type GhjkCtx, type InstallConfig, InstallConfigX, @@ -13,9 +12,10 @@ import { validators, } from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; -import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; +import { AVAIL_CONCURRENCY, dirs, } from "./utils.ts"; import { AmbientAccessPlug } from "../core/ambient.ts"; import { AsdfPlug } from "../core/asdf.ts"; +import { getInstallId } from "../core/utils.ts"; async function findConfig(path: string): Promise { let current = path; @@ -49,9 +49,9 @@ async function writeLoader(envDir: string, env: Record) { await Deno.writeTextFile( `${envDir}/loader.sh`, `export GHJK_CLEANUP="";\n` + - Object.entries(env).map(([k, v]) => - `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` - ).join("\n"), + Object.entries(env).map(([k, v]) => + `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` + ).join("\n"), ); } @@ -200,8 +200,7 @@ export async function sync(cx: GhjkCtx) { const conflict = env[key]; if (conflict) { throw new Error( - `duplicate env var found ${key} from installs ${instId} & ${ - conflict[1] + `duplicate env var found ${key} from installs ${instId} & ${conflict[1] }`, ); } @@ -243,8 +242,7 @@ function buildInstallGraph(cx: GhjkCtx) { cx.allowedDeps.get(inst.plugName); if (!regPlug) { throw new Error( - `unable to find plugin "${inst.plugName}" specified by install ${ - JSON.stringify(inst) + `unable to find plugin "${inst.plugName}" specified by install ${JSON.stringify(inst) }`, ); } @@ -374,6 +372,7 @@ async function doInstall( `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, ); } + const installId = getInstallId(inst); const installVersion = validators.string.parse( inst.version ?? await plug.latestStable({ depShims, @@ -382,13 +381,13 @@ async function doInstall( const installPath = std_path.resolve( envDir, "installs", - plug.manifest.name, + installId, installVersion, ); const downloadPath = std_path.resolve( envDir, "downloads", - plug.manifest.name, + installId, installVersion, ); const baseArgs: PlugArgsBase = { @@ -400,9 +399,9 @@ async function doInstall( config: inst, }; { - logger().info(`downloading ${inst.plugName}:${installVersion}`); + logger().info(`downloading ${installId}:${installVersion}`); const tmpDirPath = await Deno.makeTempDir({ - prefix: `ghjk_download_${inst.plugName}@${installVersion}_`, + prefix: `ghjk_download_${installId}:${installVersion}_`, }); await plug.download({ ...baseArgs, @@ -412,9 +411,9 @@ async function doInstall( void Deno.remove(tmpDirPath, { recursive: true }); } { - logger().info(`installing ${inst.plugName}:${installVersion}`); + logger().info(`installing ${installId}:${installVersion}`); const tmpDirPath = await Deno.makeTempDir({ - prefix: `ghjk_install_${inst.plugName}@${installVersion}_`, + prefix: `ghjk_install_${installId}@${installVersion}_`, }); await plug.install({ ...baseArgs, diff --git a/core/asdf.ts b/core/asdf.ts index 06bc0189..23188d3a 100644 --- a/core/asdf.ts +++ b/core/asdf.ts @@ -12,20 +12,31 @@ import { pathWithDepShims, spawn, spawnOutput, + getInstallId } from "./utils.ts"; -import * as std_plugs from "../std.ts"; +// import * as std_plugs from "../std.ts"; import { std_fs, std_path } from "../deps/common.ts"; +const curl_aa_id = { + id: "curl@aa", +}; + +const git_aa_id = { + id: "git@aa", +}; + export const manifest = { name: "asdf@asdf", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [std_plugs.tar_aa, std_plugs.git_aa], + // deps: [std_plugs.tar_aa, std_plugs.git_aa], + deps: [curl_aa_id, git_aa_id], }; export class AsdfPlug extends PlugBase { manifest = manifest; constructor( + public asdfDir: string, public pluginDir: string, public config: AsdfInstallConfigX, ) { @@ -37,17 +48,16 @@ export class AsdfPlug extends PlugBase { depShims: DepShims, ) { const asdfDir = std_path.resolve(envDir, "asdf"); - const url = new URL(installConfig.plugRepo); - const pluginId = `${url.hostname}~${url.pathname.replaceAll("/", ".")}`; + const installId = getInstallId(installConfig); - const pluginDir = std_path.resolve(asdfDir, pluginId); + const pluginDir = std_path.resolve(asdfDir, installId); if (!await std_fs.exists(pluginDir)) { const tmpCloneDirPath = await Deno.makeTempDir({ - prefix: `ghjk_asdf_clone_${pluginId}@$asdf_`, + prefix: `ghjk_asdf_clone_${installId}_`, }); await spawn( [ - depBinShimPath(std_plugs.git_aa, "git", depShims), + depBinShimPath(git_aa_id, "git", depShims), "clone", installConfig.plugRepo, "--depth", @@ -61,14 +71,16 @@ export class AsdfPlug extends PlugBase { ); void Deno.remove(tmpCloneDirPath, { recursive: true }); } - return new AsdfPlug(pluginDir, installConfig); + return new AsdfPlug(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); + return out.split(" ").filter((str) => str.length > 0).map((str) => + str.trim() + ); } async latestStable(args: ListAllArgs): Promise { @@ -102,9 +114,17 @@ export class AsdfPlug extends PlugBase { ASDF_INSTALL_PATH: args.installPath, }, }); - return out.split(" ").filter((str) => str.length > 0); + return out.split(" ").filter((str) => str.length > 0).map((str) => + str.trim() + ); } - async download(args: DownloadArgs): Promise { + + 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"), ], { @@ -117,7 +137,7 @@ export class AsdfPlug extends PlugBase { }, }); } - async install(args: InstallArgs): Promise { + async install(args: InstallArgs) { await spawn([ std_path.resolve(this.pluginDir, "bin", "install"), ], { diff --git a/core/mod.ts b/core/mod.ts index 657801cd..5035ff7b 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,8 +1,9 @@ export * from "./types.ts"; export { default as validators } from "./validators.ts"; -import { semver } from "../deps/common.ts"; +import { semver, } from "../deps/common.ts"; import type { AmbientAccessPlugManifest, + AsdfInstallConfig, DenoWorkerPlugManifest, GhjkConfig, InstallConfig, @@ -15,10 +16,6 @@ export const Ghjk = { cwd: Deno.cwd, }; -export function getInstallId(install: InstallConfig) { - return install.plugName; -} - export function registerDenoPlug( cx: GhjkConfig, manifestUnclean: DenoWorkerPlugManifest, @@ -48,7 +45,7 @@ export function registerPlug( ) { throw new Error( `Two instances of plugin "${manifest.name}" found with ` + - `both set to "${manifest.conflictResolution}" conflictResolution"`, + `both set to "${manifest.conflictResolution}" conflictResolution"`, ); } else if (conflict.conflictResolution == "override") { logger().debug("plug rejected due to override", { @@ -70,7 +67,7 @@ export function registerPlug( ) { throw new Error( `Two instances of the plug "${manifest.name}" found with an identical version` + - `and both set to "deferToNewer" conflictResolution.`, + `and both set to "deferToNewer" conflictResolution.`, ); } else if ( semver.compare( @@ -101,8 +98,7 @@ export function addInstall( ) { if (!cx.plugs.has(config.plugName)) { throw new Error( - `unrecognized plug "${config.plugName}" specified by install ${ - JSON.stringify(config) + `unrecognized plug "${config.plugName}" specified by install ${JSON.stringify(config) }`, ); } diff --git a/core/utils.ts b/core/utils.ts index e5adb48b..8b80e6b7 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -1,6 +1,6 @@ import { std_path } from "../deps/common.ts"; import logger from "./logger.ts"; -import { DepShims, PlugDep } from "./types.ts"; +import type { DepShims, PlugDep, InstallConfig, AsdfInstallConfig } from "./types.ts"; export function dbg(val: T) { logger().debug("inline", val); return val; @@ -130,3 +130,12 @@ export function depBinShimPath( } return path; } + +export function getInstallId(install: InstallConfig | AsdfInstallConfig) { + if ("plugRepo" in install) { + const url = new URL(install.plugRepo); + const pluginId = `${url.hostname}:${url.pathname.replaceAll("/", "=")}`; + return `asdf-${pluginId}`; + } + return install.plugName; +} diff --git a/core/validators.ts b/core/validators.ts index da520057..fdf04364 100644 --- a/core/validators.ts +++ b/core/validators.ts @@ -26,7 +26,7 @@ const denoWorkerPlugManifest = plugManifestBase.merge( const ambientAccessPlugManifest = plugManifestBase.merge( zod.object({ execName: zod.string().min(1), - versionExtractFlag: zod.enum(["version", "-v", "--version", "-v"]), + versionExtractFlag: zod.enum(["version", "-v", "--version", "-V", "-W version"]), versionExtractRegex: zod.string().refine((str) => new RegExp(str), { message: "invalid RegExp string", }), diff --git a/deno.jsonc b/deno.jsonc index 8c972308..55eda3b9 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 --allow-read --allow-env tests/*", + "test": "deno test --fail-fast --parallel --unstable --allow-run=docker,sudo,which,ls,tar,git,curl --allow-read --allow-env tests/*", "cache": "deno cache deps/*" } } diff --git a/ghjk.ts b/ghjk.ts index b832d18f..e525ee67 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -23,16 +23,21 @@ import wasm_tools from "./plugs/wasm-tools.ts"; import wasm_opt from "./plugs/wasm-opt.ts"; import cargo_insta from "./plugs/cargo-insta.ts"; import jco from "./plugs/jco.ts"; -// import mold from "./plugs/mold.ts"; +import mold from "./plugs/mold.ts"; import act from "./plugs/act.ts"; +import asdf from "./plugs/asdf.ts"; // node({}); // wasmedge({}); // pnpm({}); -// cargo_binstall({}); +cargo_binstall({}); // wasm_tools({}); // wasm_opt({}); // cargo_insta({}); // jco({}); // mold({}); -act({}); +// act({}); +// asdf({ +// plugRepo: "https://github.com/asdf-community/asdf-zig", +// installType: "version", +// }); diff --git a/plugs/awk.ts b/plugs/awk.ts deleted file mode 100644 index ccf37366..00000000 --- a/plugs/awk.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - addInstallGlobal, - type AmbientAccessPlugManifest, - registerAmbientPlugGlobal, -} from "../plug.ts"; - -export const manifest: AmbientAccessPlugManifest = { - name: "awk@aa", - version: "0.1.0", - execName: "awk", - versionExtractFlag: "--version", - versionExtractRegex: "(\\d+\\.\\d+\\.\\d+)", - versionExtractRegexFlags: "", -}; - -registerAmbientPlugGlobal(manifest); -export default function install() { - addInstallGlobal({ - plugName: manifest.name, - }); -} diff --git a/std.ts b/std.ts index 16a9b6c0..7abbe230 100644 --- a/std.ts +++ b/std.ts @@ -4,6 +4,7 @@ import { PlugDep, RegisteredPlug } from "./core/types.ts"; import validators from "./core/validators.ts"; import { manifest as man_tar_aa } from "./plugs/tar.ts"; import { manifest as man_git_aa } from "./plugs/git.ts"; +import { manifest as man_curl_aa } from "./plugs/curl.ts"; import { manifest as man_cbin_ghrel } from "./plugs/cargo-binstall.ts"; import { manifest as man_node_org } from "./plugs/node.ts"; import { manifest as man_pnpm_ghrel } from "./plugs/pnpm.ts"; @@ -11,6 +12,7 @@ import { manifest as man_pnpm_ghrel } from "./plugs/pnpm.ts"; const aaPlugs: RegisteredPlug[] = [ man_tar_aa, man_git_aa, + man_curl_aa, ] .map((man) => ({ ty: "ambientAccess", @@ -42,6 +44,10 @@ export const git_aa = Object.freeze({ id: man_git_aa.name, } as PlugDep); +export const curl_aa = Object.freeze({ + id: man_curl_aa.name, +} as PlugDep); + export const cbin_ghrel = Object.freeze({ id: man_cbin_ghrel.name, } as PlugDep); @@ -53,3 +59,4 @@ export const node_org = Object.freeze({ export const pnpm_ghrel = Object.freeze({ id: man_pnpm_ghrel.name, } as PlugDep); + diff --git a/tests/ambient.ts b/tests/ambient.ts index 54a58d72..5b0ee113 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -6,7 +6,6 @@ import { type AmbientAccessPlugManifest } from "../core/types.ts"; import * as tar from "../plugs/tar.ts"; import * as git from "../plugs/git.ts"; import * as curl from "../plugs/curl.ts"; -import * as awk from "../plugs/awk.ts"; const manifests = [ { @@ -20,7 +19,6 @@ const manifests = [ tar.manifest, git.manifest, curl.manifest, - awk.manifest, ]; for (const manifest of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { diff --git a/tests/e2e.ts b/tests/e2e.ts index b8548ac5..02d79a10 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -163,4 +163,28 @@ await dockerTest([ }`, ePoint: `jco --version`, }, + // big + { + name: "asdf-zig", + imports: `import plug from "$ghjk/plugs/asdf.ts"`, + confFn: `async () => { + plug({ + plugRepo: "https://github.com/asdf-community/asdf-zig", + installType: "version", + }); + }`, + ePoint: `zig version`, + }, + // big + { + name: "asdf-python", + imports: `import plug from "$ghjk/plugs/asdf.ts"`, + confFn: `async () => { + plug({ + plugRepo: "https://github.com/asdf-community/asdf-python", + installType: "version", + }); + }`, + ePoint: `python --version`, + }, ]); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 2910e00b..dd1e0851 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -1,5 +1,13 @@ FROM docker.io/denoland/deno:debian-1.38.0 +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt update; \ + apt install --yes \ + git curl xz-utils \ + ;\ + apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; + ENV SHELL=/bin/bash WORKDIR /ghjk From 411ee78b5891b6f0a6173845b218fbad7a6f4482 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 07:28:45 +0000 Subject: [PATCH 15/43] fix: formatting --- std.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/std.ts b/std.ts index 7abbe230..db26bdd1 100644 --- a/std.ts +++ b/std.ts @@ -59,4 +59,3 @@ export const node_org = Object.freeze({ export const pnpm_ghrel = Object.freeze({ id: man_pnpm_ghrel.name, } as PlugDep); - From 1b8bcfbd99f2a303c6f7eb60f6df7e220df4667c Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 24 Nov 2023 07:33:53 +0000 Subject: [PATCH 16/43] fix(tests): disable asdf-python --- tests/e2e.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/e2e.ts b/tests/e2e.ts index 02d79a10..477c0966 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -175,16 +175,16 @@ await dockerTest([ }`, ePoint: `zig version`, }, - // big - { - name: "asdf-python", - imports: `import plug from "$ghjk/plugs/asdf.ts"`, - confFn: `async () => { - plug({ - plugRepo: "https://github.com/asdf-community/asdf-python", - installType: "version", - }); - }`, - ePoint: `python --version`, - }, + // // big + // { + // name: "asdf-python", + // imports: `import plug from "$ghjk/plugs/asdf.ts"`, + // confFn: `async () => { + // plug({ + // plugRepo: "https://github.com/asdf-community/asdf-python", + // installType: "version", + // }); + // }`, + // ePoint: `python --version`, + // }, ]); From e52fbcf317248ce9de3df8b5c36942ec6434fe13 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:34:06 +0000 Subject: [PATCH 17/43] feat(plug): protoc --- ghjk.ts | 4 +- plugs/protoc.ts | 122 +++++++++++++++++++++++++++++++++++++++++++++++ plugs/unzip.ts | 21 ++++++++ std.ts | 6 +++ tests/ambient.ts | 2 + 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 plugs/protoc.ts create mode 100644 plugs/unzip.ts diff --git a/ghjk.ts b/ghjk.ts index e525ee67..890c4cb2 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -26,11 +26,12 @@ import jco from "./plugs/jco.ts"; import mold from "./plugs/mold.ts"; import act from "./plugs/act.ts"; import asdf from "./plugs/asdf.ts"; +import protoc from "./plugs/protoc.ts"; // node({}); // wasmedge({}); // pnpm({}); -cargo_binstall({}); +// cargo_binstall({}); // wasm_tools({}); // wasm_opt({}); // cargo_insta({}); @@ -41,3 +42,4 @@ cargo_binstall({}); // plugRepo: "https://github.com/asdf-community/asdf-zig", // installType: "version", // }); +protoc({}); diff --git a/plugs/protoc.ts b/plugs/protoc.ts new file mode 100644 index 00000000..238c9950 --- /dev/null +++ b/plugs/protoc.ts @@ -0,0 +1,122 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "protoc@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.unzip_aa, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "protocolbuffers"; +const repoName = "protobuf"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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 spawn([ + depBinShimPath(std_plugs.unzip_aa, "unzip", args.depShims), + fileDwnPath, + "-d", + 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/plugs/unzip.ts b/plugs/unzip.ts new file mode 100644 index 00000000..63f704ad --- /dev/null +++ b/plugs/unzip.ts @@ -0,0 +1,21 @@ +import { + addInstallGlobal, + type AmbientAccessPlugManifest, + registerAmbientPlugGlobal, +} from "../plug.ts"; + +export const manifest: AmbientAccessPlugManifest = { + name: "unzip@aa", + version: "0.1.0", + execName: "unzip", + versionExtractFlag: "-v", + versionExtractRegex: "(\\d+\\.\\d+)", + versionExtractRegexFlags: "", +}; + +registerAmbientPlugGlobal(manifest); +export default function install() { + addInstallGlobal({ + plugName: manifest.name, + }); +} diff --git a/std.ts b/std.ts index db26bdd1..636593dd 100644 --- a/std.ts +++ b/std.ts @@ -5,6 +5,7 @@ import validators from "./core/validators.ts"; import { manifest as man_tar_aa } from "./plugs/tar.ts"; import { manifest as man_git_aa } from "./plugs/git.ts"; import { manifest as man_curl_aa } from "./plugs/curl.ts"; +import { manifest as man_unzip_aa } from "./plugs/unzip.ts"; import { manifest as man_cbin_ghrel } from "./plugs/cargo-binstall.ts"; import { manifest as man_node_org } from "./plugs/node.ts"; import { manifest as man_pnpm_ghrel } from "./plugs/pnpm.ts"; @@ -13,6 +14,7 @@ const aaPlugs: RegisteredPlug[] = [ man_tar_aa, man_git_aa, man_curl_aa, + man_unzip_aa, ] .map((man) => ({ ty: "ambientAccess", @@ -48,6 +50,10 @@ export const curl_aa = Object.freeze({ id: man_curl_aa.name, } as PlugDep); +export const unzip_aa = Object.freeze({ + id: man_unzip_aa.name, +} as PlugDep); + export const cbin_ghrel = Object.freeze({ id: man_cbin_ghrel.name, } as PlugDep); diff --git a/tests/ambient.ts b/tests/ambient.ts index 5b0ee113..d9a56c07 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -6,6 +6,7 @@ import { type AmbientAccessPlugManifest } from "../core/types.ts"; import * as tar from "../plugs/tar.ts"; import * as git from "../plugs/git.ts"; import * as curl from "../plugs/curl.ts"; +import * as unzip from "../plugs/unzip.ts"; const manifests = [ { @@ -19,6 +20,7 @@ const manifests = [ tar.manifest, git.manifest, curl.manifest, + unzip.manifest, ]; for (const manifest of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { From 4ee2592c8fac33eac7b0eab2637e253d70e4541b Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:39:06 +0000 Subject: [PATCH 18/43] feat(tests): `protoc` --- deno.jsonc | 2 +- tests/e2e.ts | 9 +++++++++ tests/test.Dockerfile | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/deno.jsonc b/deno.jsonc index 55eda3b9..26fb7497 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 --allow-read --allow-env 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/*" } } diff --git a/tests/e2e.ts b/tests/e2e.ts index 477c0966..fa6c0f4a 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -73,6 +73,15 @@ await (${confFn.toString()})()`; // order tests by download size to make failed runs less expensive await dockerTest([ + // 3 megs + { + name: "protoc", + imports: `import plug from "$ghjk/plugs/protoc.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `protoc --version`, + }, // 7 megs { name: "act", diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index dd1e0851..753f4673 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -4,7 +4,7 @@ RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ apt install --yes \ - git curl xz-utils \ + git curl xz-utils unzip \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; From 627e3d478e79179f9b87a498ff813e179f65ead0 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 27 Nov 2023 00:59:37 +0000 Subject: [PATCH 19/43] refactor(hooks): move to bash-preexec --- cli/hooks.ts | 48 +++++++++++++++++++++++++------------------ tests/e2e.ts | 7 +++++-- tests/test.Dockerfile | 6 +++--- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/cli/hooks.ts b/cli/hooks.ts index 58ab36bd..8f14d13b 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -27,11 +27,14 @@ console.log = log; mod.ghjk.runCli(Deno.args.slice(1), mod.options); `, + "hooks/bash-preexec.sh": await ( + await fetch( + "https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh", + ) + ).text(), // the hook run before every prompt draw in bash "hooks/hook.sh": ` -ghjk_already_run=false - -clean_up_paths() { +__ghjk_clean_up_paths() { PATH=$(echo "$PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') PATH="$\{PATH%:\}" LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') @@ -44,12 +47,7 @@ clean_up_paths() { CPLUS_INCLUDE_PATH="$\{CPLUS_INCLUDE_PATH%:\}" } -ghjk_hook() { - # Check if the trap has already executed - if [[ "$ghjk_already_run" = true ]]; then - return - fi - ghjk_already_run=true +init_ghjk() { if [[ -v GHJK_CLEANUP ]]; then eval $GHJK_CLEANUP unset GHJK_CLEANUP @@ -59,7 +57,7 @@ ghjk_hook() { if [ -e "$cur_dir/ghjk.ts" ]; then envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" if [ -d "$envDir" ]; then - clean_up_paths + __ghjk_clean_up_paths PATH="$envDir/shims/bin:$PATH" LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" @@ -75,26 +73,36 @@ ghjk_hook() { echo -e "\e[38;2;255;69;0m[ghjk] Uninstalled runtime found, please sync...\e[0m" echo "$envDir" fi - alias ghjk="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + export ghjk_alias="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return fi cur_dir="$(dirname "$cur_dir")" done - clean_up_paths - alias ghjk="echo 'No ghjk.ts config found.'" + __ghjk_clean_up_paths + export ghjk_alias="echo 'No ghjk.ts config found.'" } -trap 'ghjk_hook' DEBUG +ghjk_alias="echo 'No ghjk.ts config found.'" -set_hook_flag() { - ghjk_already_run=false +ghjk () { + eval "$ghjk_alias" $*; } -if [[ -n "$PROMPT_COMMAND" ]]; then - PROMPT_COMMAND+=";" -fi +# export function for non-interactive use +export -f ghjk +export -f init_ghjk +export -f __ghjk_clean_up_paths -PROMPT_COMMAND+="set_hook_flag;" +hooksDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")") +source "$hooksDir/bash-preexec.sh" +precmd() { + # Check if the trap has already executed + if [[ "$ghjk_already_run" = true ]]; then + return + fi + ghjk_already_run=true + init_ghjk +} `, // the hook run before every prompt draw in fish diff --git a/tests/e2e.ts b/tests/e2e.ts index fa6c0f4a..16c59446 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -59,8 +59,11 @@ await (${confFn.toString()})()`; tag, "bash", "-c", - "-i", - ePoint, + ` + source ~/.bashrc + init_ghjk + ${ePoint} + `, ], { env }); await spawn([ ...dockerCmd, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 753f4673..43283ebc 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -24,11 +24,11 @@ RUN cat > ghjk.ts < Date: Mon, 27 Nov 2023 02:00:11 +0000 Subject: [PATCH 20/43] fix(hooks): `init_ghjk` directly in hooks.sh --- cli/hooks.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cli/hooks.ts b/cli/hooks.ts index 8f14d13b..5f8a402b 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -83,7 +83,6 @@ init_ghjk() { } ghjk_alias="echo 'No ghjk.ts config found.'" - ghjk () { eval "$ghjk_alias" $*; } @@ -93,16 +92,15 @@ export -f ghjk export -f init_ghjk export -f __ghjk_clean_up_paths +# use precmd to check for ghjk.ts before every prompt draw hooksDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")") source "$hooksDir/bash-preexec.sh" precmd() { - # Check if the trap has already executed - if [[ "$ghjk_already_run" = true ]]; then - return - fi - ghjk_already_run=true init_ghjk } + +# try loading any relevant ghjk.ts right away +init_ghjk `, // the hook run before every prompt draw in fish From eee3a6ddc1ee59d605d1426f1845e482cf5b8e78 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:10:24 +0000 Subject: [PATCH 21/43] feat(plug): earthly --- ghjk.ts | 19 ++------- plugs/earthly.ts | 108 +++++++++++++++++++++++++++++++++++++++++++++++ tests/e2e.ts | 9 ++++ 3 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 plugs/earthly.ts diff --git a/ghjk.ts b/ghjk.ts index 890c4cb2..6c72ed16 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,18 +1,3 @@ -/* -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 "./plugs/node.ts"; import install from "./plugs/wasmedge.ts"; @@ -27,6 +12,7 @@ import mold from "./plugs/mold.ts"; import act from "./plugs/act.ts"; import asdf from "./plugs/asdf.ts"; import protoc from "./plugs/protoc.ts"; +import earthly from "./plugs/earthly.ts"; // node({}); // wasmedge({}); @@ -42,4 +28,5 @@ import protoc from "./plugs/protoc.ts"; // plugRepo: "https://github.com/asdf-community/asdf-zig", // installType: "version", // }); -protoc({}); +// protoc({}); +earthly({}); diff --git a/plugs/earthly.ts b/plugs/earthly.ts new file mode 100644 index 00000000..534090bc --- /dev/null +++ b/plugs/earthly.ts @@ -0,0 +1,108 @@ +import { + addInstallGlobal, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + std_fs, + std_path, +} from "../plug.ts"; + +const manifest = { + name: "earthly@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "earthly"; +const repoName = "earthly"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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/tests/e2e.ts b/tests/e2e.ts index 16c59446..c216c59e 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -157,6 +157,15 @@ await dockerTest([ }`, ePoint: `wasm-opt --version`, }, + // 42 megs + { + name: "pnpm", + imports: `import plug from "$ghjk/plugs/earthly.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `earthly --version`, + }, // 56 megs { name: "pnpm", From 915abbca7a3d9d34f10776b3c2b4d4539a5dadbe Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 28 Nov 2023 23:08:58 +0000 Subject: [PATCH 22/43] fix: change installId format to something less problematic --- core/utils.ts | 9 +++++++-- ghjk.ts | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/utils.ts b/core/utils.ts index 8b80e6b7..624362c3 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -1,6 +1,11 @@ import { std_path } from "../deps/common.ts"; import logger from "./logger.ts"; -import type { DepShims, PlugDep, InstallConfig, AsdfInstallConfig } from "./types.ts"; +import type { + AsdfInstallConfig, + DepShims, + InstallConfig, + PlugDep, +} from "./types.ts"; export function dbg(val: T) { logger().debug("inline", val); return val; @@ -134,7 +139,7 @@ export function depBinShimPath( export function getInstallId(install: InstallConfig | AsdfInstallConfig) { if ("plugRepo" in install) { const url = new URL(install.plugRepo); - const pluginId = `${url.hostname}:${url.pathname.replaceAll("/", "=")}`; + const pluginId = `${url.hostname}-${url.pathname.replaceAll("/", ".")}`; return `asdf-${pluginId}`; } return install.plugName; diff --git a/ghjk.ts b/ghjk.ts index 6c72ed16..a7410fb4 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -28,5 +28,5 @@ import earthly from "./plugs/earthly.ts"; // plugRepo: "https://github.com/asdf-community/asdf-zig", // installType: "version", // }); -// protoc({}); -earthly({}); +protoc({}); +// earthly({}); From 6fe347eea28a8ac3297219a946956813d790217a Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 28 Nov 2023 23:57:34 +0000 Subject: [PATCH 23/43] feat(plug): ruff --- core/mod.ts | 10 ++-- ghjk.ts | 4 +- plugs/cargo-insta.ts | 1 - plugs/ruff.ts | 126 +++++++++++++++++++++++++++++++++++++++++++ plugs/wasm-opt.ts | 1 - plugs/wasm-tools.ts | 1 - tests/e2e.ts | 9 ++++ 7 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 plugs/ruff.ts diff --git a/core/mod.ts b/core/mod.ts index 5035ff7b..49494343 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,9 +1,8 @@ export * from "./types.ts"; export { default as validators } from "./validators.ts"; -import { semver, } from "../deps/common.ts"; +import { semver } from "../deps/common.ts"; import type { AmbientAccessPlugManifest, - AsdfInstallConfig, DenoWorkerPlugManifest, GhjkConfig, InstallConfig, @@ -45,7 +44,7 @@ export function registerPlug( ) { throw new Error( `Two instances of plugin "${manifest.name}" found with ` + - `both set to "${manifest.conflictResolution}" conflictResolution"`, + `both set to "${manifest.conflictResolution}" conflictResolution"`, ); } else if (conflict.conflictResolution == "override") { logger().debug("plug rejected due to override", { @@ -67,7 +66,7 @@ export function registerPlug( ) { throw new Error( `Two instances of the plug "${manifest.name}" found with an identical version` + - `and both set to "deferToNewer" conflictResolution.`, + `and both set to "deferToNewer" conflictResolution.`, ); } else if ( semver.compare( @@ -98,7 +97,8 @@ export function addInstall( ) { if (!cx.plugs.has(config.plugName)) { throw new Error( - `unrecognized plug "${config.plugName}" specified by install ${JSON.stringify(config) + `unrecognized plug "${config.plugName}" specified by install ${ + JSON.stringify(config) }`, ); } diff --git a/ghjk.ts b/ghjk.ts index a7410fb4..21ef5beb 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -13,6 +13,7 @@ import act from "./plugs/act.ts"; import asdf from "./plugs/asdf.ts"; import protoc from "./plugs/protoc.ts"; import earthly from "./plugs/earthly.ts"; +import ruff from "./plugs/ruff.ts"; // node({}); // wasmedge({}); @@ -28,5 +29,6 @@ import earthly from "./plugs/earthly.ts"; // plugRepo: "https://github.com/asdf-community/asdf-zig", // installType: "version", // }); -protoc({}); +// protoc({}); // earthly({}); +ruff({}); diff --git a/plugs/cargo-insta.ts b/plugs/cargo-insta.ts index 5610fec9..31ef056e 100644 --- a/plugs/cargo-insta.ts +++ b/plugs/cargo-insta.ts @@ -77,7 +77,6 @@ export class Plug extends PlugBase { args.tmpDirPath, args.downloadPath, ); - await std_fs.ensureDir(args.downloadPath); } async install(args: InstallArgs) { diff --git a/plugs/ruff.ts b/plugs/ruff.ts new file mode 100644 index 00000000..32b9d2c6 --- /dev/null +++ b/plugs/ruff.ts @@ -0,0 +1,126 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "ruff@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "astral-sh"; +const repoName = "ruff"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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 spawn([ + depBinShimPath(std_plugs.tar_aa, "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( + 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; + 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/plugs/wasm-opt.ts b/plugs/wasm-opt.ts index e272790d..ac2210ee 100644 --- a/plugs/wasm-opt.ts +++ b/plugs/wasm-opt.ts @@ -77,7 +77,6 @@ export class Plug extends PlugBase { args.tmpDirPath, args.downloadPath, ); - await std_fs.ensureDir(args.downloadPath); } async install(args: InstallArgs) { diff --git a/plugs/wasm-tools.ts b/plugs/wasm-tools.ts index 37d33071..c862886c 100644 --- a/plugs/wasm-tools.ts +++ b/plugs/wasm-tools.ts @@ -77,7 +77,6 @@ export class Plug extends PlugBase { args.tmpDirPath, args.downloadPath, ); - await std_fs.ensureDir(args.downloadPath); } async install(args: InstallArgs) { diff --git a/tests/e2e.ts b/tests/e2e.ts index c216c59e..8d7a784b 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -85,6 +85,15 @@ await dockerTest([ }`, ePoint: `protoc --version`, }, + // 6 megs + { + name: "ruff", + imports: `import plug from "$ghjk/plugs/ruff.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `ruff --version`, + }, // 7 megs { name: "act", From 0a9030723b48a079b0deb020a8050ef1f8f36bda Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 08:38:40 +0000 Subject: [PATCH 24/43] feat(plug): whiz --- ghjk.ts | 4 +- plugs/whiz.ts | 123 ++++++++++++++++++++++++++++++++++++++++++ tests/e2e.ts | 9 ++++ tests/test.Dockerfile | 14 ++--- 4 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 plugs/whiz.ts diff --git a/ghjk.ts b/ghjk.ts index 21ef5beb..48747f7a 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -14,6 +14,7 @@ import asdf from "./plugs/asdf.ts"; import protoc from "./plugs/protoc.ts"; import earthly from "./plugs/earthly.ts"; import ruff from "./plugs/ruff.ts"; +import whiz from "./plugs/whiz.ts"; // node({}); // wasmedge({}); @@ -31,4 +32,5 @@ import ruff from "./plugs/ruff.ts"; // }); // protoc({}); // earthly({}); -ruff({}); +// ruff({}); +whiz({}); diff --git a/plugs/whiz.ts b/plugs/whiz.ts new file mode 100644 index 00000000..6c04736b --- /dev/null +++ b/plugs/whiz.ts @@ -0,0 +1,123 @@ +import { + addInstallGlobal, + depBinShimPath, + DownloadArgs, + downloadFile, + InstallArgs, + type InstallConfigBase, + type PlatformInfo, + PlugBase, + registerDenoPlugGlobal, + removeFile, + spawn, + std_fs, + std_path, + std_url, +} from "../plug.ts"; +import * as std_plugs from "../std.ts"; + +const manifest = { + name: "whiz@ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], +}; + +registerDenoPlugGlobal(manifest, () => new Plug()); + +export default function install(config: InstallConfigBase = {}) { + addInstallGlobal({ + plugName: manifest.name, + ...config, + }); +} + +const repoOwner = "zifeo"; +const repoName = "whiz"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; + +export class Plug extends PlugBase { + 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 spawn([ + depBinShimPath(std_plugs.tar_aa, "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( + 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/tests/e2e.ts b/tests/e2e.ts index 8d7a784b..78236dc3 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -95,6 +95,15 @@ await dockerTest([ ePoint: `ruff --version`, }, // 7 megs + { + name: "whiz", + imports: `import plug from "$ghjk/plugs/whiz.ts"`, + confFn: `async () => { + plug({ }); + }`, + ePoint: `whiz --version`, + }, + // 7 megs { name: "act", imports: `import plug from "$ghjk/plugs/act.ts"`, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 43283ebc..e6c37742 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -8,7 +8,13 @@ RUN set -eux; \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; +# activate ghjk for each bash shell +ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh +# explicitly set the shell var as detection fails otherwise +# because ps program is not present in this image ENV SHELL=/bin/bash +# BASH_ENV behavior is only avail in bash, not sh +SHELL [ "/bin/bash", "-c"] WORKDIR /ghjk @@ -24,12 +30,6 @@ RUN cat > ghjk.ts < Date: Thu, 30 Nov 2023 13:43:15 +0300 Subject: [PATCH 25/43] refactor: native js `unarchive` --- core/asdf.ts | 5 +- deno.lock | 130 ++++++++++++++++++++++++++++++++++++++++ deps/plug.ts | 2 + ghjk.ts | 2 +- plug.ts | 25 +++++++- plugs/act.ts | 14 +---- plugs/cargo-binstall.ts | 33 +++------- plugs/jco.ts | 9 +-- plugs/mold.ts | 19 ++---- plugs/node.ts | 18 +----- plugs/protoc.ts | 14 +---- plugs/ruff.ts | 12 +--- plugs/wasmedge.ts | 14 +---- plugs/whiz.ts | 14 +---- tests/e2e.ts | 2 +- tests/test.Dockerfile | 3 +- 16 files changed, 189 insertions(+), 127 deletions(-) diff --git a/core/asdf.ts b/core/asdf.ts index 23188d3a..865b2eb4 100644 --- a/core/asdf.ts +++ b/core/asdf.ts @@ -9,14 +9,16 @@ import { } from "./types.ts"; import { depBinShimPath, + getInstallId, pathWithDepShims, spawn, spawnOutput, - getInstallId } from "./utils.ts"; // import * as std_plugs 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", }; @@ -29,7 +31,6 @@ export const manifest = { name: "asdf@asdf", version: "0.1.0", moduleSpecifier: import.meta.url, - // deps: [std_plugs.tar_aa, std_plugs.git_aa], deps: [curl_aa_id, git_aa_id], }; diff --git a/deno.lock b/deno.lock index 53c72ab1..cf61d088 100644 --- a/deno.lock +++ b/deno.lock @@ -6,6 +6,104 @@ "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/readers.ts": "679471f3b9929b54393c9cd75b6bd178b4bc6d9aab5c0f1f9538f862cf4746fe", + "https://deno.land/std@0.129.0/io/util.ts": "078da53bba767bec0d45f7da44411f6dbf269e51ef7fcfea5e3714e04681c674", + "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/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "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", @@ -240,6 +338,33 @@ "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/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", @@ -396,6 +521,11 @@ "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/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/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", diff --git a/deps/plug.ts b/deps/plug.ts index d61da08f..c52ebfc1 100644 --- a/deps/plug.ts +++ b/deps/plug.ts @@ -1,3 +1,5 @@ //! This contains dependencies used by plugins export * from "./common.ts"; +export * as compress from "https://deno.land/x/compress@v0.4.5/mod.ts"; +export * as zip from "https://deno.land/x/zip@v1.2.5/mod.ts"; diff --git a/ghjk.ts b/ghjk.ts index 48747f7a..2c5e1096 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -33,4 +33,4 @@ import whiz from "./plugs/whiz.ts"; // protoc({}); // earthly({}); // ruff({}); -whiz({}); +// whiz({}); diff --git a/plug.ts b/plug.ts index 2e2a30a8..069bc732 100644 --- a/plug.ts +++ b/plug.ts @@ -13,7 +13,7 @@ import { registerPlug, validators, } from "./core/mod.ts"; -import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; +import { compress, log, std_fs, std_path, std_url, zip } from "./deps/plug.ts"; import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; import * as asdf from "./core/asdf.ts"; import logger from "./core/logger.ts"; @@ -161,4 +161,27 @@ export async function downloadFile( ); } +/// Uses file extension to determine type +export async function unarchive( + path: string, + dest = "./", + ext = std_path.extname(path), +) { + switch (ext) { + case ".gz": + case ".tar.gz": + case ".tgz": + await compress.tgz.uncompress(path, dest); + break; + case ".tar": + await compress.tar.uncompress(path, dest); + break; + case ".zip": + await zip.decompress(path, dest); + break; + default: + throw Error("unsupported archive extension: ${ext}"); + } +} + export const removeFile = Deno.remove; diff --git a/plugs/act.ts b/plugs/act.ts index becc53cf..e1308d09 100644 --- a/plugs/act.ts +++ b/plugs/act.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,20 +8,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "act@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.tar_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -80,12 +75,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts index aa56322b..81b58eef 100644 --- a/plugs/cargo-binstall.ts +++ b/plugs/cargo-binstall.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,24 +8,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -// FIXME: find a better way to expose std_plug.plug_Id -// that allows standard plugs to depend on each other -// import * as std_plugs from "../std.ts"; - -const tar_aa_id = { - id: "tar@aa", -}; export const manifest = { name: "cargo-binstall@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [tar_aa_id], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -38,15 +29,13 @@ export default function install(config: InstallConfigBase = {}) { }); } -const repoAddress = "https://github.com/cargo-bins/cargo-binstall"; +const repoOwner = "cargo-bins"; +const repoName = "cargo-binstall"; +const repoAddress = `https://github.com/${repoOwner}/${repoName}`; export class Plug extends PlugBase { manifest = manifest; - listBinPaths(): string[] { - return ["cargo-binstall", "detect-targets", "detect-wasi"]; - } - async listAll() { const metadataRequest = await fetch( `https://index.crates.io/ca/rg/cargo-binstall`, @@ -73,20 +62,14 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(tar_aa_id, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + 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, + std_path.resolve(args.installPath, "bin"), ); } } @@ -105,12 +88,12 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { } if (platform.os == "darwin") { // NOTE: the archive file name extensions are different from os to os - return `${repoAddress}/releases/download/v${installVersion}/cargo-binstall-${arch}-apple-darwin.full.zip`; + 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 plugs const os = "unknown-linux-musl"; - return `${repoAddress}/releases/download/v${installVersion}/cargo-binstall-${arch}-${os}.full.tgz`; + return `${repoAddress}/releases/download/v${installVersion}/${repoName}-${arch}-${os}.full.tgz`; } else { throw new Error(`unsupported os: ${platform.os}`); } diff --git a/plugs/jco.ts b/plugs/jco.ts index 57d79fde..b0234408 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -15,6 +15,7 @@ import { std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; import node from "./node.ts"; import * as std_plugs from "../std.ts"; @@ -24,7 +25,6 @@ const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.tar_aa, std_plugs.node_org, ], }; @@ -73,12 +73,7 @@ class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/mold.ts b/plugs/mold.ts index 525d264e..1e7ec983 100644 --- a/plugs/mold.ts +++ b/plugs/mold.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,20 +8,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "mold@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.tar_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -75,12 +70,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath args.tmpDirPath) if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); @@ -118,9 +108,8 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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`; + 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/plugs/node.ts b/plugs/node.ts index d5c51fa4..43ec5310 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, ExecEnvArgs, @@ -11,25 +10,17 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -// import * as std_plugs from "../std.ts"; - -const tar_aa_id = { - id: "tar@aa", -}; // TODO: sanity check exports of all plugs export const manifest = { name: "node@org", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - tar_aa_id, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -77,12 +68,7 @@ export class Plug extends PlugBase { 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}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/protoc.ts b/plugs/protoc.ts index 238c9950..726bd8be 100644 --- a/plugs/protoc.ts +++ b/plugs/protoc.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,20 +8,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "protoc@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.unzip_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -75,12 +70,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.unzip_aa, "unzip", args.depShims), - fileDwnPath, - "-d", - args.tmpDirPath, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/ruff.ts b/plugs/ruff.ts index 32b9d2c6..874e592c 100644 --- a/plugs/ruff.ts +++ b/plugs/ruff.ts @@ -13,16 +13,13 @@ import { std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "ruff@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.tar_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -75,12 +72,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 778f4de8..97acffaf 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, ExecEnvArgs, @@ -10,20 +9,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "wasmedge@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.tar_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -110,12 +105,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/plugs/whiz.ts b/plugs/whiz.ts index 6c04736b..2ea892f8 100644 --- a/plugs/whiz.ts +++ b/plugs/whiz.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,20 +8,16 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, + unarchive, } from "../plug.ts"; -import * as std_plugs from "../std.ts"; const manifest = { name: "whiz@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ - std_plugs.tar_aa, - ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -75,12 +70,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), - "xf", - fileDwnPath, - `--directory=${args.tmpDirPath}`, - ]); + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/tests/e2e.ts b/tests/e2e.ts index 78236dc3..3f284bf4 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -193,7 +193,7 @@ await dockerTest([ }`, ePoint: `pnpm --version`, }, - // pnpm + more megs + // node + more megs { name: "jco", imports: `import plug from "$ghjk/plugs/jco.ts"`, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index e6c37742..b05529c3 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -4,7 +4,8 @@ RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ apt install --yes \ - git curl xz-utils unzip \ + # asdf deps + git curl \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; From f21dafbb6c4dde2f8f46efcdb9f75d09c44d83d5 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:37:49 +0000 Subject: [PATCH 26/43] fix: use zipjs --- deno.lock | 29 +++++++++++++++++++++++++++++ deps/plug.ts | 2 +- ghjk.ts | 2 +- plug.ts | 49 ++++++++++++++++++++++++++++++++++++++++++------- plugs/mold.ts | 7 ++++--- plugs/ruff.ts | 3 +-- 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/deno.lock b/deno.lock index cf61d088..f318d02e 100644 --- a/deno.lock +++ b/deno.lock @@ -526,6 +526,35 @@ "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", diff --git a/deps/plug.ts b/deps/plug.ts index c52ebfc1..59299bed 100644 --- a/deps/plug.ts +++ b/deps/plug.ts @@ -2,4 +2,4 @@ export * from "./common.ts"; export * as compress from "https://deno.land/x/compress@v0.4.5/mod.ts"; -export * as zip from "https://deno.land/x/zip@v1.2.5/mod.ts"; +export * as zipjs from "https://deno.land/x/zipjs@v2.7.31/index.js"; diff --git a/ghjk.ts b/ghjk.ts index 2c5e1096..48747f7a 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -33,4 +33,4 @@ import whiz from "./plugs/whiz.ts"; // protoc({}); // earthly({}); // ruff({}); -// whiz({}); +whiz({}); diff --git a/plug.ts b/plug.ts index 069bc732..783b3f92 100644 --- a/plug.ts +++ b/plug.ts @@ -2,18 +2,23 @@ import { addInstall, type AmbientAccessPlugManifest, type DenoWorkerPlugManifest, - type DepShims, type DownloadArgs, type GhjkConfig, type InstallConfig, type PlugBase, - type PlugDep, registerAmbientPlug, registerDenoPlug, registerPlug, validators, } from "./core/mod.ts"; -import { compress, log, std_fs, std_path, std_url, zip } from "./deps/plug.ts"; +import { + compress, + log, + std_fs, + std_path, + std_url, + zipjs, +} from "./deps/plug.ts"; import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; import * as asdf from "./core/asdf.ts"; import logger from "./core/logger.ts"; @@ -171,14 +176,44 @@ export async function unarchive( case ".gz": case ".tar.gz": case ".tgz": - await compress.tgz.uncompress(path, dest); + await compress.tgz.uncompress(path, dest, { + debug: true, + }); break; case ".tar": - await compress.tar.uncompress(path, dest); + await compress.tar.uncompress(path, dest, { + debug: true, + }); break; - case ".zip": - await zip.decompress(path, dest); + case ".zip": { + 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) => { + if (entry.directory) { + await std_fs.ensureDir(std_path.resolve(dest, entry.filename)); + return; + } + const filePath = std_path.resolve(dest, entry.filename); + 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(); + } break; + } default: throw Error("unsupported archive extension: ${ext}"); } diff --git a/plugs/mold.ts b/plugs/mold.ts index 1e7ec983..4ff66102 100644 --- a/plugs/mold.ts +++ b/plugs/mold.ts @@ -70,7 +70,7 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await unarchive(fileDwnPath args.tmpDirPath) + await unarchive(fileDwnPath, args.tmpDirPath); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); @@ -108,8 +108,9 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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`; + 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/plugs/ruff.ts b/plugs/ruff.ts index 874e592c..40ecb5dc 100644 --- a/plugs/ruff.ts +++ b/plugs/ruff.ts @@ -1,6 +1,5 @@ import { addInstallGlobal, - depBinShimPath, DownloadArgs, downloadFile, InstallArgs, @@ -9,7 +8,6 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, - spawn, std_fs, std_path, std_url, @@ -81,6 +79,7 @@ export class Plug extends PlugBase { args.tmpDirPath, std_path.resolve(args.installPath, "bin"), ); + // await Deno.chmod(std_path.resolve(args.installPath, "bin", "ruff"), 0o700); } } From 0b0fb49eb9b2a775ff45f3c4bab57a030f673e4d Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:53:04 +0300 Subject: [PATCH 27/43] fix: better `unarchive` --- deno.lock | 11 +++++ deps/plug.ts | 6 ++- plug.ts | 135 ++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 117 insertions(+), 35 deletions(-) diff --git a/deno.lock b/deno.lock index f318d02e..9d30e33c 100644 --- a/deno.lock +++ b/deno.lock @@ -30,8 +30,11 @@ "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", @@ -76,7 +79,11 @@ "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", @@ -379,6 +386,10 @@ "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", diff --git a/deps/plug.ts b/deps/plug.ts index 59299bed..d72be1f9 100644 --- a/deps/plug.ts +++ b/deps/plug.ts @@ -1,5 +1,9 @@ //! This contains dependencies used by plugins export * from "./common.ts"; -export * as compress from "https://deno.land/x/compress@v0.4.5/mod.ts"; export * as zipjs from "https://deno.land/x/zipjs@v2.7.31/index.js"; +export * as compress from "https://deno.land/x/compress@v0.4.5/mod.ts"; +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/plug.ts b/plug.ts index 783b3f92..1eef6f50 100644 --- a/plug.ts +++ b/plug.ts @@ -13,9 +13,13 @@ import { } from "./core/mod.ts"; import { compress, + Foras, log, std_fs, + std_io, std_path, + std_streams, + std_tar, std_url, zipjs, } from "./deps/plug.ts"; @@ -176,47 +180,110 @@ export async function unarchive( case ".gz": case ".tar.gz": case ".tgz": - await compress.tgz.uncompress(path, dest, { - debug: true, - }); + await untgz(path, dest); break; case ".tar": - await compress.tar.uncompress(path, dest, { - debug: true, - }); + await untar(path, dest); break; - case ".zip": { - 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) => { - if (entry.directory) { - await std_fs.ensureDir(std_path.resolve(dest, entry.filename)); - return; - } - const filePath = std_path.resolve(dest, entry.filename); - 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(); - } + 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 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); + return; + } + 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(); + } +} + export const removeFile = Deno.remove; From 74d44a6fd48316abd98f9db78e73f65cbde761cd Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:21:02 +0000 Subject: [PATCH 28/43] fix: get tests working --- deps/plug.ts | 1 - ghjk.ts | 4 ++-- plug.ts | 5 +++-- plugs/node.ts | 18 ++++++++++++++++-- plugs/wasmedge.ts | 14 ++++++++++++-- tests/test.Dockerfile | 2 +- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/deps/plug.ts b/deps/plug.ts index d72be1f9..33d62761 100644 --- a/deps/plug.ts +++ b/deps/plug.ts @@ -2,7 +2,6 @@ export * from "./common.ts"; export * as zipjs from "https://deno.land/x/zipjs@v2.7.31/index.js"; -export * as compress from "https://deno.land/x/compress@v0.4.5/mod.ts"; 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"; diff --git a/ghjk.ts b/ghjk.ts index 48747f7a..4987faaa 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -17,7 +17,7 @@ import ruff from "./plugs/ruff.ts"; import whiz from "./plugs/whiz.ts"; // node({}); -// wasmedge({}); +wasmedge({}); // pnpm({}); // cargo_binstall({}); // wasm_tools({}); @@ -33,4 +33,4 @@ import whiz from "./plugs/whiz.ts"; // protoc({}); // earthly({}); // ruff({}); -whiz({}); +// whiz({}); diff --git a/plug.ts b/plug.ts index 1eef6f50..10f3319e 100644 --- a/plug.ts +++ b/plug.ts @@ -12,7 +12,6 @@ import { validators, } from "./core/mod.ts"; import { - compress, Foras, log, std_fs, @@ -171,6 +170,7 @@ export async function downloadFile( } /// Uses file extension to determine type +/// Does not support symlinks export async function unarchive( path: string, dest = "./", @@ -212,6 +212,7 @@ export async function untgz( }, }); const buf = gzDec.finish().copyAndDispose(); + await Deno.writeFile("/tmp/my.tar", buf); await untarReader(new std_io.Buffer(buf), dest); } export async function untar( @@ -240,7 +241,7 @@ export async function untarReader( const filePath = std_path.resolve(dest, entry.fileName); if (entry.type === "directory") { await std_fs.ensureDir(filePath); - return; + continue; } await std_fs.ensureDir(std_path.dirname(filePath)); const file = await Deno.open(filePath, { diff --git a/plugs/node.ts b/plugs/node.ts index 43ec5310..d5c51fa4 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -1,5 +1,6 @@ import { addInstallGlobal, + depBinShimPath, DownloadArgs, downloadFile, ExecEnvArgs, @@ -10,17 +11,25 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - unarchive, } from "../plug.ts"; +// import * as std_plugs from "../std.ts"; + +const tar_aa_id = { + id: "tar@aa", +}; // TODO: sanity check exports of all plugs export const manifest = { name: "node@org", version: "0.1.0", moduleSpecifier: import.meta.url, + deps: [ + tar_aa_id, + ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -68,7 +77,12 @@ export class Plug extends PlugBase { artifactUrl(args.installVersion, args.platform), ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await unarchive(fileDwnPath, args.tmpDirPath); + 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 }); diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 97acffaf..778f4de8 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -1,5 +1,6 @@ import { addInstallGlobal, + depBinShimPath, DownloadArgs, downloadFile, ExecEnvArgs, @@ -9,16 +10,20 @@ import { PlugBase, registerDenoPlugGlobal, removeFile, + spawn, std_fs, std_path, std_url, - unarchive, } from "../plug.ts"; +import * as std_plugs from "../std.ts"; const manifest = { name: "wasmedge@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, + deps: [ + std_plugs.tar_aa, + ], }; registerDenoPlugGlobal(manifest, () => new Plug()); @@ -105,7 +110,12 @@ export class Plug extends PlugBase { ); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); - await unarchive(fileDwnPath, args.tmpDirPath); + await spawn([ + depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + "xf", + fileDwnPath, + `--directory=${args.tmpDirPath}`, + ]); if (await std_fs.exists(args.installPath)) { await removeFile(args.installPath, { recursive: true }); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index b05529c3..d095baa9 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -5,7 +5,7 @@ RUN set -eux; \ apt update; \ apt install --yes \ # asdf deps - git curl \ + git curl xz-utils unzip \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; From 237c0572cdfbe492287a6cbbdd40401f11c95996 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:44:14 +0000 Subject: [PATCH 29/43] fix: address feedback --- .pre-commit-config.yaml | 85 -------------------------- cli/hooks.ts | 68 +++++++++++++++------ core/utils.ts | 23 +------ ghjk.ts | 8 +-- plug.ts | 131 +--------------------------------------- tests/e2e.ts | 28 ++++----- tests/test.Dockerfile | 16 +++-- unarchive.ts | 127 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 206 insertions(+), 280 deletions(-) create mode 100644 unarchive.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f77d8a28..6178832b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,33 +19,12 @@ repos: 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 @@ -73,67 +52,3 @@ repos: - ts - tsx files: ^website/ - - 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: - - ts - - tsx - files: ^website/ diff --git a/cli/hooks.ts b/cli/hooks.ts index 5f8a402b..e6f825ac 100644 --- a/cli/hooks.ts +++ b/cli/hooks.ts @@ -14,6 +14,17 @@ switch (Deno.build.os) { throw new Error(`unsupported os ${Deno.build.os}`); } +const BASH_PREXEC = await ( + async () => { + const resp = await fetch( + "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", + ); + if (!resp.ok) { + throw new Error("error fetching bash-preexec"); + } + return await resp.text(); + } +)(); // null means it should be removed (for cleaning up old versions) const vfs = { // the script executed when users use the ghjk command @@ -27,29 +38,38 @@ console.log = log; mod.ghjk.runCli(Deno.args.slice(1), mod.options); `, - "hooks/bash-preexec.sh": await ( - await fetch( - "https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh", - ) - ).text(), + "hooks/bash-preexec.sh": BASH_PREXEC, + + "hooks/.zshenv": ` +if [ -e ~/.zshenv ]; then . ~/.zshenv; fi +hooksDir=$(dirname -- "$(readlink -f -- "\${(%):-%x}")") +. $hooksDir/hook.sh +`, // the hook run before every prompt draw in bash "hooks/hook.sh": ` __ghjk_clean_up_paths() { PATH=$(echo "$PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - PATH="$\{PATH%:\}" + PATH="\${PATH%:}" LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - LIBRARY_PATH="$\{LIBRARY_PATH%:\}" + LIBRARY_PATH="\${LIBRARY_PATH%:}" ${LD_LIBRARY_ENV}=$(echo "$${LD_LIBRARY_ENV}" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - ${LD_LIBRARY_ENV}="$\{${LD_LIBRARY_ENV}%:\}" + ${LD_LIBRARY_ENV}="\${${LD_LIBRARY_ENV}%:}" C_INCLUDE_PATH=$(echo "$C_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - C_INCLUDE_PATH="$\{C_INCLUDE_PATH%:\}" + C_INCLUDE_PATH="\${C_INCLUDE_PATH%:}" CPLUS_INCLUDE_PATH=$(echo "$CPLUS_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - CPLUS_INCLUDE_PATH="$\{CPLUS_INCLUDE_PATH%:\}" + CPLUS_INCLUDE_PATH="\${CPLUS_INCLUDE_PATH%:}" } +# 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 [[ -v GHJK_CLEANUP ]]; then - eval $GHJK_CLEANUP + if [ -n "\${GHJK_CLEANUP+x}" ]; then + eval "$GHJK_CLEANUP" unset GHJK_CLEANUP fi cur_dir=$PWD @@ -61,16 +81,18 @@ init_ghjk() { PATH="$envDir/shims/bin:$PATH" LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" - ${LD_LIBRARY_ENV}="$envDir/shims/lib:$${LD_LIBRARY_ENV}" + LD_LIBRARY_PATH="$envDir/shims/lib:$LD_LIBRARY_PATH" C_INCLUDE_PATH="$envDir/shims/include:$C_INCLUDE_PATH" CPLUS_INCLUDE_PATH="$envDir/shims/include:$CPLUS_INCLUDE_PATH" - source "$envDir/loader.sh" + . "$envDir/loader.sh" + # FIXME: -ot not valid in POSIX + # shellcheck disable=SC3000-SC4000 if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then - echo -e "\e[38;2;255;69;0m[ghjk] Detected changes, please sync...\e[0m" + echo "\${ansi_yel}[ghjk] Detected changes, please sync...\${ansi_nc}" fi else - echo -e "\e[38;2;255;69;0m[ghjk] Uninstalled runtime found, please sync...\e[0m" + echo "\${ansi_red}[ghjk] Uninstalled runtime found, please sync...\${ansi_nc}" echo "$envDir" fi export ghjk_alias="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" @@ -79,12 +101,12 @@ init_ghjk() { cur_dir="$(dirname "$cur_dir")" done __ghjk_clean_up_paths - export ghjk_alias="echo 'No ghjk.ts config found.'" + export ghjk_alias="echo '\${ansi_red}No ghjk.ts config found.\${ansi_nc}'" } ghjk_alias="echo 'No ghjk.ts config found.'" ghjk () { - eval "$ghjk_alias" $*; + eval "$ghjk_alias" "$*"; } # export function for non-interactive use @@ -93,8 +115,8 @@ export -f init_ghjk export -f __ghjk_clean_up_paths # use precmd to check for ghjk.ts before every prompt draw -hooksDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")") -source "$hooksDir/bash-preexec.sh" +hooksDir=$(dirname -- "$(readlink -f -- "\${BASH_SOURCE}")") +. "$hooksDir/bash-preexec.sh" precmd() { init_ghjk } @@ -236,6 +258,12 @@ export async function install() { /\.local\/share\/ghjk\/hooks\/hook.sh/, "source $HOME/.local/share/ghjk/hooks/hook.sh", ); + } else if (shell === "zsh") { + await filterAddFile( + std_path.resolve(homeDir, ".zshrc"), + /\.local\/share\/ghjk\/hooks\/hook.sh/, + "source $HOME/.local/share/ghjk/hooks/hook.sh", + ); } else { throw new Error(`unsupported shell: ${shell}`); } diff --git a/core/utils.ts b/core/utils.ts index 624362c3..bc99de97 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -28,7 +28,7 @@ export type SpawnOptions = { // pipeErr?: WritableStream; }; -// FIXME: pita function responsible for test flakiness +// FIXME: replace with deidcated ergonomic library export async function spawn( cmd: string[], options: SpawnOptions = {}, @@ -40,8 +40,6 @@ export async function spawn( const child = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd, - // stdout: "piped", - // stderr: "piped", ...(pipeInput ? { stdin: "piped", @@ -50,14 +48,6 @@ export async function spawn( env, }).spawn(); - // keep pipe asynchronous till the command exists - // void child.stdout.pipeTo(options.pipeOut ?? Deno.stdout.writable, { - // preventClose: true, - // }); - // void child.stderr.pipeTo(options.pipeErr ?? Deno.stderr.writable, { - // preventClose: true, - // }); - if (pipeInput) { const writer = child.stdin.getWriter(); await writer.write(new TextEncoder().encode(pipeInput)); @@ -83,20 +73,9 @@ export async function spawnOutput( cwd, stdout: "piped", stderr: "piped", - // ...(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, stdout, stderr } = await child.output(); if (!success) { throw new Error( diff --git a/ghjk.ts b/ghjk.ts index 4987faaa..3773d60b 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -26,10 +26,10 @@ wasmedge({}); // jco({}); // mold({}); // act({}); -// asdf({ -// plugRepo: "https://github.com/asdf-community/asdf-zig", -// installType: "version", -// }); +asdf({ + plugRepo: "https://github.com/asdf-community/asdf-python", + installType: "version", +}); // protoc({}); // earthly({}); // ruff({}); diff --git a/plug.ts b/plug.ts index 10f3319e..e6b38489 100644 --- a/plug.ts +++ b/plug.ts @@ -11,17 +11,7 @@ import { registerPlug, validators, } from "./core/mod.ts"; -import { - Foras, - log, - std_fs, - std_io, - std_path, - std_streams, - std_tar, - std_url, - zipjs, -} from "./deps/plug.ts"; +import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; import * as asdf from "./core/asdf.ts"; import logger from "./core/logger.ts"; @@ -33,6 +23,7 @@ export { default as logger } from "./core/logger.ts"; export { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; export * as asdf from "./core/asdf.ts"; export type * from "./core/mod.ts"; +export * from "./unarchive.ts"; if (isWorker()) { log.setup({ @@ -169,122 +160,4 @@ export async function downloadFile( ); } -/// 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(); - } -} - export const removeFile = Deno.remove; diff --git a/tests/e2e.ts b/tests/e2e.ts index 3f284bf4..655a7a7c 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -50,21 +50,19 @@ await (${confFn.toString()})()`; "-f-", ".", ], { env, pipeInput: dFile }); - await spawn([ - ...dockerCmd, - "run", - "--rm", - ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) - .flat(), - tag, - "bash", - "-c", - ` - source ~/.bashrc - init_ghjk - ${ePoint} - `, - ], { env }); + for (const shell of ["bash", "fish"]) { + await spawn([ + ...dockerCmd, + "run", + "--rm", + ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) + .flat(), + tag, + shell, + "-c", + ePoint, + ], { env }); + } await spawn([ ...dockerCmd, "rmi", diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index d095baa9..79f8c2ab 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -4,16 +4,17 @@ RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ apt install --yes \ + # test deps + fish zsh \ # asdf deps git curl xz-utils unzip \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; -# activate ghjk for each bash shell +# activate ghjk non-interactive shells execs ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh -# explicitly set the shell var as detection fails otherwise -# because ps program is not present in this image -ENV SHELL=/bin/bash +ENV ZDOTDIR=/root/.local/share/ghjk/hooks/ + # BASH_ENV behavior is only avail in bash, not sh SHELL [ "/bin/bash", "-c"] @@ -23,7 +24,12 @@ COPY deno.lock ./ COPY deps/* ./deps/ RUN deno cache deps/* COPY . ./ -RUN deno run -A /ghjk/install.ts + +# 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 WORKDIR /app diff --git a/unarchive.ts b/unarchive.ts new file mode 100644 index 00000000..6eba2f3b --- /dev/null +++ b/unarchive.ts @@ -0,0 +1,127 @@ +import { + Foras, + std_fs, + std_io, + std_path, + std_streams, + std_tar, + zipjs, +} from "./deps/plug.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(); + } +} From 5e197143cf50699da034885d702f2e70db4fb584 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:45:42 +0000 Subject: [PATCH 30/43] fix: remove dead code --- cli/sync.ts | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/cli/sync.ts b/cli/sync.ts index 9ecf940e..895e61b0 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -12,7 +12,7 @@ import { validators, } from "../core/mod.ts"; import { DenoWorkerPlug } from "../core/worker.ts"; -import { AVAIL_CONCURRENCY, dirs, } from "./utils.ts"; +import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; import { AmbientAccessPlug } from "../core/ambient.ts"; import { AsdfPlug } from "../core/asdf.ts"; import { getInstallId } from "../core/utils.ts"; @@ -49,9 +49,9 @@ async function writeLoader(envDir: string, env: Record) { await Deno.writeTextFile( `${envDir}/loader.sh`, `export GHJK_CLEANUP="";\n` + - Object.entries(env).map(([k, v]) => - `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` - ).join("\n"), + Object.entries(env).map(([k, v]) => + `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` + ).join("\n"), ); } @@ -77,20 +77,6 @@ export async function sync(cx: GhjkCtx) { const envDir = envDirFromConfig(config); logger().debug({ envDir }); - /* for (const [name, { ty, manifest }] of cx.plugs) { - if (ty == "denoWorker") { - const plug = new DenoWorkerPlug( - manifest as DenoWorkerPlugManifestX, - ); - const versions = await plug.listAll({}); - console.log(name, { versions }); - } else { - throw new Error( - `unsupported plugin type "${ty}": ${JSON.stringify(manifest)}`, - ); - } - } */ - const installs = buildInstallGraph(cx); const artifacts = new Map(); const pendingInstalls = [...installs.indie]; @@ -200,7 +186,8 @@ export async function sync(cx: GhjkCtx) { const conflict = env[key]; if (conflict) { throw new Error( - `duplicate env var found ${key} from installs ${instId} & ${conflict[1] + `duplicate env var found ${key} from installs ${instId} & ${ + conflict[1] }`, ); } @@ -242,7 +229,8 @@ function buildInstallGraph(cx: GhjkCtx) { cx.allowedDeps.get(inst.plugName); if (!regPlug) { throw new Error( - `unable to find plugin "${inst.plugName}" specified by install ${JSON.stringify(inst) + `unable to find plugin "${inst.plugName}" specified by install ${ + JSON.stringify(inst) }`, ); } From f95a1bccafebff2e3f65c821a1cb5c317a04ca3d Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:42:44 +0300 Subject: [PATCH 31/43] refactor: `install.ts` -> `setup.ts` --- .dockerignore | 1 + README.md | 2 +- cli/hooks.ts | 270 ---------------------------------------- cli/utils.ts | 16 +++ install.ts | 5 - setup.ts | 5 + setup/mod.ts | 131 +++++++++++++++++++ setup/vfs/bash.sh | 79 ++++++++++++ setup/vfs/entrypoint.ts | 7 ++ setup/vfs/fish.fish | 61 +++++++++ setup/vfs/zsh.zsh | 3 + tests/test.Dockerfile | 22 ++-- 12 files changed, 315 insertions(+), 287 deletions(-) delete mode 100644 cli/hooks.ts delete mode 100644 install.ts create mode 100644 setup.ts create mode 100644 setup/mod.ts create mode 100644 setup/vfs/bash.sh create mode 100644 setup/vfs/entrypoint.ts create mode 100644 setup/vfs/fish.fish create mode 100644 setup/vfs/zsh.zsh diff --git a/.dockerignore b/.dockerignore index 28a19e32..1eb1bed0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .git .vscode tests/ +*.md diff --git a/README.md b/README.md index af36c18d..c8d28d52 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ghjk (jk) is a programmable runtime manager. Install the hooks: ```bash -deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/install.ts +deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/setup.ts ``` In your project, create a configuration file `ghjk.ts`: diff --git a/cli/hooks.ts b/cli/hooks.ts deleted file mode 100644 index e6f825ac..00000000 --- a/cli/hooks.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { std_path } from "../deps/cli.ts"; -import { dirs } from "./utils.ts"; -import { spawnOutput } from "../core/utils.ts"; - -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 BASH_PREXEC = await ( - async () => { - const resp = await fetch( - "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", - ); - if (!resp.ok) { - throw new Error("error fetching bash-preexec"); - } - return await resp.text(); - } -)(); -// null means it should be removed (for cleaning up old versions) -const vfs = { - // the script executed when users use the ghjk command - "hooks/entrypoint.ts": ` -const log = console.log; -console.log = (...args) => { - log("[ghjk.ts]", ...args); -}; -const mod = await import(Deno.args[0]); -console.log = log; -mod.ghjk.runCli(Deno.args.slice(1), mod.options); - `, - - "hooks/bash-preexec.sh": BASH_PREXEC, - - "hooks/.zshenv": ` -if [ -e ~/.zshenv ]; then . ~/.zshenv; fi -hooksDir=$(dirname -- "$(readlink -f -- "\${(%):-%x}")") -. $hooksDir/hook.sh -`, - // the hook run before every prompt draw in bash - "hooks/hook.sh": ` -__ghjk_clean_up_paths() { - PATH=$(echo "$PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - PATH="\${PATH%:}" - LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - LIBRARY_PATH="\${LIBRARY_PATH%:}" - ${LD_LIBRARY_ENV}=$(echo "$${LD_LIBRARY_ENV}" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - ${LD_LIBRARY_ENV}="\${${LD_LIBRARY_ENV}%:}" - C_INCLUDE_PATH=$(echo "$C_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - C_INCLUDE_PATH="\${C_INCLUDE_PATH%:}" - CPLUS_INCLUDE_PATH=$(echo "$CPLUS_INCLUDE_PATH" | tr ':' '\\n' | grep -vE "^$HOME/\\.local/share/ghjk/envs" | tr '\\n' ':') - CPLUS_INCLUDE_PATH="\${CPLUS_INCLUDE_PATH%:}" -} - -# 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" - unset GHJK_CLEANUP - fi - cur_dir=$PWD - while [ "$cur_dir" != "/" ]; do - if [ -e "$cur_dir/ghjk.ts" ]; then - envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" - if [ -d "$envDir" ]; then - __ghjk_clean_up_paths - - PATH="$envDir/shims/bin:$PATH" - LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" - LD_LIBRARY_PATH="$envDir/shims/lib:$LD_LIBRARY_PATH" - C_INCLUDE_PATH="$envDir/shims/include:$C_INCLUDE_PATH" - CPLUS_INCLUDE_PATH="$envDir/shims/include:$CPLUS_INCLUDE_PATH" - - . "$envDir/loader.sh" - # FIXME: -ot not valid in POSIX - # shellcheck disable=SC3000-SC4000 - if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then - echo "\${ansi_yel}[ghjk] Detected changes, please sync...\${ansi_nc}" - fi - else - echo "\${ansi_red}[ghjk] Uninstalled runtime found, please sync...\${ansi_nc}" - echo "$envDir" - fi - export ghjk_alias="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" - return - fi - cur_dir="$(dirname "$cur_dir")" - done - __ghjk_clean_up_paths - export ghjk_alias="echo '\${ansi_red}No ghjk.ts config found.\${ansi_nc}'" -} - -ghjk_alias="echo 'No ghjk.ts config found.'" -ghjk () { - eval "$ghjk_alias" "$*"; -} - -# export function for non-interactive use -export -f ghjk -export -f init_ghjk -export -f __ghjk_clean_up_paths - -# use precmd to check for ghjk.ts before every prompt draw -hooksDir=$(dirname -- "$(readlink -f -- "\${BASH_SOURCE}")") -. "$hooksDir/bash-preexec.sh" -precmd() { - init_ghjk -} - -# try loading any relevant ghjk.ts right away -init_ghjk -`, - - // the hook run before every prompt draw in fish - "hooks/hook.fish": ` -function clean_up_paths - set --global --path PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) - set --global --path LIBRARY_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) - set --global --path ${LD_LIBRARY_ENV} (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $${LD_LIBRARY_ENV}) - set --global --path C_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) - set --global --path CPLUS_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) -end - -function ghjk_hook --on-variable PWD - if set --query GHJK_CLEANUP - eval $GHJK_CLEANUP - set --erase GHJK_CLEANUP - end - 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 - clean_up_paths - - set --global --prepend PATH $envDir/shims/bin - set --global --prepend LIBRARY_PATH $envDir/shims/lib - set --global --prepend ${LD_LIBRARY_ENV} $envDir/shims/lib - set --global --prepend C_INCLUDE_PATH $envDir/shims/include - set --global --prepend CPLUS_INCLUDE_PATH $envDir/shims/include - - 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 - alias ghjk "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" - return - end - set cur_dir (dirname $cur_dir) - end - clean_up_paths - alias ghjk "echo 'No ghjk.ts config found.'" -end -ghjk_hook -`, -}; - -async function detectShell(): Promise { - let path; - - try { - path = await spawnOutput([ - "ps", - "-p", - String(Deno.ppid), - "-o", - "comm=", - ]); - } catch (err) { - const envShell = Deno.env.get("SHELL"); - if (!envShell) { - throw new Error(`cannot get parent process name: ${err}`); - } - path = envShell; - } - return std_path.basename(path, ".exe").toLowerCase().trim(); -} - -async function unpackVFS(baseDir: string): Promise { - await Deno.mkdir(baseDir, { recursive: true }); - - for (const [subpath, content] of Object.entries(vfs)) { - 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() { - const { homeDir, shareDir } = dirs(); - await unpackVFS(shareDir); - const shell = await detectShell(); - if (shell === "fish") { - await filterAddFile( - std_path.resolve(homeDir, ".config/fish/config.fish"), - /\.local\/share\/ghjk\/hooks\/hook.fish/, - "source $HOME/.local/share/ghjk/hooks/hook.fish", - ); - } else if (shell === "bash") { - await filterAddFile( - std_path.resolve(homeDir, ".bashrc"), - /\.local\/share\/ghjk\/hooks\/hook.sh/, - "source $HOME/.local/share/ghjk/hooks/hook.sh", - ); - } else if (shell === "zsh") { - await filterAddFile( - std_path.resolve(homeDir, ".zshrc"), - /\.local\/share\/ghjk\/hooks\/hook.sh/, - "source $HOME/.local/share/ghjk/hooks/hook.sh", - ); - } else { - throw new Error(`unsupported shell: ${shell}`); - } -} diff --git a/cli/utils.ts b/cli/utils.ts index ad62a57b..6edf5547 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -25,3 +25,19 @@ export const AVAIL_CONCURRENCY = Number.parseInt( 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/install.ts b/install.ts deleted file mode 100644 index 2bf06e97..00000000 --- a/install.ts +++ /dev/null @@ -1,5 +0,0 @@ -// this is only a shortcut for the first install - -import { install } from "./cli/hooks.ts"; - -await install(); diff --git a/setup.ts b/setup.ts new file mode 100644 index 00000000..b376f04d --- /dev/null +++ b/setup.ts @@ -0,0 +1,5 @@ +//! Setup ghjk for the CWD + +import { setup } from "./setup/mod.ts"; + +await setup(); diff --git a/setup/mod.ts b/setup/mod.ts new file mode 100644 index 00000000..d0393d6c --- /dev/null +++ b/setup/mod.ts @@ -0,0 +1,131 @@ +import { std_path } from "../deps/cli.ts"; +import { dirs, importRaw } from "../cli/utils.ts"; +import { spawnOutput } from "../core/utils.ts"; + +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}`); +} + +// null means it should be removed (for cleaning up old versions) +const vfs = { + // the script executed when users use the ghjk command + "hooks/entrypoint.ts": ( + await importRaw(import.meta.resolve("./vfs/entrypoint.ts")) + ), + + "hooks/bash-preexec.sh": await importRaw( + "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", + ), + + "hooks/.zshenv": ( + await importRaw(import.meta.resolve("./vfs/zsh.zsh")) + ) + .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), + // the hook run before every prompt draw in bash + "hooks/hook.sh": ( + await importRaw(import.meta.resolve("./vfs/bash.sh")) + ) + .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), + + "hooks/hook.fish": ( + await importRaw(import.meta.resolve("./vfs/fish.fish")) + ) + .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), +}; + +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 }); + + for (const [subpath, content] of Object.entries(vfs)) { + 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 setup() { + const { homeDir, shareDir } = dirs(); + await unpackVFS(shareDir); + const shell = await detectShell(); + if (shell === "fish") { + await filterAddFile( + std_path.resolve(homeDir, ".config/fish/config.fish"), + /\.local\/share\/ghjk\/hooks\/hook.fish/, + ". $HOME/.local/share/ghjk/hooks/hook.fish", + ); + } else if (shell === "bash") { + await filterAddFile( + std_path.resolve(homeDir, ".bashrc"), + /\.local\/share\/ghjk\/hooks\/hook.sh/, + ". $HOME/.local/share/ghjk/hooks/hook.sh", + ); + } else if (shell === "zsh") { + await filterAddFile( + std_path.resolve(homeDir, ".zshrc"), + /\.local\/share\/ghjk\/hooks\/hook.sh/, + ". $HOME/.local/share/ghjk/hooks/.zshenv", + ); + } else { + throw new Error(`unsupported shell: ${shell}`); + } +} diff --git a/setup/vfs/bash.sh b/setup/vfs/bash.sh new file mode 100644 index 00000000..f8cbfdc5 --- /dev/null +++ b/setup/vfs/bash.sh @@ -0,0 +1,79 @@ + +__ghjk_clean_up_paths() { + PATH=$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + PATH="${PATH%:}" + LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + LIBRARY_PATH="${LIBRARY_PATH%:}" + __var_LD_LIBRARY_ENV__=$(echo "$__var_LD_LIBRARY_ENV__" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + __var_LD_LIBRARY_ENV__="${__var_LD_LIBRARY_ENV__%:}" + C_INCLUDE_PATH=$(echo "$C_INCLUDE_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + C_INCLUDE_PATH="${C_INCLUDE_PATH%:}" + CPLUS_INCLUDE_PATH=$(echo "$CPLUS_INCLUDE_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') + CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH%:}" +} + +# 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" + unset GHJK_CLEANUP + fi + cur_dir=$PWD + while [ "$cur_dir" != "/" ]; do + if [ -e "$cur_dir/ghjk.ts" ]; then + envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" + if [ -d "$envDir" ]; then + __ghjk_clean_up_paths + + PATH="$envDir/shims/bin:$PATH" + LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" + LD_LIBRARY_PATH="$envDir/shims/lib:$LD_LIBRARY_PATH" + C_INCLUDE_PATH="$envDir/shims/include:$C_INCLUDE_PATH" + CPLUS_INCLUDE_PATH="$envDir/shims/include:$CPLUS_INCLUDE_PATH" + + . "$envDir/loader.sh" + # FIXME: -ot not valid in POSIX + # shellcheck disable=SC3000-SC4000 + if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then + echo "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}" + fi + else + echo "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}" + echo "$envDir" + fi + export ghjk_alias="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + return + fi + cur_dir="$(dirname "$cur_dir")" + done + __ghjk_clean_up_paths + export ghjk_alias="echo '${ansi_red}No ghjk.ts config found.${ansi_nc}'" +} + +# the alias value could be changed by init_ghjk +# to execute the appropriate cmd based on ghjk.ts +ghjk_alias="echo 'No ghjk.ts config found.'" +ghjk () { + eval "$ghjk_alias" "$*"; +} + +# export function for non-interactive use +export -f ghjk +export -f init_ghjk +export -f __ghjk_clean_up_paths + +# use precmd to check for ghjk.ts before every prompt draw +hooksDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") +. "$hooksDir/bash-preexec.sh" +precmd() { + init_ghjk +} + +# try loading any relevant ghjk.ts right away +init_ghjk diff --git a/setup/vfs/entrypoint.ts b/setup/vfs/entrypoint.ts new file mode 100644 index 00000000..fdc16d5d --- /dev/null +++ b/setup/vfs/entrypoint.ts @@ -0,0 +1,7 @@ +const log = console.log; +console.log = (...args) => { + log("[ghjk.ts]", ...args); +}; +const mod = await import(Deno.args[0]); +console.log = log; +mod.ghjk.runCli(Deno.args.slice(1), mod.options); diff --git a/setup/vfs/fish.fish b/setup/vfs/fish.fish new file mode 100644 index 00000000..281e402b --- /dev/null +++ b/setup/vfs/fish.fish @@ -0,0 +1,61 @@ +function clean_up_paths + set --global --path PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) + set --global --path LIBRARY_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) + set --global --path __var_LD_LIBRARY_ENV__ (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $__var_LD_LIBRARY_ENV__) + set --global --path C_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) + set --global --path CPLUS_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) +end + +function init_ghjk + if set --query GHJK_CLEANUP + eval $GHJK_CLEANUP + set --erase GHJK_CLEANUP + end + 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 + clean_up_paths + + set --global --prepend PATH $envDir/shims/bin + set --global --prepend LIBRARY_PATH $envDir/shims/lib + set --global --prepend __var_LD_LIBRARY_ENV__ $envDir/shims/lib + set --global --prepend C_INCLUDE_PATH $envDir/shims/include + set --global --prepend CPLUS_INCLUDE_PATH $envDir/shims/include + + 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 + set ghjk_alias "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + return + end + set cur_dir (dirname $cur_dir) + end + clean_up_paths + set ghjk_alias "echo 'No ghjk.ts config found.'" +end + +# the alias value could be changed by init_ghjk +# to execute the appropriate cmd based on ghjk.ts +set ghjk_alias "echo 'No ghjk.ts config found.'" +function ghjk + eval $ghjk_alias $argv +end + +# try to detect ghjk.ts on each change of PWD +function ghjk_hook --on-variable PWD + init_ghjk +end + +# try loading any relevant ghjk.ts right away +init_ghjk diff --git a/setup/vfs/zsh.zsh b/setup/vfs/zsh.zsh new file mode 100644 index 00000000..df45a327 --- /dev/null +++ b/setup/vfs/zsh.zsh @@ -0,0 +1,3 @@ +if [ -e ~/.zshenv ]; then . ~/.zshenv; fi +hooksDir=$(dirname -- "$(readlink -f -- "\${(%):-%x}")") +. $hooksDir/hook.sh diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 79f8c2ab..e395a904 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -11,13 +11,6 @@ RUN set -eux; \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; -# activate ghjk non-interactive shells execs -ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh -ENV ZDOTDIR=/root/.local/share/ghjk/hooks/ - -# BASH_ENV behavior is only avail in bash, not sh -SHELL [ "/bin/bash", "-c"] - WORKDIR /ghjk COPY deno.lock ./ @@ -25,13 +18,20 @@ COPY deps/* ./deps/ RUN deno cache deps/* COPY . ./ +WORKDIR /app + # 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 SHELL=/bin/bash deno run -A /ghjk/setup.ts +RUN SHELL=/bin/fish deno run -A /ghjk/setup.ts +RUN SHELL=/bin/zsh deno run -A /ghjk/setup.ts -WORKDIR /app +# activate ghjk non-interactive shells execs +ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh +ENV ZDOTDIR=/root/.local/share/ghjk/hooks/ + +# BASH_ENV behavior is only avail in bash, not sh +SHELL [ "/bin/bash", "-c"] RUN cat > ghjk.ts < Date: Fri, 1 Dec 2023 19:22:09 +0300 Subject: [PATCH 32/43] refactor: `setup.ts` -> `init.ts` --- README.md | 2 +- core/logger.ts | 60 ++++++++++++++- core/utils.ts | 7 +- deno.lock | 97 +++++++++++++++++++++++++ deps/cli.ts | 2 - deps/common.ts | 4 +- setup.ts => init.ts | 2 +- init/entrypoint.ts | 22 ++++++ {setup/vfs => init/hooks}/bash.sh | 0 {setup/vfs => init/hooks}/fish.fish | 0 {setup/vfs => init/hooks}/zsh.zsh | 0 setup/vfs/entrypoint.ts => init/init.ts | 2 + {setup => init}/mod.ts | 8 +- mod.ts | 1 + plug.ts | 18 +---- setup_globals.ts | 32 -------- setup_logger.ts | 19 +++++ tests/e2e.ts | 2 +- tests/setup_globals.ts | 30 -------- tests/test.Dockerfile | 6 +- 20 files changed, 219 insertions(+), 95 deletions(-) rename setup.ts => init.ts (52%) create mode 100644 init/entrypoint.ts rename {setup/vfs => init/hooks}/bash.sh (100%) rename {setup/vfs => init/hooks}/fish.fish (100%) rename {setup/vfs => init/hooks}/zsh.zsh (100%) rename setup/vfs/entrypoint.ts => init/init.ts (75%) rename {setup => init}/mod.ts (93%) create mode 100644 setup_logger.ts delete mode 100644 tests/setup_globals.ts diff --git a/README.md b/README.md index c8d28d52..caebb109 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ghjk (jk) is a programmable runtime manager. Install the hooks: ```bash -deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/setup.ts +deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/init.ts ``` In your project, create a configuration file `ghjk.ts`: diff --git a/core/logger.ts b/core/logger.ts index 578078fe..01329032 100644 --- a/core/logger.ts +++ b/core/logger.ts @@ -1,5 +1,59 @@ -import { log } from "../deps/common.ts"; +import { log, std_fmt_colors, std_path, std_url } from "../deps/common.ts"; -export default function logger() { - return log.getLogger(self.name ?? "ghjk"); +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 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/core/utils.ts b/core/utils.ts index bc99de97..ca0f970c 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -1,4 +1,4 @@ -import { std_path } from "../deps/common.ts"; +import { dax, std_path } from "../deps/common.ts"; import logger from "./logger.ts"; import type { AsdfInstallConfig, @@ -123,3 +123,8 @@ export function getInstallId(install: InstallConfig | AsdfInstallConfig) { } return install.plugName; } + +export const $ = dax.build$( + {}, +); +$.setPrintCommand(true); diff --git a/deno.lock b/deno.lock index 9d30e33c..c255f3d3 100644 --- a/deno.lock +++ b/deno.lock @@ -117,6 +117,64 @@ "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", @@ -372,6 +430,43 @@ "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", @@ -532,6 +627,8 @@ "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", diff --git a/deps/cli.ts b/deps/cli.ts index 7fe574f7..585f3daa 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -2,8 +2,6 @@ export * from "./common.ts"; -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 * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; export { Command, diff --git a/deps/common.ts b/deps/common.ts index 04cc2acd..8265004f 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -1,9 +1,11 @@ //! dependencies used by all -export * from "./common.ts"; 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"; +export { $ } from "https://deno.land/x/dax@0.35.0/mod.ts"; diff --git a/setup.ts b/init.ts similarity index 52% rename from setup.ts rename to init.ts index b376f04d..a69df91c 100644 --- a/setup.ts +++ b/init.ts @@ -1,5 +1,5 @@ //! Setup ghjk for the CWD -import { setup } from "./setup/mod.ts"; +import { setup } from "./init/mod.ts"; await setup(); diff --git a/init/entrypoint.ts b/init/entrypoint.ts new file mode 100644 index 00000000..427d1ba3 --- /dev/null +++ b/init/entrypoint.ts @@ -0,0 +1,22 @@ +import "../setup_logger.ts"; + +import { std_path } from "../deps/common.ts"; +import logger from "../core/logger.ts"; + +function runDenoConfig(configPath: string) { +} + +const configPath = Deno.args[0]; + +switch (std_path.extname(configPath)) { + case "": + logger().warning("config file has no extension, assuming deno config"); + /* falls through */ + case ".ts": + runDenoConfig(configPath); + break; + default: + throw new Error( + `unrecognized ghjk config type provided at path: ${configPath}`, + ); +} diff --git a/setup/vfs/bash.sh b/init/hooks/bash.sh similarity index 100% rename from setup/vfs/bash.sh rename to init/hooks/bash.sh diff --git a/setup/vfs/fish.fish b/init/hooks/fish.fish similarity index 100% rename from setup/vfs/fish.fish rename to init/hooks/fish.fish diff --git a/setup/vfs/zsh.zsh b/init/hooks/zsh.zsh similarity index 100% rename from setup/vfs/zsh.zsh rename to init/hooks/zsh.zsh diff --git a/setup/vfs/entrypoint.ts b/init/init.ts similarity index 75% rename from setup/vfs/entrypoint.ts rename to init/init.ts index fdc16d5d..7c5237c3 100644 --- a/setup/vfs/entrypoint.ts +++ b/init/init.ts @@ -1,3 +1,5 @@ +//! this loads the ghjk.ts module and provides a program for it + const log = console.log; console.log = (...args) => { log("[ghjk.ts]", ...args); diff --git a/setup/mod.ts b/init/mod.ts similarity index 93% rename from setup/mod.ts rename to init/mod.ts index d0393d6c..6e2ba089 100644 --- a/setup/mod.ts +++ b/init/mod.ts @@ -18,7 +18,7 @@ switch (Deno.build.os) { const vfs = { // the script executed when users use the ghjk command "hooks/entrypoint.ts": ( - await importRaw(import.meta.resolve("./vfs/entrypoint.ts")) + await importRaw(import.meta.resolve("./entrypoint.ts")) ), "hooks/bash-preexec.sh": await importRaw( @@ -26,17 +26,17 @@ const vfs = { ), "hooks/.zshenv": ( - await importRaw(import.meta.resolve("./vfs/zsh.zsh")) + await importRaw(import.meta.resolve("./hooks/zsh.zsh")) ) .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), // the hook run before every prompt draw in bash "hooks/hook.sh": ( - await importRaw(import.meta.resolve("./vfs/bash.sh")) + await importRaw(import.meta.resolve("./hooks/bash.sh")) ) .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), "hooks/hook.fish": ( - await importRaw(import.meta.resolve("./vfs/fish.fish")) + await importRaw(import.meta.resolve("./hooks/fish.fish")) ) .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), }; diff --git a/mod.ts b/mod.ts index 68e573cd..733f9f78 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,7 @@ //! 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 "./setup_globals.ts"; import { type GhjkConfig } from "./core/mod.ts"; diff --git a/plug.ts b/plug.ts index e6b38489..5e6f86e1 100644 --- a/plug.ts +++ b/plug.ts @@ -14,7 +14,7 @@ import { import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; import * as asdf from "./core/asdf.ts"; -import logger from "./core/logger.ts"; +import logger, { ConsoleErrHandler } from "./core/logger.ts"; export * from "./core/mod.ts"; export * from "./core/utils.ts"; @@ -28,21 +28,7 @@ export * from "./unarchive.ts"; if (isWorker()) { log.setup({ handlers: { - console: new log.handlers.ConsoleHandler("NOTSET", { - formatter: (lr) => { - let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; - - lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; - }); - // if (lr.args.length > 0) { - // msg += JSON.stringify(lr.args); - // } - - return msg; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), + console: new ConsoleErrHandler("NOTSET"), }, loggers: { diff --git a/setup_globals.ts b/setup_globals.ts index cc250b4d..751a79e9 100644 --- a/setup_globals.ts +++ b/setup_globals.ts @@ -1,4 +1,3 @@ -import { log } from "./deps/common.ts"; import type { GhjkConfig } from "./core/mod.ts"; declare global { @@ -11,34 +10,3 @@ self.ghjk = { plugs: new Map(), installs: [], }; - -log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("NOTSET", { - formatter: (lr) => { - 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; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), - }, - - loggers: { - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - }, -}); diff --git a/setup_logger.ts b/setup_logger.ts new file mode 100644 index 00000000..5a931608 --- /dev/null +++ b/setup_logger.ts @@ -0,0 +1,19 @@ +import { log } from "./deps/common.ts"; +import { ConsoleErrHandler } from "./core/logger.ts"; + +log.setup({ + handlers: { + console: new ConsoleErrHandler("NOTSET"), + }, + + loggers: { + default: { + level: "DEBUG", + handlers: ["console"], + }, + ghjk: { + level: "DEBUG", + handlers: ["console"], + }, + }, +}); diff --git a/tests/e2e.ts b/tests/e2e.ts index 655a7a7c..02873c3c 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,4 +1,4 @@ -import "./setup_globals.ts"; +import "../setup_logger.ts"; import { spawn } from "../core/utils.ts"; type TestCase = { diff --git a/tests/setup_globals.ts b/tests/setup_globals.ts deleted file mode 100644 index ed43aa8d..00000000 --- a/tests/setup_globals.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { log } from "../deps/dev.ts"; - -log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("DEBUG", { - formatter: (lr) => { - let msg = `[${lr.levelName} ${lr.loggerName}] ${lr.msg}`; - - lr.args.forEach((arg, _index) => { - msg += `, ${JSON.stringify(arg)}`; - }); - - return msg; - }, - // formatter: "[{loggerName}] - {levelName} {msg}", - }), - }, - - loggers: { - // configure default logger available via short-hand methods above. - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - }, -}); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index e395a904..ff81e07c 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -22,9 +22,9 @@ WORKDIR /app # 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/setup.ts -RUN SHELL=/bin/fish deno run -A /ghjk/setup.ts -RUN SHELL=/bin/zsh deno run -A /ghjk/setup.ts +RUN SHELL=/bin/bash deno run -A /ghjk/init.ts +RUN SHELL=/bin/fish deno run -A /ghjk/init.ts +RUN SHELL=/bin/zsh deno run -A /ghjk/init.ts # activate ghjk non-interactive shells execs ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh From f06ec823c4a75f6cf8434f57b11fd24a4526af24 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:54:07 +0300 Subject: [PATCH 33/43] wip: `modules` --- cli/list.ts | 2 +- cli/mod.ts | 2 +- cli/sync.ts | 428 +---------------------------- core/utils.ts | 2 +- core/validators.ts | 76 ----- deps/common.ts | 1 - host/deno.ts | 4 + host/mod.ts | 41 +++ host/types.ts | 14 + init.ts | 5 - init/entrypoint.ts | 22 -- init/init.ts | 9 - install.ts | 5 + {init => install}/hooks/bash.sh | 0 {init => install}/hooks/fish.fish | 0 {init => install}/hooks/zsh.zsh | 0 {init => install}/mod.ts | 10 +- mod.ts | 6 +- modules/mod.ts | 0 {core => modules/ports}/ambient.ts | 2 +- {core => modules/ports}/asdf.ts | 12 +- {core => modules/ports}/mod.ts | 19 +- std.ts => modules/ports/std.ts | 17 +- modules/ports/sync.ts | 423 ++++++++++++++++++++++++++++ {core => modules/ports}/types.ts | 89 +++++- {core => modules/ports}/worker.ts | 2 +- modules/std.ts | 1 + modules/tasks/mod.ts | 0 modules/types.ts | 11 + plugs/act.ts | 2 +- plugs/asdf.ts | 2 +- plugs/cargo-binstall.ts | 2 +- plugs/cargo-insta.ts | 4 +- plugs/curl.ts | 2 +- plugs/earthly.ts | 2 +- plugs/git.ts | 2 +- plugs/jco.ts | 4 +- plugs/mold.ts | 2 +- plugs/node.ts | 2 +- plugs/pnpm.ts | 2 +- plugs/protoc.ts | 2 +- plugs/ruff.ts | 2 +- plugs/tar.ts | 2 +- plugs/unzip.ts | 2 +- plugs/wasm-opt.ts | 4 +- plugs/wasm-tools.ts | 4 +- plugs/wasmedge.ts | 4 +- plugs/whiz.ts | 2 +- plug.ts => port.ts | 18 +- setup_globals.ts | 2 +- tests/ambient.ts | 6 +- 51 files changed, 662 insertions(+), 615 deletions(-) delete mode 100644 core/validators.ts create mode 100644 host/deno.ts create mode 100644 host/mod.ts create mode 100644 host/types.ts delete mode 100644 init.ts delete mode 100644 init/entrypoint.ts delete mode 100644 init/init.ts create mode 100644 install.ts rename {init => install}/hooks/bash.sh (100%) rename {init => install}/hooks/fish.fish (100%) rename {init => install}/hooks/zsh.zsh (100%) rename {init => install}/mod.ts (95%) create mode 100644 modules/mod.ts rename {core => modules/ports}/ambient.ts (97%) rename {core => modules/ports}/asdf.ts (96%) rename {core => modules/ports}/mod.ts (89%) rename std.ts => modules/ports/std.ts (69%) create mode 100644 modules/ports/sync.ts rename {core => modules/ports}/types.ts (72%) rename {core => modules/ports}/worker.ts (99%) create mode 100644 modules/std.ts create mode 100644 modules/tasks/mod.ts create mode 100644 modules/types.ts rename plug.ts => port.ts (87%) diff --git a/cli/list.ts b/cli/list.ts index ce23a543..34314235 100644 --- a/cli/list.ts +++ b/cli/list.ts @@ -1,5 +1,5 @@ import { Command } from "../deps/cli.ts"; -import { type GhjkCtx } from "../core/mod.ts"; +import { GhjkCtx } from "../modules/ports/mod.ts"; export class ListCommand extends Command { constructor( diff --git a/cli/mod.ts b/cli/mod.ts index 273547d7..76523807 100644 --- a/cli/mod.ts +++ b/cli/mod.ts @@ -4,7 +4,7 @@ import { SyncCommand } from "./sync.ts"; import { ListCommand } from "./list.ts"; import { OutdatedCommand } from "./outdated.ts"; import { CleanupCommand } from "./cleanup.ts"; -import { type GhjkCtx } from "../core/mod.ts"; +import { GhjkCtx } from "../modules/ports/mod.ts"; export function runCli( args: string[], diff --git a/cli/sync.ts b/cli/sync.ts index 895e61b0..721cae36 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1,60 +1,6 @@ -import { Command, std_fs, std_path, zod } from "../deps/cli.ts"; -import logger from "../core/logger.ts"; -import { - type AmbientAccessPlugManifestX, - type DenoWorkerPlugManifestX, - type DepShims, - type GhjkCtx, - type InstallConfig, - InstallConfigX, - type PlugArgsBase, - type RegisteredPlug, - validators, -} from "../core/mod.ts"; -import { DenoWorkerPlug } from "../core/worker.ts"; -import { AVAIL_CONCURRENCY, dirs } from "./utils.ts"; -import { AmbientAccessPlug } from "../core/ambient.ts"; -import { AsdfPlug } from "../core/asdf.ts"; -import { getInstallId } from "../core/utils.ts"; - -async function findConfig(path: string): Promise { - 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; -} - -function envDirFromConfig(config: string): string { - const { shareDir } = dirs(); - return std_path.resolve( - shareDir, - "envs", - std_path.dirname(config).replaceAll("/", "."), - ); -} - -async function writeLoader(envDir: string, env: Record) { - await Deno.mkdir(envDir, { recursive: true }); - await Deno.writeTextFile( - `${envDir}/loader.fish`, - Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP "set --global --export ${k} '$${k}';";\nset --global --export ${k} '${v}';` - ).join("\n"), - ); - await Deno.writeTextFile( - `${envDir}/loader.sh`, - `export GHJK_CLEANUP="";\n` + - Object.entries(env).map(([k, v]) => - `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` - ).join("\n"), - ); -} - +import { Command } from "../deps/cli.ts"; +import { GhjkCtx } from "../modules/ports/mod.ts"; +import { sync } from "../modules/ports/sync.ts"; export class SyncCommand extends Command { constructor( public cx: GhjkCtx, @@ -65,371 +11,3 @@ export class SyncCommand extends Command { .action(() => sync(cx)); } } - -export async function sync(cx: GhjkCtx) { - const config = await findConfig(Deno.cwd()); - if (!config) { - logger().error("ghjk did not find any `ghjk.ts` config."); - return; - } - logger().debug("syncnig", config); - - const envDir = envDirFromConfig(config); - logger().debug({ envDir }); - - 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 regPlug = cx.plugs.get(inst.plugName) ?? - cx.allowedDeps.get(inst.plugName)!; - const { manifest } = regPlug; - 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 depPlug = cx.allowedDeps.get(depId.id)!; - const depInstall = { - plugName: depPlug.manifest.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, regPlug, 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 installs ${instId} & ${ - conflict[1] - }`, - ); - } - env[key] = [val, instId]; - } - } - // FIXME: prevent malicious env manipulations - await writeLoader( - envDir, - Object.fromEntries( - Object.entries(env).map(([key, [val, _]]) => [key, val]), - ), - ); -} -function buildInstallGraph(cx: GhjkCtx) { - 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.plugName}`); - } - installs.user.add(instId); - foundInstalls.push(inst); - } - - while (foundInstalls.length > 0) { - const inst = foundInstalls.pop()!; - const regPlug = cx.plugs.get(inst.plugName) ?? - cx.allowedDeps.get(inst.plugName); - if (!regPlug) { - throw new Error( - `unable to find plugin "${inst.plugName}" 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); - - const { manifest } = regPlug; - if (!manifest.deps || manifest.deps.length == 0) { - installs.indie.push(installId); - } else { - const deps = []; - for (const depId of manifest.deps) { - const depPlug = cx.allowedDeps.get(depId.id); - if (!depPlug) { - throw new Error( - `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, - ); - } - const depInstall = { - plugName: depPlug.manifest.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, - regPlug: RegisteredPlug, - depShims: DepShims, -) { - const { ty: plugType, manifest } = regPlug; - let plug; - let inst: InstallConfigX; - if (plugType == "denoWorker") { - inst = validators.installConfig.parse(instUnclean); - plug = new DenoWorkerPlug( - manifest as DenoWorkerPlugManifestX, - ); - } else if (plugType == "ambientAccess") { - inst = validators.installConfig.parse(instUnclean); - plug = new AmbientAccessPlug( - manifest as AmbientAccessPlugManifestX, - ); - } else if (plugType == "asdf") { - const asdfInst = validators.asdfInstallConfig.parse(instUnclean); - inst = asdfInst; - plug = await AsdfPlug.init(envDir, asdfInst, depShims); - } else { - throw new Error( - `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, - ); - } - const installId = getInstallId(inst); - const installVersion = validators.string.parse( - inst.version ?? await plug.latestStable({ - depShims, - }), - ); - const installPath = std_path.resolve( - envDir, - "installs", - installId, - installVersion, - ); - const downloadPath = std_path.resolve( - envDir, - "downloads", - installId, - installVersion, - ); - const baseArgs: PlugArgsBase = { - 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 plug.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 plug.install({ - ...baseArgs, - availConcurrency: AVAIL_CONCURRENCY, - downloadPath: downloadPath, - tmpDirPath, - }); - void Deno.remove(tmpDirPath, { recursive: true }); - } - const binPaths = validators.stringArray.parse( - await plug.listBinPaths({ - ...baseArgs, - }), - ); - const libPaths = validators.stringArray.parse( - await plug.listLibPaths({ - ...baseArgs, - }), - ); - const includePaths = validators.stringArray.parse( - await plug.listIncludePaths({ - ...baseArgs, - }), - ); - const env = zod.record(zod.string()).parse( - await plug.execEnv({ - ...baseArgs, - }), - ); - return { env, binPaths, libPaths, includePaths, installPath, downloadPath }; -} diff --git a/core/utils.ts b/core/utils.ts index ca0f970c..2202a50f 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -5,7 +5,7 @@ import type { DepShims, InstallConfig, PlugDep, -} from "./types.ts"; +} from "../modules/ports/types.ts"; export function dbg(val: T) { logger().debug("inline", val); return val; diff --git a/core/validators.ts b/core/validators.ts deleted file mode 100644 index fdf04364..00000000 --- a/core/validators.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { semver, zod } from "../deps/common.ts"; - -const plugDep = zod.object({ - id: zod.string(), -}); - -const plugManifestBase = zod.object({ - 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(plugDep).nullish(), -}).passthrough(); - -const denoWorkerPlugManifest = plugManifestBase.merge( - zod.object({ - moduleSpecifier: zod.string().url(), - }), -); - -const ambientAccessPlugManifest = plugManifestBase.merge( - zod.object({ - 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 installConfigBase = zod.object({ - version: zod.string() - .nullish(), - conflictResolution: zod - .enum(["deferToNewer", "override"]) - .nullish() - .default("deferToNewer"), -}).passthrough(); - -const installConfig = installConfigBase.merge( - zod.object({ - plugName: zod.string().min(1), - }), -); - -const asdfInstallConfig = installConfig.merge( - zod.object({ - plugRepo: zod.string().url(), - installType: zod - .enum(["version", "ref"]), - }), -); - -export default { - plugDep, - plugManifestBase, - denoWorkerPlugManifest, - ambientAccessPlugManifest, - string: zod.string(), - installConfigBase, - installConfig, - asdfInstallConfig, - stringArray: zod.string().min(1).array(), -}; diff --git a/deps/common.ts b/deps/common.ts index 8265004f..4f438125 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -8,4 +8,3 @@ 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"; -export { $ } from "https://deno.land/x/dax@0.35.0/mod.ts"; diff --git a/host/deno.ts b/host/deno.ts new file mode 100644 index 00000000..886b5e73 --- /dev/null +++ b/host/deno.ts @@ -0,0 +1,4 @@ +//! this loads the ghjk.ts module and provides a program for it + +const mod = await import(Deno.args[0]); +console.log(JSON.stringify(mod)); diff --git a/host/mod.ts b/host/mod.ts new file mode 100644 index 00000000..fd14b7f5 --- /dev/null +++ b/host/mod.ts @@ -0,0 +1,41 @@ +import "../setup_logger.ts"; + +import { std_path } from "../deps/common.ts"; +import logger from "../core/logger.ts"; +import { $ } from "../core/utils.ts"; + +import validators, { type SerializedConfig } from "./types.ts"; +import * as std_modules from "../modules/std.ts"; + +async function getSerializedConfigDeno(configPath: string) { + const denoRunner = import.meta.resolve("./deno.ts"); + return await $`deno run --allow-read=. --allow-env --allow-net ${denoRunner} ${configPath}` + .json(); +} + +export async function main() { + const configPath = Deno.args[0]; + + let serializedJson; + switch (std_path.extname(configPath)) { + case "": + logger().warning("config file has no extension, assuming deno config"); + /* falls through */ + case ".ts": + serializedJson = await getSerializedConfigDeno(configPath); + 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}`, + ); + } + + console.log(serializedJson); + // const serializedConfig = validators.serializedConfig.parse( + // serializedJson, + // ); +} diff --git a/host/types.ts b/host/types.ts new file mode 100644 index 00000000..aa18e608 --- /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.module), + }, +); + +export type SerializedConfig = zod.infer; + +export default { + serializedConfig, +}; diff --git a/init.ts b/init.ts deleted file mode 100644 index a69df91c..00000000 --- a/init.ts +++ /dev/null @@ -1,5 +0,0 @@ -//! Setup ghjk for the CWD - -import { setup } from "./init/mod.ts"; - -await setup(); diff --git a/init/entrypoint.ts b/init/entrypoint.ts deleted file mode 100644 index 427d1ba3..00000000 --- a/init/entrypoint.ts +++ /dev/null @@ -1,22 +0,0 @@ -import "../setup_logger.ts"; - -import { std_path } from "../deps/common.ts"; -import logger from "../core/logger.ts"; - -function runDenoConfig(configPath: string) { -} - -const configPath = Deno.args[0]; - -switch (std_path.extname(configPath)) { - case "": - logger().warning("config file has no extension, assuming deno config"); - /* falls through */ - case ".ts": - runDenoConfig(configPath); - break; - default: - throw new Error( - `unrecognized ghjk config type provided at path: ${configPath}`, - ); -} diff --git a/init/init.ts b/init/init.ts deleted file mode 100644 index 7c5237c3..00000000 --- a/init/init.ts +++ /dev/null @@ -1,9 +0,0 @@ -//! this loads the ghjk.ts module and provides a program for it - -const log = console.log; -console.log = (...args) => { - log("[ghjk.ts]", ...args); -}; -const mod = await import(Deno.args[0]); -console.log = log; -mod.ghjk.runCli(Deno.args.slice(1), mod.options); diff --git a/install.ts b/install.ts new file mode 100644 index 00000000..2eed1000 --- /dev/null +++ b/install.ts @@ -0,0 +1,5 @@ +//! Setup ghjk for the CWD + +import { install } from "./install/mod.ts"; + +await install(); diff --git a/init/hooks/bash.sh b/install/hooks/bash.sh similarity index 100% rename from init/hooks/bash.sh rename to install/hooks/bash.sh diff --git a/init/hooks/fish.fish b/install/hooks/fish.fish similarity index 100% rename from init/hooks/fish.fish rename to install/hooks/fish.fish diff --git a/init/hooks/zsh.zsh b/install/hooks/zsh.zsh similarity index 100% rename from init/hooks/zsh.zsh rename to install/hooks/zsh.zsh diff --git a/init/mod.ts b/install/mod.ts similarity index 95% rename from init/mod.ts rename to install/mod.ts index 6e2ba089..25f6d4e2 100644 --- a/init/mod.ts +++ b/install/mod.ts @@ -17,9 +17,11 @@ switch (Deno.build.os) { // null means it should be removed (for cleaning up old versions) const vfs = { // the script executed when users use the ghjk command - "hooks/entrypoint.ts": ( - await importRaw(import.meta.resolve("./entrypoint.ts")) - ), + "hooks/entrypoint.ts": ` +import { main } from "${import.meta.resolve("../host/mod.ts")}"; + +await main(); +`, "hooks/bash-preexec.sh": await importRaw( "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", @@ -103,7 +105,7 @@ async function filterAddFile( await Deno.writeTextFile(path, lines.join("\n")); } -export async function setup() { +export async function install() { const { homeDir, shareDir } = dirs(); await unpackVFS(shareDir); const shell = await detectShell(); diff --git a/mod.ts b/mod.ts index 733f9f78..a2d3e25f 100644 --- a/mod.ts +++ b/mod.ts @@ -4,12 +4,12 @@ import "./setup_logger.ts"; import "./setup_globals.ts"; -import { type GhjkConfig } from "./core/mod.ts"; +import { type GhjkConfig } from "./modules/ports/types.ts"; // this is only a shortcut for the cli import { runCli } from "./cli/mod.ts"; import logger from "./core/logger.ts"; -import { GhjkSecureConfig } from "./plug.ts"; -import * as std_plugs from "./std.ts"; +import { GhjkSecureConfig } from "./port.ts"; +import * as std_plugs from "./modules/ports/std.ts"; // we need to use global variables to allow // plugins to access the config object. diff --git a/modules/mod.ts b/modules/mod.ts new file mode 100644 index 00000000..e69de29b diff --git a/core/ambient.ts b/modules/ports/ambient.ts similarity index 97% rename from core/ambient.ts rename to modules/ports/ambient.ts index 3c63bb45..0776d6fb 100644 --- a/core/ambient.ts +++ b/modules/ports/ambient.ts @@ -6,7 +6,7 @@ import { type ListBinPathsArgs, PlugBase, } from "./types.ts"; -import { ChildError, spawnOutput } from "./utils.ts"; +import { ChildError, spawnOutput } from "../../core/utils.ts"; export class AmbientAccessPlug extends PlugBase { constructor(public manifest: AmbientAccessPlugManifest) { diff --git a/core/asdf.ts b/modules/ports/asdf.ts similarity index 96% rename from core/asdf.ts rename to modules/ports/asdf.ts index 865b2eb4..4620df0a 100644 --- a/core/asdf.ts +++ b/modules/ports/asdf.ts @@ -1,10 +1,10 @@ import { type AsdfInstallConfigX, type DepShims, - DownloadArgs, - InstallArgs, - ListAllArgs, - ListBinPathsArgs, + type DownloadArgs, + type InstallArgs, + type ListAllArgs, + type ListBinPathsArgs, PlugBase, } from "./types.ts"; import { @@ -13,9 +13,9 @@ import { pathWithDepShims, spawn, spawnOutput, -} from "./utils.ts"; +} from "../../core/utils.ts"; // import * as std_plugs from "../std.ts"; -import { std_fs, std_path } from "../deps/common.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 diff --git a/core/mod.ts b/modules/ports/mod.ts similarity index 89% rename from core/mod.ts rename to modules/ports/mod.ts index 49494343..a7d22ab4 100644 --- a/core/mod.ts +++ b/modules/ports/mod.ts @@ -1,15 +1,14 @@ export * from "./types.ts"; -export { default as validators } from "./validators.ts"; -import { semver } from "../deps/common.ts"; -import type { - AmbientAccessPlugManifest, - DenoWorkerPlugManifest, - GhjkConfig, - InstallConfig, - RegisteredPlug, +import { semver } from "../../deps/common.ts"; + +import validators, { + type AmbientAccessPlugManifest, + type DenoWorkerPlugManifest, + type GhjkConfig, + type InstallConfig, + type RegisteredPlug, } from "./types.ts"; -import validators from "./validators.ts"; -import logger from "./logger.ts"; +import logger from "../../core/logger.ts"; export const Ghjk = { cwd: Deno.cwd, diff --git a/std.ts b/modules/ports/std.ts similarity index 69% rename from std.ts rename to modules/ports/std.ts index 636593dd..d5738490 100644 --- a/std.ts +++ b/modules/ports/std.ts @@ -1,14 +1,13 @@ //! This plugin exports the list of standard plugins other //! plugins are allowed to depend on. -import { PlugDep, RegisteredPlug } from "./core/types.ts"; -import validators from "./core/validators.ts"; -import { manifest as man_tar_aa } from "./plugs/tar.ts"; -import { manifest as man_git_aa } from "./plugs/git.ts"; -import { manifest as man_curl_aa } from "./plugs/curl.ts"; -import { manifest as man_unzip_aa } from "./plugs/unzip.ts"; -import { manifest as man_cbin_ghrel } from "./plugs/cargo-binstall.ts"; -import { manifest as man_node_org } from "./plugs/node.ts"; -import { manifest as man_pnpm_ghrel } from "./plugs/pnpm.ts"; +import validators, { PlugDep, RegisteredPlug } from "./types.ts"; +import { manifest as man_tar_aa } from "../../plugs/tar.ts"; +import { manifest as man_git_aa } from "../../plugs/git.ts"; +import { manifest as man_curl_aa } from "../../plugs/curl.ts"; +import { manifest as man_unzip_aa } from "../../plugs/unzip.ts"; +import { manifest as man_cbin_ghrel } from "../../plugs/cargo-binstall.ts"; +import { manifest as man_node_org } from "../../plugs/node.ts"; +import { manifest as man_pnpm_ghrel } from "../../plugs/pnpm.ts"; const aaPlugs: RegisteredPlug[] = [ man_tar_aa, diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts new file mode 100644 index 00000000..ff681cef --- /dev/null +++ b/modules/ports/sync.ts @@ -0,0 +1,423 @@ +import { std_fs, std_path, zod } from "../../deps/cli.ts"; +import logger from "../../core/logger.ts"; +import validators, { + type AmbientAccessPlugManifestX, + type DenoWorkerPlugManifestX, + type DepShims, + type GhjkCtx, + type InstallConfig, + InstallConfigX, + type PlugArgsBase, + type RegisteredPlug, +} from "./types.ts"; +import { DenoWorkerPlug } from "./worker.ts"; +import { AVAIL_CONCURRENCY, dirs } from "../../cli/utils.ts"; +import { AmbientAccessPlug } from "./ambient.ts"; +import { AsdfPlug } from "./asdf.ts"; +import { getInstallId } from "../../core/utils.ts"; + +async function findConfig(path: string): Promise { + 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; +} + +function envDirFromConfig(config: string): string { + const { shareDir } = dirs(); + return std_path.resolve( + shareDir, + "envs", + std_path.dirname(config).replaceAll("/", "."), + ); +} + +async function writeLoader(envDir: string, env: Record) { + await Deno.mkdir(envDir, { recursive: true }); + await Deno.writeTextFile( + `${envDir}/loader.fish`, + Object.entries(env).map(([k, v]) => + `set --global --append GHJK_CLEANUP "set --global --export ${k} '$${k}';";\nset --global --export ${k} '${v}';` + ).join("\n"), + ); + await Deno.writeTextFile( + `${envDir}/loader.sh`, + `export GHJK_CLEANUP="";\n` + + Object.entries(env).map(([k, v]) => + `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` + ).join("\n"), + ); +} + +export async function sync(cx: GhjkCtx) { + const config = await findConfig(Deno.cwd()); + if (!config) { + logger().error("ghjk did not find any `ghjk.ts` config."); + return; + } + logger().debug("syncnig", config); + + const envDir = envDirFromConfig(config); + logger().debug({ envDir }); + + 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 regPlug = cx.plugs.get(inst.plugName) ?? + cx.allowedDeps.get(inst.plugName)!; + const { manifest } = regPlug; + 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 depPlug = cx.allowedDeps.get(depId.id)!; + const depInstall = { + plugName: depPlug.manifest.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, regPlug, 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 installs ${instId} & ${ + conflict[1] + }`, + ); + } + env[key] = [val, instId]; + } + } + // FIXME: prevent malicious env manipulations + await writeLoader( + envDir, + Object.fromEntries( + Object.entries(env).map(([key, [val, _]]) => [key, val]), + ), + ); +} +function buildInstallGraph(cx: GhjkCtx) { + 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.plugName}`); + } + installs.user.add(instId); + foundInstalls.push(inst); + } + + while (foundInstalls.length > 0) { + const inst = foundInstalls.pop()!; + const regPlug = cx.plugs.get(inst.plugName) ?? + cx.allowedDeps.get(inst.plugName); + if (!regPlug) { + throw new Error( + `unable to find plugin "${inst.plugName}" 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); + + const { manifest } = regPlug; + if (!manifest.deps || manifest.deps.length == 0) { + installs.indie.push(installId); + } else { + const deps = []; + for (const depId of manifest.deps) { + const depPlug = cx.allowedDeps.get(depId.id); + if (!depPlug) { + throw new Error( + `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, + ); + } + const depInstall = { + plugName: depPlug.manifest.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, + regPlug: RegisteredPlug, + depShims: DepShims, +) { + const { ty: plugType, manifest } = regPlug; + let plug; + let inst: InstallConfigX; + if (plugType == "denoWorker") { + inst = validators.installConfig.parse(instUnclean); + plug = new DenoWorkerPlug( + manifest as DenoWorkerPlugManifestX, + ); + } else if (plugType == "ambientAccess") { + inst = validators.installConfig.parse(instUnclean); + plug = new AmbientAccessPlug( + manifest as AmbientAccessPlugManifestX, + ); + } else if (plugType == "asdf") { + const asdfInst = validators.asdfInstallConfig.parse(instUnclean); + inst = asdfInst; + plug = await AsdfPlug.init(envDir, asdfInst, depShims); + } else { + throw new Error( + `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, + ); + } + const installId = getInstallId(inst); + const installVersion = validators.string.parse( + inst.version ?? await plug.latestStable({ + depShims, + }), + ); + const installPath = std_path.resolve( + envDir, + "installs", + installId, + installVersion, + ); + const downloadPath = std_path.resolve( + envDir, + "downloads", + installId, + installVersion, + ); + const baseArgs: PlugArgsBase = { + 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 plug.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 plug.install({ + ...baseArgs, + availConcurrency: AVAIL_CONCURRENCY, + downloadPath: downloadPath, + tmpDirPath, + }); + void Deno.remove(tmpDirPath, { recursive: true }); + } + const binPaths = validators.stringArray.parse( + await plug.listBinPaths({ + ...baseArgs, + }), + ); + const libPaths = validators.stringArray.parse( + await plug.listLibPaths({ + ...baseArgs, + }), + ); + const includePaths = validators.stringArray.parse( + await plug.listIncludePaths({ + ...baseArgs, + }), + ); + const env = zod.record(zod.string()).parse( + await plug.execEnv({ + ...baseArgs, + }), + ); + return { env, binPaths, libPaths, includePaths, installPath, downloadPath }; +} diff --git a/core/types.ts b/modules/ports/types.ts similarity index 72% rename from core/types.ts rename to modules/ports/types.ts index 3ec7362a..688644e6 100644 --- a/core/types.ts +++ b/modules/ports/types.ts @@ -1,7 +1,88 @@ -import { zod } from "../deps/common.ts"; -import validators from "./validators.ts"; -import logger from "./logger.ts"; -import { std_path } from "../deps/cli.ts"; +import { semver, zod } from "../../deps/common.ts"; +import logger from "../../core/logger.ts"; +import { std_path } from "../../deps/common.ts"; + +const plugDep = zod.object({ + id: zod.string(), +}); + +const plugManifestBase = zod.object({ + 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(plugDep).nullish(), +}).passthrough(); + +const denoWorkerPlugManifest = plugManifestBase.merge( + zod.object({ + moduleSpecifier: zod.string().url(), + }), +); + +const ambientAccessPlugManifest = plugManifestBase.merge( + zod.object({ + 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 installConfigBase = zod.object({ + version: zod.string() + .nullish(), + conflictResolution: zod + .enum(["deferToNewer", "override"]) + .nullish() + .default("deferToNewer"), +}).passthrough(); + +const installConfig = installConfigBase.merge( + zod.object({ + plugName: zod.string().min(1), + }), +); + +const asdfInstallConfig = installConfig.merge( + zod.object({ + plugRepo: zod.string().url(), + installType: zod + .enum(["version", "ref"]), + }), +); + +const validators = { + plugDep, + plugManifestBase, + denoWorkerPlugManifest, + ambientAccessPlugManifest, + string: zod.string(), + installConfigBase, + installConfig, + asdfInstallConfig, + stringArray: zod.string().min(1).array(), +}; +export default validators; // Describes the plugin itself export type PlugManifestBase = zod.input; diff --git a/core/worker.ts b/modules/ports/worker.ts similarity index 99% rename from core/worker.ts rename to modules/ports/worker.ts index 1f36237f..ff4a8ac1 100644 --- a/core/worker.ts +++ b/modules/ports/worker.ts @@ -1,7 +1,7 @@ //// /// -import logger from "./logger.ts"; +import logger from "../../core/logger.ts"; import { type DenoWorkerPlugManifestX, type DownloadArgs, diff --git a/modules/std.ts b/modules/std.ts new file mode 100644 index 00000000..0aeb6c3f --- /dev/null +++ b/modules/std.ts @@ -0,0 +1 @@ +export const map = new Map(); diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts new file mode 100644 index 00000000..e69de29b diff --git a/modules/types.ts b/modules/types.ts new file mode 100644 index 00000000..5be9a484 --- /dev/null +++ b/modules/types.ts @@ -0,0 +1,11 @@ +import { zod } from "../deps/common.ts"; + +const module = zod.object({ + id: zod.string(), +}); + +export type Module = zod.infer; + +export default { + module, +}; diff --git a/plugs/act.ts b/plugs/act.ts index e1308d09..9c514de2 100644 --- a/plugs/act.ts +++ b/plugs/act.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "act@ghrel", diff --git a/plugs/asdf.ts b/plugs/asdf.ts index d82b4a1f..917a4899 100644 --- a/plugs/asdf.ts +++ b/plugs/asdf.ts @@ -3,7 +3,7 @@ import { asdf, AsdfInstallConfig, registerAsdfPlug, -} from "../plug.ts"; +} from "../port.ts"; registerAsdfPlug(); export default function install(config: Omit) { diff --git a/plugs/cargo-binstall.ts b/plugs/cargo-binstall.ts index 81b58eef..582f55bb 100644 --- a/plugs/cargo-binstall.ts +++ b/plugs/cargo-binstall.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; export const manifest = { name: "cargo-binstall@ghrel", diff --git a/plugs/cargo-insta.ts b/plugs/cargo-insta.ts index 31ef056e..b8bd39dc 100644 --- a/plugs/cargo-insta.ts +++ b/plugs/cargo-insta.ts @@ -11,8 +11,8 @@ import { spawn, std_fs, std_path, -} from "../plug.ts"; -import * as std_plugs from "../std.ts"; +} from "../port.ts"; +import * as std_plugs from "../modules/ports/std.ts"; const manifest = { name: "cargo-insta@cbinst", diff --git a/plugs/curl.ts b/plugs/curl.ts index acd56196..0924d8cc 100644 --- a/plugs/curl.ts +++ b/plugs/curl.ts @@ -2,7 +2,7 @@ import { addInstallGlobal, type AmbientAccessPlugManifest, registerAmbientPlugGlobal, -} from "../plug.ts"; +} from "../port.ts"; export const manifest: AmbientAccessPlugManifest = { name: "curl@aa", diff --git a/plugs/earthly.ts b/plugs/earthly.ts index 534090bc..3237b2de 100644 --- a/plugs/earthly.ts +++ b/plugs/earthly.ts @@ -10,7 +10,7 @@ import { removeFile, std_fs, std_path, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "earthly@ghrel", diff --git a/plugs/git.ts b/plugs/git.ts index 04ccb14d..7ddd4755 100644 --- a/plugs/git.ts +++ b/plugs/git.ts @@ -2,7 +2,7 @@ import { addInstallGlobal, type AmbientAccessPlugManifest, registerAmbientPlugGlobal, -} from "../plug.ts"; +} from "../port.ts"; export const manifest: AmbientAccessPlugManifest = { name: "git@aa", diff --git a/plugs/jco.ts b/plugs/jco.ts index b0234408..ce5827a8 100644 --- a/plugs/jco.ts +++ b/plugs/jco.ts @@ -16,9 +16,9 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; import node from "./node.ts"; -import * as std_plugs from "../std.ts"; +import * as std_plugs from "../modules/ports/std.ts"; const manifest = { name: "jco@npm", diff --git a/plugs/mold.ts b/plugs/mold.ts index 4ff66102..06ca2084 100644 --- a/plugs/mold.ts +++ b/plugs/mold.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "mold@ghrel", diff --git a/plugs/node.ts b/plugs/node.ts index d5c51fa4..a0e91c73 100644 --- a/plugs/node.ts +++ b/plugs/node.ts @@ -15,7 +15,7 @@ import { std_fs, std_path, std_url, -} from "../plug.ts"; +} from "../port.ts"; // import * as std_plugs from "../std.ts"; const tar_aa_id = { diff --git a/plugs/pnpm.ts b/plugs/pnpm.ts index 22a6edaa..dd0ce332 100644 --- a/plugs/pnpm.ts +++ b/plugs/pnpm.ts @@ -12,7 +12,7 @@ import { std_fs, std_path, std_url, -} from "../plug.ts"; +} from "../port.ts"; export const manifest = { name: "pnpm@ghrel", diff --git a/plugs/protoc.ts b/plugs/protoc.ts index 726bd8be..cf699fe7 100644 --- a/plugs/protoc.ts +++ b/plugs/protoc.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "protoc@ghrel", diff --git a/plugs/ruff.ts b/plugs/ruff.ts index 40ecb5dc..2779d721 100644 --- a/plugs/ruff.ts +++ b/plugs/ruff.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "ruff@ghrel", diff --git a/plugs/tar.ts b/plugs/tar.ts index 070654be..bae62b9f 100644 --- a/plugs/tar.ts +++ b/plugs/tar.ts @@ -2,7 +2,7 @@ import { addInstallGlobal, type AmbientAccessPlugManifest, registerAmbientPlugGlobal, -} from "../plug.ts"; +} from "../port.ts"; export const manifest: AmbientAccessPlugManifest = { name: "tar@aa", diff --git a/plugs/unzip.ts b/plugs/unzip.ts index 63f704ad..96c99860 100644 --- a/plugs/unzip.ts +++ b/plugs/unzip.ts @@ -2,7 +2,7 @@ import { addInstallGlobal, type AmbientAccessPlugManifest, registerAmbientPlugGlobal, -} from "../plug.ts"; +} from "../port.ts"; export const manifest: AmbientAccessPlugManifest = { name: "unzip@aa", diff --git a/plugs/wasm-opt.ts b/plugs/wasm-opt.ts index ac2210ee..f5a1316f 100644 --- a/plugs/wasm-opt.ts +++ b/plugs/wasm-opt.ts @@ -11,8 +11,8 @@ import { spawn, std_fs, std_path, -} from "../plug.ts"; -import * as std_plugs from "../std.ts"; +} from "../port.ts"; +import * as std_plugs from "../modules/ports/std.ts"; const manifest = { name: "wasm-opt@cbinst", diff --git a/plugs/wasm-tools.ts b/plugs/wasm-tools.ts index c862886c..9af95891 100644 --- a/plugs/wasm-tools.ts +++ b/plugs/wasm-tools.ts @@ -11,8 +11,8 @@ import { spawn, std_fs, std_path, -} from "../plug.ts"; -import * as std_plugs from "../std.ts"; +} from "../port.ts"; +import * as std_plugs from "../modules/ports/std.ts"; const manifest = { name: "wasm-tools@cbinst", diff --git a/plugs/wasmedge.ts b/plugs/wasmedge.ts index 778f4de8..b1280f00 100644 --- a/plugs/wasmedge.ts +++ b/plugs/wasmedge.ts @@ -14,8 +14,8 @@ import { std_fs, std_path, std_url, -} from "../plug.ts"; -import * as std_plugs from "../std.ts"; +} from "../port.ts"; +import * as std_plugs from "../modules/ports/std.ts"; const manifest = { name: "wasmedge@ghrel", diff --git a/plugs/whiz.ts b/plugs/whiz.ts index 2ea892f8..4179b773 100644 --- a/plugs/whiz.ts +++ b/plugs/whiz.ts @@ -12,7 +12,7 @@ import { std_path, std_url, unarchive, -} from "../plug.ts"; +} from "../port.ts"; const manifest = { name: "whiz@ghrel", diff --git a/plug.ts b/port.ts similarity index 87% rename from plug.ts rename to port.ts index 5e6f86e1..20f2c022 100644 --- a/plug.ts +++ b/port.ts @@ -1,3 +1,5 @@ +//! this provides common exports for Port implementors + import { addInstall, type AmbientAccessPlugManifest, @@ -9,20 +11,20 @@ import { registerAmbientPlug, registerDenoPlug, registerPlug, - validators, -} from "./core/mod.ts"; +} from "./modules/ports/mod.ts"; +import validators from "./modules/ports/types.ts"; import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; -import { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; -import * as asdf from "./core/asdf.ts"; +import { initDenoWorkerPlug, isWorker } from "./modules/ports/worker.ts"; +import * as asdf from "./modules/ports/asdf.ts"; import logger, { ConsoleErrHandler } from "./core/logger.ts"; -export * from "./core/mod.ts"; +export * from "./modules/ports/mod.ts"; export * from "./core/utils.ts"; export * from "./deps/plug.ts"; export { default as logger } from "./core/logger.ts"; -export { initDenoWorkerPlug, isWorker } from "./core/worker.ts"; -export * as asdf from "./core/asdf.ts"; -export type * from "./core/mod.ts"; +export { initDenoWorkerPlug, isWorker } from "./modules/ports/worker.ts"; +export * as asdf from "./modules/ports/asdf.ts"; +export type * from "./modules/ports/mod.ts"; export * from "./unarchive.ts"; if (isWorker()) { diff --git a/setup_globals.ts b/setup_globals.ts index 751a79e9..1cb9336b 100644 --- a/setup_globals.ts +++ b/setup_globals.ts @@ -1,4 +1,4 @@ -import type { GhjkConfig } from "./core/mod.ts"; +import type { GhjkConfig } from "./modules/ports/mod.ts"; declare global { interface Window { diff --git a/tests/ambient.ts b/tests/ambient.ts index d9a56c07..4a778553 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -1,7 +1,7 @@ -import "./setup_globals.ts"; +import "../setup_logger.ts"; import { std_assert } from "../deps/dev.ts"; -import { AmbientAccessPlug } from "../core/ambient.ts"; -import { type AmbientAccessPlugManifest } from "../core/types.ts"; +import { AmbientAccessPlug } from "../modules/ports/ambient.ts"; +import { type AmbientAccessPlugManifest } from "../modules/ports/types.ts"; import * as tar from "../plugs/tar.ts"; import * as git from "../plugs/git.ts"; From da08af2e1bb9301f98d0d983980de7103ee90ac3 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 2 Dec 2023 00:40:31 +0300 Subject: [PATCH 34/43] wip: `plugs` -> `ports` --- cli/list.ts | 2 +- ghjk.ts | 32 ++++----- host/deno.ts | 112 ++++++++++++++++++++++++++++- host/mod.ts | 17 +++-- install/hooks/bash.sh | 2 +- install/hooks/fish.fish | 2 +- install/mod.ts | 2 +- mod.ts | 23 +++--- modules/ports/ambient.ts | 10 +-- modules/ports/asdf.ts | 10 +-- modules/ports/mod.ts | 32 ++++----- modules/ports/std.ts | 40 +++++------ modules/ports/sync.ts | 64 ++++++++--------- modules/ports/types.ts | 102 +++++++++++++------------- modules/ports/worker.ts | 19 +++-- port.ts | 71 ++++++++---------- {plugs => ports}/act.ts | 8 +-- {plugs => ports}/asdf.ts | 8 +-- {plugs => ports}/cargo-binstall.ts | 10 +-- {plugs => ports}/cargo-insta.ts | 14 ++-- {plugs => ports}/curl.ts | 6 +- {plugs => ports}/earthly.ts | 8 +-- {plugs => ports}/git.ts | 6 +- {plugs => ports}/jco.ts | 14 ++-- {plugs => ports}/mold.ts | 8 +-- {plugs => ports}/node.ts | 12 ++-- {plugs => ports}/pnpm.ts | 8 +-- {plugs => ports}/protoc.ts | 8 +-- {plugs => ports}/ruff.ts | 8 +-- {plugs => ports}/tar.ts | 6 +- {plugs => ports}/unzip.ts | 6 +- {plugs => ports}/wasm-opt.ts | 14 ++-- {plugs => ports}/wasm-tools.ts | 14 ++-- {plugs => ports}/wasmedge.ts | 16 ++--- {plugs => ports}/whiz.ts | 8 +-- setup_globals.ts | 12 ---- setup_logger.ts | 20 +----- tests/ambient.ts | 14 ++-- tests/e2e.ts | 34 ++++----- {core => utils}/logger.ts | 20 ++++++ core/utils.ts => utils/mod.ts | 11 ++- unarchive.ts => utils/unarchive.ts | 2 +- 42 files changed, 461 insertions(+), 374 deletions(-) rename {plugs => ports}/act.ts (95%) rename {plugs => ports}/asdf.ts (67%) rename {plugs => ports}/cargo-binstall.ts (93%) rename {plugs => ports}/cargo-insta.ts (86%) rename {plugs => ports}/curl.ts (75%) rename {plugs => ports}/earthly.ts (94%) rename {plugs => ports}/git.ts (75%) rename {plugs => ports}/jco.ts (89%) rename {plugs => ports}/mold.ts (95%) rename {plugs => ports}/node.ts (92%) rename {plugs => ports}/pnpm.ts (95%) rename {plugs => ports}/protoc.ts (94%) rename {plugs => ports}/ruff.ts (95%) rename {plugs => ports}/tar.ts (75%) rename {plugs => ports}/unzip.ts (74%) rename {plugs => ports}/wasm-opt.ts (86%) rename {plugs => ports}/wasm-tools.ts (86%) rename {plugs => ports}/wasmedge.ts (92%) rename {plugs => ports}/whiz.ts (95%) delete mode 100644 setup_globals.ts rename {core => utils}/logger.ts (81%) rename core/utils.ts => utils/mod.ts (94%) rename unarchive.ts => utils/unarchive.ts (99%) diff --git a/cli/list.ts b/cli/list.ts index 34314235..69a0291e 100644 --- a/cli/list.ts +++ b/cli/list.ts @@ -12,7 +12,7 @@ export class ListCommand extends Command { console.log( cx.installs.map((install) => ({ install, - plug: cx.plugs.get(install.plugName), + plug: cx.ports.get(install.portName), })), ); }); diff --git a/ghjk.ts b/ghjk.ts index 3773d60b..20ff7615 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,20 +1,20 @@ export { ghjk } from "./mod.ts"; -import node from "./plugs/node.ts"; -import install from "./plugs/wasmedge.ts"; -import pnpm from "./plugs/pnpm.ts"; -import cargo_binstall from "./plugs/cargo-binstall.ts"; -import wasmedge from "./plugs/wasmedge.ts"; -import wasm_tools from "./plugs/wasm-tools.ts"; -import wasm_opt from "./plugs/wasm-opt.ts"; -import cargo_insta from "./plugs/cargo-insta.ts"; -import jco from "./plugs/jco.ts"; -import mold from "./plugs/mold.ts"; -import act from "./plugs/act.ts"; -import asdf from "./plugs/asdf.ts"; -import protoc from "./plugs/protoc.ts"; -import earthly from "./plugs/earthly.ts"; -import ruff from "./plugs/ruff.ts"; -import whiz from "./plugs/whiz.ts"; +import node from "./ports/node.ts"; +import install from "./ports/wasmedge.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"; // node({}); wasmedge({}); diff --git a/host/deno.ts b/host/deno.ts index 886b5e73..de9591d3 100644 --- a/host/deno.ts +++ b/host/deno.ts @@ -1,4 +1,112 @@ //! this loads the ghjk.ts module and provides a program for it -const mod = await import(Deno.args[0]); -console.log(JSON.stringify(mod)); +//// +/// + +import { std_url } from "../deps/common.ts"; + +import { inWorker } from "../utils/mod.ts"; +import logger, { setup as setupLogger } from "../utils/logger.ts"; +import type { GhjkConfig } from "../modules/ports/mod.ts"; + +if (inWorker()) { + initWorker(); +} + +declare global { + interface Window { + ghjk: GhjkConfig; + } +} + +function initWorker() { + setupLogger(); + self.ghjk = { + ports: new Map(), + 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); + return JSON.parse(JSON.stringify(mod)); +} + +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", + 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 index fd14b7f5..f9a24967 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -1,28 +1,27 @@ import "../setup_logger.ts"; import { std_path } from "../deps/common.ts"; -import logger from "../core/logger.ts"; -import { $ } from "../core/utils.ts"; +import logger from "../utils/logger.ts"; +import { $ } from "../utils/mod.ts"; import validators, { type SerializedConfig } from "./types.ts"; import * as std_modules from "../modules/std.ts"; - -async function getSerializedConfigDeno(configPath: string) { - const denoRunner = import.meta.resolve("./deno.ts"); - return await $`deno run --allow-read=. --allow-env --allow-net ${denoRunner} ${configPath}` - .json(); -} +import * as deno from "./deno.ts"; export async function main() { const configPath = Deno.args[0]; + logger().debug("config", configPath); + let serializedJson; switch (std_path.extname(configPath)) { case "": logger().warning("config file has no extension, assuming deno config"); /* falls through */ case ".ts": - serializedJson = await getSerializedConfigDeno(configPath); + serializedJson = await deno.getSerializedConfig( + std_path.toFileUrl(configPath).href, + ); break; // case ".jsonc": case ".json": diff --git a/install/hooks/bash.sh b/install/hooks/bash.sh index f8cbfdc5..6131b0ff 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -47,7 +47,7 @@ init_ghjk() { echo "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}" echo "$envDir" fi - export ghjk_alias="deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + export ghjk_alias="deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return fi cur_dir="$(dirname "$cur_dir")" diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index 281e402b..f09dd3ce 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -36,7 +36,7 @@ function init_ghjk echo $envDir set_color normal end - set ghjk_alias "deno run -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + set ghjk_alias "deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return end set cur_dir (dirname $cur_dir) diff --git a/install/mod.ts b/install/mod.ts index 25f6d4e2..bb35a016 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -1,6 +1,6 @@ import { std_path } from "../deps/cli.ts"; import { dirs, importRaw } from "../cli/utils.ts"; -import { spawnOutput } from "../core/utils.ts"; +import { spawnOutput } from "../utils/mod.ts"; let LD_LIBRARY_ENV: string; switch (Deno.build.os) { diff --git a/mod.ts b/mod.ts index a2d3e25f..a2f4faf3 100644 --- a/mod.ts +++ b/mod.ts @@ -2,19 +2,18 @@ //! avoid importing elsewhere at it has side-effects. import "./setup_logger.ts"; -import "./setup_globals.ts"; import { type GhjkConfig } from "./modules/ports/types.ts"; // this is only a shortcut for the cli import { runCli } from "./cli/mod.ts"; -import logger from "./core/logger.ts"; +import logger from "./utils/logger.ts"; import { GhjkSecureConfig } from "./port.ts"; -import * as std_plugs from "./modules/ports/std.ts"; +import * as std_ports from "./modules/ports/std.ts"; // we need to use global variables to allow -// plugins to access the config object. -// module imports wouldn't work as plugins might -// import a different version. +// 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 { ghjk: GhjkConfig; @@ -26,19 +25,19 @@ function runCliShim( secureConfig: GhjkSecureConfig | undefined, ) { let allowedDeps; - if (secureConfig?.allowedPluginDeps) { + if (secureConfig?.allowedPortDeps) { allowedDeps = new Map(); - for (const depId of secureConfig.allowedPluginDeps) { - const regPlug = std_plugs.map.get(depId.id); - if (!regPlug) { + for (const depId of secureConfig.allowedPortDeps) { + const regPort = std_ports.map.get(depId.id); + if (!regPort) { throw new Error( `unrecognized dep "${depId.id}" found in "allowedPluginDeps"`, ); } - allowedDeps.set(depId.id, regPlug); + allowedDeps.set(depId.id, regPort); } } else { - allowedDeps = new Map(std_plugs.map.entries()); + allowedDeps = new Map(std_ports.map.entries()); } runCli(args, { ...self.ghjk, diff --git a/modules/ports/ambient.ts b/modules/ports/ambient.ts index 0776d6fb..89ae033a 100644 --- a/modules/ports/ambient.ts +++ b/modules/ports/ambient.ts @@ -1,15 +1,15 @@ import { - type AmbientAccessPlugManifest, + type AmbientAccessPortManifest, type DownloadArgs, type InstallArgs, type ListAllArgs, type ListBinPathsArgs, - PlugBase, + PortBase, } from "./types.ts"; -import { ChildError, spawnOutput } from "../../core/utils.ts"; +import { ChildError, spawnOutput } from "../../utils/mod.ts"; -export class AmbientAccessPlug extends PlugBase { - constructor(public manifest: AmbientAccessPlugManifest) { +export class AmbientAccessPort extends PortBase { + constructor(public manifest: AmbientAccessPortManifest) { super(); if (manifest.deps && manifest.deps.length > 0) { throw new Error( diff --git a/modules/ports/asdf.ts b/modules/ports/asdf.ts index 4620df0a..8866d1c5 100644 --- a/modules/ports/asdf.ts +++ b/modules/ports/asdf.ts @@ -5,7 +5,7 @@ import { type InstallArgs, type ListAllArgs, type ListBinPathsArgs, - PlugBase, + PortBase, } from "./types.ts"; import { depBinShimPath, @@ -13,8 +13,8 @@ import { pathWithDepShims, spawn, spawnOutput, -} from "../../core/utils.ts"; -// import * as std_plugs from "../std.ts"; +} 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 @@ -34,7 +34,7 @@ export const manifest = { deps: [curl_aa_id, git_aa_id], }; -export class AsdfPlug extends PlugBase { +export class AsdfPort extends PortBase { manifest = manifest; constructor( public asdfDir: string, @@ -72,7 +72,7 @@ export class AsdfPlug extends PlugBase { ); void Deno.remove(tmpCloneDirPath, { recursive: true }); } - return new AsdfPlug(asdfDir, pluginDir, installConfig); + return new AsdfPort(asdfDir, pluginDir, installConfig); } async listAll(_args: ListAllArgs): Promise { diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index a7d22ab4..5cd3d5d1 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -2,13 +2,13 @@ export * from "./types.ts"; import { semver } from "../../deps/common.ts"; import validators, { - type AmbientAccessPlugManifest, - type DenoWorkerPlugManifest, + type AmbientAccessPortManifest, + type DenoWorkerPortManifest, type GhjkConfig, type InstallConfig, - type RegisteredPlug, + type RegisteredPort, } from "./types.ts"; -import logger from "../../core/logger.ts"; +import logger from "../../utils/logger.ts"; export const Ghjk = { cwd: Deno.cwd, @@ -16,26 +16,26 @@ export const Ghjk = { export function registerDenoPlug( cx: GhjkConfig, - manifestUnclean: DenoWorkerPlugManifest, + manifestUnclean: DenoWorkerPortManifest, ) { - const manifest = validators.denoWorkerPlugManifest.parse(manifestUnclean); + const manifest = validators.denoWorkerPortManifest.parse(manifestUnclean); registerPlug(cx, { ty: "denoWorker", manifest }); } export function registerAmbientPlug( cx: GhjkConfig, - manifestUnclean: AmbientAccessPlugManifest, + manifestUnclean: AmbientAccessPortManifest, ) { - const manifest = validators.ambientAccessPlugManifest.parse(manifestUnclean); + const manifest = validators.ambientAccessPortManifest.parse(manifestUnclean); registerPlug(cx, { ty: "ambientAccess", manifest }); } export function registerPlug( cx: GhjkConfig, - plug: RegisteredPlug, + plug: RegisteredPort, ) { const { manifest } = plug; - const conflict = cx.plugs.get(manifest.name)?.manifest; + const conflict = cx.ports.get(manifest.name)?.manifest; if (conflict) { if ( conflict.conflictResolution == "override" && @@ -56,7 +56,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, plug); + cx.ports.set(manifest.name, plug); } else if ( semver.compare( semver.parse(manifest.version), @@ -77,7 +77,7 @@ export function registerPlug( new: manifest, replaced: conflict, }); - cx.plugs.set(manifest.name, plug); + cx.ports.set(manifest.name, plug); } else { logger().debug("plug rejected due after defer", { retained: conflict, @@ -85,8 +85,8 @@ export function registerPlug( }); } } else { - logger().debug("plug registered", manifest); - cx.plugs.set(manifest.name, plug); + logger().debug("plug registered", manifest.name); + cx.ports.set(manifest.name, plug); } } @@ -94,9 +94,9 @@ export function addInstall( cx: GhjkConfig, config: InstallConfig, ) { - if (!cx.plugs.has(config.plugName)) { + if (!cx.ports.has(config.portName)) { throw new Error( - `unrecognized plug "${config.plugName}" specified by install ${ + `unrecognized plug "${config.portName}" specified by install ${ JSON.stringify(config) }`, ); diff --git a/modules/ports/std.ts b/modules/ports/std.ts index d5738490..2167b011 100644 --- a/modules/ports/std.ts +++ b/modules/ports/std.ts @@ -1,15 +1,15 @@ //! This plugin exports the list of standard plugins other //! plugins are allowed to depend on. -import validators, { PlugDep, RegisteredPlug } from "./types.ts"; -import { manifest as man_tar_aa } from "../../plugs/tar.ts"; -import { manifest as man_git_aa } from "../../plugs/git.ts"; -import { manifest as man_curl_aa } from "../../plugs/curl.ts"; -import { manifest as man_unzip_aa } from "../../plugs/unzip.ts"; -import { manifest as man_cbin_ghrel } from "../../plugs/cargo-binstall.ts"; -import { manifest as man_node_org } from "../../plugs/node.ts"; -import { manifest as man_pnpm_ghrel } from "../../plugs/pnpm.ts"; +import validators, { PortDep, RegisteredPort } 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 aaPlugs: RegisteredPlug[] = [ +const aaPorts: RegisteredPort[] = [ man_tar_aa, man_git_aa, man_curl_aa, @@ -17,50 +17,50 @@ const aaPlugs: RegisteredPlug[] = [ ] .map((man) => ({ ty: "ambientAccess", - manifest: validators.ambientAccessPlugManifest.parse(man), + manifest: validators.ambientAccessPortManifest.parse(man), })); -const denoPlugs: RegisteredPlug[] = [ +const denoPlugs: RegisteredPort[] = [ man_cbin_ghrel, man_node_org, man_pnpm_ghrel, ] .map((man) => ({ ty: "denoWorker", - manifest: validators.denoWorkerPlugManifest.parse(man), + manifest: validators.denoWorkerPortManifest.parse(man), })); export const map = Object.freeze( new Map([ - ...aaPlugs, + ...aaPorts, ...denoPlugs, ].map((plug) => [plug.manifest.name, plug])), ); export const tar_aa = Object.freeze({ id: man_tar_aa.name, -} as PlugDep); +} as PortDep); export const git_aa = Object.freeze({ id: man_git_aa.name, -} as PlugDep); +} as PortDep); export const curl_aa = Object.freeze({ id: man_curl_aa.name, -} as PlugDep); +} as PortDep); export const unzip_aa = Object.freeze({ id: man_unzip_aa.name, -} as PlugDep); +} as PortDep); export const cbin_ghrel = Object.freeze({ id: man_cbin_ghrel.name, -} as PlugDep); +} as PortDep); export const node_org = Object.freeze({ id: man_node_org.name, -} as PlugDep); +} as PortDep); export const pnpm_ghrel = Object.freeze({ id: man_pnpm_ghrel.name, -} as PlugDep); +} as PortDep); diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index ff681cef..57b6568f 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -1,20 +1,20 @@ import { std_fs, std_path, zod } from "../../deps/cli.ts"; -import logger from "../../core/logger.ts"; +import logger from "../../utils/logger.ts"; import validators, { - type AmbientAccessPlugManifestX, - type DenoWorkerPlugManifestX, + type AmbientAccessPortManifestX, + type DenoWorkerPortManifestX, type DepShims, type GhjkCtx, type InstallConfig, InstallConfigX, - type PlugArgsBase, - type RegisteredPlug, + type PortArgsBase, + type RegisteredPort, } from "./types.ts"; -import { DenoWorkerPlug } from "./worker.ts"; +import { DenoWorkerPort } from "./worker.ts"; import { AVAIL_CONCURRENCY, dirs } from "../../cli/utils.ts"; -import { AmbientAccessPlug } from "./ambient.ts"; -import { AsdfPlug } from "./asdf.ts"; -import { getInstallId } from "../../core/utils.ts"; +import { AmbientAccessPort } from "./ambient.ts"; +import { AsdfPort } from "./asdf.ts"; +import { getInstallId } from "../../utils/mod.ts"; async function findConfig(path: string): Promise { let current = path; @@ -72,9 +72,9 @@ export async function sync(cx: GhjkCtx) { const installId = pendingInstalls.pop()!; const inst = installs.all.get(installId)!; - const regPlug = cx.plugs.get(inst.plugName) ?? - cx.allowedDeps.get(inst.plugName)!; - const { manifest } = regPlug; + const regPort = cx.ports.get(inst.portName) ?? + cx.allowedDeps.get(inst.portName)!; + const { manifest } = regPort; const depShims: DepShims = {}; // create the shims for the deps @@ -82,9 +82,9 @@ export async function sync(cx: GhjkCtx) { prefix: `ghjk_dep_shims_${installId}_`, }); for (const depId of manifest.deps ?? []) { - const depPlug = cx.allowedDeps.get(depId.id)!; + const depPort = cx.allowedDeps.get(depId.id)!; const depInstall = { - plugName: depPlug.manifest.name, + portName: depPort.manifest.name, }; const depInstallId = getInstallId(depInstall); const depArtifacts = artifacts.get(depInstallId); @@ -107,7 +107,7 @@ export async function sync(cx: GhjkCtx) { let thisArtifacts; try { - thisArtifacts = await doInstall(envDir, inst, regPlug, depShims); + thisArtifacts = await doInstall(envDir, inst, regPort, depShims); } catch (err) { throw new Error(`error installing ${installId}`, { cause: err }); } @@ -205,7 +205,7 @@ function buildInstallGraph(cx: GhjkCtx) { const instId = getInstallId(inst); // FIXME: better support for multi installs if (installs.user.has(instId)) { - throw new Error(`duplicate install found by plugin ${inst.plugName}`); + throw new Error(`duplicate install found by plugin ${inst.portName}`); } installs.user.add(instId); foundInstalls.push(inst); @@ -213,11 +213,11 @@ function buildInstallGraph(cx: GhjkCtx) { while (foundInstalls.length > 0) { const inst = foundInstalls.pop()!; - const regPlug = cx.plugs.get(inst.plugName) ?? - cx.allowedDeps.get(inst.plugName); - if (!regPlug) { + const regPort = cx.ports.get(inst.portName) ?? + cx.allowedDeps.get(inst.portName); + if (!regPort) { throw new Error( - `unable to find plugin "${inst.plugName}" specified by install ${ + `unable to find plugin "${inst.portName}" specified by install ${ JSON.stringify(inst) }`, ); @@ -233,20 +233,20 @@ function buildInstallGraph(cx: GhjkCtx) { installs.all.set(installId, inst); - const { manifest } = regPlug; + const { manifest } = regPort; if (!manifest.deps || manifest.deps.length == 0) { installs.indie.push(installId); } else { const deps = []; for (const depId of manifest.deps) { - const depPlug = cx.allowedDeps.get(depId.id); - if (!depPlug) { + const depPort = cx.allowedDeps.get(depId.id); + if (!depPort) { throw new Error( `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, ); } const depInstall = { - plugName: depPlug.manifest.name, + portName: depPort.manifest.name, }; const depInstallId = getInstallId(depInstall); @@ -323,26 +323,26 @@ type InstallArtifacts = DePromisify>; async function doInstall( envDir: string, instUnclean: InstallConfig, - regPlug: RegisteredPlug, + regPort: RegisteredPort, depShims: DepShims, ) { - const { ty: plugType, manifest } = regPlug; + const { ty: plugType, manifest } = regPort; let plug; let inst: InstallConfigX; if (plugType == "denoWorker") { inst = validators.installConfig.parse(instUnclean); - plug = new DenoWorkerPlug( - manifest as DenoWorkerPlugManifestX, + plug = new DenoWorkerPort( + manifest as DenoWorkerPortManifestX, ); } else if (plugType == "ambientAccess") { inst = validators.installConfig.parse(instUnclean); - plug = new AmbientAccessPlug( - manifest as AmbientAccessPlugManifestX, + plug = new AmbientAccessPort( + manifest as AmbientAccessPortManifestX, ); } else if (plugType == "asdf") { const asdfInst = validators.asdfInstallConfig.parse(instUnclean); inst = asdfInst; - plug = await AsdfPlug.init(envDir, asdfInst, depShims); + plug = await AsdfPort.init(envDir, asdfInst, depShims); } else { throw new Error( `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, @@ -366,7 +366,7 @@ async function doInstall( installId, installVersion, ); - const baseArgs: PlugArgsBase = { + const baseArgs: PortArgsBase = { installPath: installPath, // installType: "version", installVersion: installVersion, diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 688644e6..0f167373 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -1,12 +1,14 @@ import { semver, zod } from "../../deps/common.ts"; -import logger from "../../core/logger.ts"; +import logger from "../../utils/logger.ts"; import { std_path } from "../../deps/common.ts"; -const plugDep = zod.object({ +// TODO: find a better identification scheme for ports + +const portDep = zod.object({ id: zod.string(), }); -const plugManifestBase = zod.object({ +const portManifestBase = zod.object({ name: zod.string().min(1), version: zod.string() .refine((str) => semver.parse(str), { @@ -16,16 +18,16 @@ const plugManifestBase = zod.object({ .enum(["deferToNewer", "override"]) .nullish() .default("deferToNewer"), - deps: zod.array(plugDep).nullish(), + deps: zod.array(portDep).nullish(), }).passthrough(); -const denoWorkerPlugManifest = plugManifestBase.merge( +const denoWorkerPortManifest = portManifestBase.merge( zod.object({ moduleSpecifier: zod.string().url(), }), ); -const ambientAccessPlugManifest = plugManifestBase.merge( +const ambientAccessPortManifest = portManifestBase.merge( zod.object({ execName: zod.string().min(1), versionExtractFlag: zod.enum([ @@ -59,7 +61,7 @@ const installConfigBase = zod.object({ const installConfig = installConfigBase.merge( zod.object({ - plugName: zod.string().min(1), + portName: zod.string().min(1), }), ); @@ -72,10 +74,10 @@ const asdfInstallConfig = installConfig.merge( ); const validators = { - plugDep, - plugManifestBase, - denoWorkerPlugManifest, - ambientAccessPlugManifest, + portDep, + portManifestBase, + denoWorkerPortManifest, + ambientAccessPortManifest, string: zod.string(), installConfigBase, installConfig, @@ -85,48 +87,48 @@ const validators = { export default validators; // Describes the plugin itself -export type PlugManifestBase = zod.input; +export type PortManifestBase = zod.input; -export type DenoWorkerPlugManifest = zod.input< - typeof validators.denoWorkerPlugManifest +export type DenoWorkerPortManifest = zod.input< + typeof validators.denoWorkerPortManifest >; -export type AmbientAccessPlugManifest = zod.input< - typeof validators.ambientAccessPlugManifest +export type AmbientAccessPortManifest = zod.input< + typeof validators.ambientAccessPortManifest >; // Describes the plugin itself -export type PlugManifest = - | PlugManifestBase - | DenoWorkerPlugManifest - | AmbientAccessPlugManifest; - -export type PlugDep = zod.infer; -export type PlugManifestBaseX = zod.infer; -export type DenoWorkerPlugManifestX = zod.infer< - typeof validators.denoWorkerPlugManifest +export type PortManifest = + | PortManifestBase + | DenoWorkerPortManifest + | AmbientAccessPortManifest; + +export type PortDep = zod.infer; +export type PortManifestBaseX = zod.infer; +export type DenoWorkerPortManifestX = zod.infer< + typeof validators.denoWorkerPortManifest >; -export type AmbientAccessPlugManifestX = zod.infer< - typeof validators.ambientAccessPlugManifest +export type AmbientAccessPortManifestX = zod.infer< + typeof validators.ambientAccessPortManifest >; -// This is the transformed version of PlugManifest, ready for consumption -export type PlugManifestX = - | PlugManifestBaseX - | DenoWorkerPlugManifestX - | AmbientAccessPlugManifestX; +// This is the transformed version of PortManifest, ready for consumption +export type PortManifestX = + | PortManifestBaseX + | DenoWorkerPortManifestX + | AmbientAccessPortManifestX; -export type RegisteredPlug = { +export type RegisteredPort = { ty: "ambientAccess"; - manifest: AmbientAccessPlugManifestX; + manifest: AmbientAccessPortManifestX; } | { ty: "denoWorker"; - manifest: DenoWorkerPlugManifestX; + manifest: DenoWorkerPortManifestX; } | { ty: "asdf"; - manifest: PlugManifestBaseX; + manifest: PortManifestBaseX; }; -export type RegisteredPlugs = Map; +export type RegisteredPorts = Map; export type InstallConfigBase = zod.input; @@ -137,8 +139,8 @@ export type AsdfInstallConfig = zod.input; export type AsdfInstallConfigX = zod.infer; export interface GhjkConfig { - /// Plugs explicitly added by the user - plugs: RegisteredPlugs; + /// Ports explicitly added by the user + ports: RegisteredPorts; installs: InstallConfig[]; } @@ -146,16 +148,16 @@ export interface GhjkConfig { /// from the config script instead of the global variable approach the /// main [`GhjkConfig`] can take. export interface GhjkSecureConfig { - allowedPluginDeps?: PlugDep[]; + allowedPortDeps?: PortDep[]; } export type GhjkCtx = GhjkConfig & { - /// Standard plugs allowed to be use as deps by other plugs - allowedDeps: RegisteredPlugs; + /// Standard list of ports allowed to be use as deps by other ports + allowedDeps: RegisteredPorts; }; -export abstract class PlugBase { - abstract manifest: PlugManifest; +export abstract class PortBase { + abstract manifest: PortManifest; execEnv( _args: ExecEnvArgs, @@ -190,7 +192,7 @@ export abstract class PlugBase { latestStable(args: ListAllArgs): Promise | string { return (async () => { logger().warning( - `using default implementation of latestStable for plug ${this.manifest.name}`, + `using default implementation of latestStable for port ${this.manifest.name}`, ); const allVers = await this.listAll(args); if (allVers.length == 0) { @@ -227,7 +229,7 @@ export type DepShims = Record< export type PlatformInfo = Omit; -export interface PlugArgsBase { +export interface PortArgsBase { // installType: "version" | "ref"; installVersion: string; installPath: string; @@ -240,18 +242,18 @@ export interface ListAllArgs { depShims: DepShims; } -export interface ListBinPathsArgs extends PlugArgsBase { +export interface ListBinPathsArgs extends PortArgsBase { } -export interface ExecEnvArgs extends PlugArgsBase { +export interface ExecEnvArgs extends PortArgsBase { } -export interface DownloadArgs extends PlugArgsBase { +export interface DownloadArgs extends PortArgsBase { downloadPath: string; tmpDirPath: string; } -export interface InstallArgs extends PlugArgsBase { +export interface InstallArgs extends PortArgsBase { availConcurrency: number; downloadPath: string; tmpDirPath: string; diff --git a/modules/ports/worker.ts b/modules/ports/worker.ts index ff4a8ac1..2594b39c 100644 --- a/modules/ports/worker.ts +++ b/modules/ports/worker.ts @@ -1,21 +1,18 @@ //// /// -import logger from "../../core/logger.ts"; +import logger from "../../utils/logger.ts"; +import { inWorker } from "../../utils/mod.ts"; import { - type DenoWorkerPlugManifestX, + type DenoWorkerPortManifestX, type DownloadArgs, type ExecEnvArgs, type InstallArgs, type ListAllArgs, type ListBinPathsArgs, - PlugBase, + PortBase, } from "./types.ts"; -export function isWorker() { - return !!self.name; -} - type WorkerReq = { ty: "assert"; arg: { @@ -76,8 +73,8 @@ type WorkerResp = { /// Make sure to call this before any `await` point or your /// plug might miss messages -export function initDenoWorkerPlug

(plugInit: () => P) { - if (isWorker()) { +export function initDenoWorkerPlug

(plugInit: () => P) { + if (inWorker()) { // let plugClass: (new () => PlugBase) | undefined; // const plugInit = () => { // if (!plugClass) { @@ -159,9 +156,9 @@ export function initDenoWorkerPlug

(plugInit: () => P) { // [P in keyof T]-?: T[P] extends Function ? P : never; // }[keyof T]; -export class DenoWorkerPlug extends PlugBase { +export class DenoWorkerPort extends PortBase { constructor( - public manifest: DenoWorkerPlugManifestX, + public manifest: DenoWorkerPortManifestX, ) { super(); } diff --git a/port.ts b/port.ts index 20f2c022..6d67c581 100644 --- a/port.ts +++ b/port.ts @@ -2,87 +2,72 @@ import { addInstall, - type AmbientAccessPlugManifest, - type DenoWorkerPlugManifest, + type AmbientAccessPortManifest, + type DenoWorkerPortManifest, type DownloadArgs, type GhjkConfig, type InstallConfig, - type PlugBase, + type PortBase, registerAmbientPlug, registerDenoPlug, registerPlug, } from "./modules/ports/mod.ts"; import validators from "./modules/ports/types.ts"; import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; -import { initDenoWorkerPlug, isWorker } from "./modules/ports/worker.ts"; +import { initDenoWorkerPlug } from "./modules/ports/worker.ts"; import * as asdf from "./modules/ports/asdf.ts"; -import logger, { ConsoleErrHandler } from "./core/logger.ts"; +import logger, { setup as setupLogger } from "./utils/logger.ts"; +import { inWorker } from "./utils/mod.ts"; export * from "./modules/ports/mod.ts"; -export * from "./core/utils.ts"; +export * from "./utils/mod.ts"; export * from "./deps/plug.ts"; -export { default as logger } from "./core/logger.ts"; -export { initDenoWorkerPlug, isWorker } from "./modules/ports/worker.ts"; +export { default as logger } from "./utils/logger.ts"; +export { initDenoWorkerPlug } from "./modules/ports/worker.ts"; export * as asdf from "./modules/ports/asdf.ts"; export type * from "./modules/ports/mod.ts"; -export * from "./unarchive.ts"; +export * from "./utils/unarchive.ts"; -if (isWorker()) { - log.setup({ - handlers: { - console: new ConsoleErrHandler("NOTSET"), - }, - - loggers: { - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - [self.name]: { - level: "DEBUG", - handlers: ["console"], - }, - }, - }); +if (inWorker()) { + setupLogger(); } declare global { interface Window { - // this is null except when from from `ghjk.ts` - // i.e. a deno worker plug context won't have it avail + // this is null except when we're realmed along `ghjk.ts` + // i.e. a deno worker port context won't have it avail ghjk: GhjkConfig; } } -export function registerDenoPlugGlobal

( - manifestUnclean: DenoWorkerPlugManifest, +function isInConfig() { + return !!self.ghjk; +} + +export function registerDenoPlugGlobal

( + manifestUnclean: DenoWorkerPortManifest, plugInit: () => P, ) { - if (self.ghjk) { - if (isWorker()) throw new Error("literally impossible!"); + if (isInConfig()) { registerDenoPlug(self.ghjk, manifestUnclean); - } else { + } else if (inWorker()) { initDenoWorkerPlug(plugInit); } } -export function registerAsdfPlug() { - if (self.ghjk) { +export function registerAsdfPort() { + if (isInConfig()) { registerPlug(self.ghjk, { ty: "asdf", - manifest: validators.plugManifestBase.parse(asdf.manifest), + manifest: validators.portManifestBase.parse(asdf.manifest), }); } } export function registerAmbientPlugGlobal( - manifestUnclean: AmbientAccessPlugManifest, + manifestUnclean: AmbientAccessPortManifest, ) { - if (self.ghjk) { + if (isInConfig()) { registerAmbientPlug(self.ghjk, manifestUnclean); } } @@ -90,7 +75,7 @@ export function registerAmbientPlugGlobal( export function addInstallGlobal( config: InstallConfig, ) { - if (self.ghjk) { + if (isInConfig()) { addInstall(self.ghjk, config); } } diff --git a/plugs/act.ts b/ports/act.ts similarity index 95% rename from plugs/act.ts rename to ports/act.ts index 9c514de2..eacc8416 100644 --- a/plugs/act.ts +++ b/ports/act.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "nektos"; const repoName = "act"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; listBinPaths(): string[] { diff --git a/plugs/asdf.ts b/ports/asdf.ts similarity index 67% rename from plugs/asdf.ts rename to ports/asdf.ts index 917a4899..bc05223c 100644 --- a/plugs/asdf.ts +++ b/ports/asdf.ts @@ -2,13 +2,13 @@ import { addInstallGlobal, asdf, AsdfInstallConfig, - registerAsdfPlug, + registerAsdfPort, } from "../port.ts"; -registerAsdfPlug(); -export default function install(config: Omit) { +registerAsdfPort(); +export default function install(config: Omit) { addInstallGlobal({ - plugName: asdf.manifest.name, + portName: asdf.manifest.name, ...config, }); } diff --git a/plugs/cargo-binstall.ts b/ports/cargo-binstall.ts similarity index 93% rename from plugs/cargo-binstall.ts rename to ports/cargo-binstall.ts index 582f55bb..03126827 100644 --- a/plugs/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ export const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "cargo-bins"; const repoName = "cargo-binstall"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async listAll() { @@ -91,7 +91,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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 plugs + // 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 { diff --git a/plugs/cargo-insta.ts b/ports/cargo-insta.ts similarity index 86% rename from plugs/cargo-insta.ts rename to ports/cargo-insta.ts index b8bd39dc..c86047f6 100644 --- a/plugs/cargo-insta.ts +++ b/ports/cargo-insta.ts @@ -5,34 +5,34 @@ import { InstallArgs, type InstallConfigBase, logger, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, std_fs, std_path, } from "../port.ts"; -import * as std_plugs from "../modules/ports/std.ts"; +import * as std_ports from "../modules/ports/std.ts"; const manifest = { name: "cargo-insta@cbinst", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_ghrel, + std_ports.cbin_ghrel, ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async listAll() { @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await spawn([ - depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), "cargo-insta", `--version`, args.installVersion, diff --git a/plugs/curl.ts b/ports/curl.ts similarity index 75% rename from plugs/curl.ts rename to ports/curl.ts index 0924d8cc..f9ca1e8e 100644 --- a/plugs/curl.ts +++ b/ports/curl.ts @@ -1,10 +1,10 @@ import { addInstallGlobal, - type AmbientAccessPlugManifest, + type AmbientAccessPortManifest, registerAmbientPlugGlobal, } from "../port.ts"; -export const manifest: AmbientAccessPlugManifest = { +export const manifest: AmbientAccessPortManifest = { name: "curl@aa", version: "0.1.0", execName: "curl", @@ -16,6 +16,6 @@ export const manifest: AmbientAccessPlugManifest = { registerAmbientPlugGlobal(manifest); export default function install() { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, }); } diff --git a/plugs/earthly.ts b/ports/earthly.ts similarity index 94% rename from plugs/earthly.ts rename to ports/earthly.ts index 3237b2de..63ee110d 100644 --- a/plugs/earthly.ts +++ b/ports/earthly.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -18,11 +18,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -31,7 +31,7 @@ const repoOwner = "earthly"; const repoName = "earthly"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async latestStable(): Promise { diff --git a/plugs/git.ts b/ports/git.ts similarity index 75% rename from plugs/git.ts rename to ports/git.ts index 7ddd4755..e54c6fff 100644 --- a/plugs/git.ts +++ b/ports/git.ts @@ -1,10 +1,10 @@ import { addInstallGlobal, - type AmbientAccessPlugManifest, + type AmbientAccessPortManifest, registerAmbientPlugGlobal, } from "../port.ts"; -export const manifest: AmbientAccessPlugManifest = { +export const manifest: AmbientAccessPortManifest = { name: "git@aa", version: "0.1.0", execName: "git", @@ -16,6 +16,6 @@ export const manifest: AmbientAccessPlugManifest = { registerAmbientPlugGlobal(manifest); export default function git() { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, }); } diff --git a/plugs/jco.ts b/ports/jco.ts similarity index 89% rename from plugs/jco.ts rename to ports/jco.ts index ce5827a8..37292b6d 100644 --- a/plugs/jco.ts +++ b/ports/jco.ts @@ -8,7 +8,7 @@ import { ListAllArgs, pathWithDepShims, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, @@ -18,28 +18,28 @@ import { unarchive, } from "../port.ts"; import node from "./node.ts"; -import * as std_plugs from "../modules/ports/std.ts"; +import * as std_ports from "../modules/ports/std.ts"; const manifest = { name: "jco@npm", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.node_org, + std_ports.node_org, ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install({ version }: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, version, }); // FIXME: conflict flags for install configs node({}); } -class Plug extends PlugBase { +class Port extends PortBase { manifest = manifest; async listAll(_env: ListAllArgs) { @@ -87,7 +87,7 @@ class Plug extends PlugBase { args.installPath, ); await spawn([ - depBinShimPath(std_plugs.node_org, "npm", args.depShims), + depBinShimPath(std_ports.node_org, "npm", args.depShims), "install", "--no-fund", ], { diff --git a/plugs/mold.ts b/ports/mold.ts similarity index 95% rename from plugs/mold.ts rename to ports/mold.ts index 06ca2084..03388818 100644 --- a/plugs/mold.ts +++ b/ports/mold.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "rui314"; const repoName = "mold"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async latestStable(): Promise { diff --git a/plugs/node.ts b/ports/node.ts similarity index 92% rename from plugs/node.ts rename to ports/node.ts index a0e91c73..9267e3bb 100644 --- a/plugs/node.ts +++ b/ports/node.ts @@ -8,7 +8,7 @@ import { type InstallConfigBase, ListAllArgs, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, @@ -16,13 +16,13 @@ import { std_path, std_url, } from "../port.ts"; -// import * as std_plugs from "../std.ts"; +// import * as std_ports from "../std.ts"; const tar_aa_id = { id: "tar@aa", }; -// TODO: sanity check exports of all plugs +// TODO: sanity check exports of all ports export const manifest = { name: "node@org", version: "0.1.0", @@ -32,17 +32,17 @@ export const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); // FIXME: improve multi platform support story export default function install({ version }: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, version, }); } -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; execEnv(args: ExecEnvArgs) { diff --git a/plugs/pnpm.ts b/ports/pnpm.ts similarity index 95% rename from plugs/pnpm.ts rename to ports/pnpm.ts index dd0ce332..3a706baa 100644 --- a/plugs/pnpm.ts +++ b/ports/pnpm.ts @@ -6,7 +6,7 @@ import { type InstallConfigBase, ListAllArgs, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -19,16 +19,16 @@ export const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install({ version }: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, version, }); } -class Plug extends PlugBase { +class Port extends PortBase { manifest = manifest; async listAll(_env: ListAllArgs) { diff --git a/plugs/protoc.ts b/ports/protoc.ts similarity index 94% rename from plugs/protoc.ts rename to ports/protoc.ts index cf699fe7..98be5b7c 100644 --- a/plugs/protoc.ts +++ b/ports/protoc.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "protocolbuffers"; const repoName = "protobuf"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async latestStable(): Promise { diff --git a/plugs/ruff.ts b/ports/ruff.ts similarity index 95% rename from plugs/ruff.ts rename to ports/ruff.ts index 2779d721..47d57004 100644 --- a/plugs/ruff.ts +++ b/ports/ruff.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "astral-sh"; const repoName = "ruff"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async latestStable(): Promise { diff --git a/plugs/tar.ts b/ports/tar.ts similarity index 75% rename from plugs/tar.ts rename to ports/tar.ts index bae62b9f..1fc3ce42 100644 --- a/plugs/tar.ts +++ b/ports/tar.ts @@ -1,10 +1,10 @@ import { addInstallGlobal, - type AmbientAccessPlugManifest, + type AmbientAccessPortManifest, registerAmbientPlugGlobal, } from "../port.ts"; -export const manifest: AmbientAccessPlugManifest = { +export const manifest: AmbientAccessPortManifest = { name: "tar@aa", version: "0.1.0", execName: "tar", @@ -16,6 +16,6 @@ export const manifest: AmbientAccessPlugManifest = { registerAmbientPlugGlobal(manifest); export default function install() { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, }); } diff --git a/plugs/unzip.ts b/ports/unzip.ts similarity index 74% rename from plugs/unzip.ts rename to ports/unzip.ts index 96c99860..24e06fbe 100644 --- a/plugs/unzip.ts +++ b/ports/unzip.ts @@ -1,10 +1,10 @@ import { addInstallGlobal, - type AmbientAccessPlugManifest, + type AmbientAccessPortManifest, registerAmbientPlugGlobal, } from "../port.ts"; -export const manifest: AmbientAccessPlugManifest = { +export const manifest: AmbientAccessPortManifest = { name: "unzip@aa", version: "0.1.0", execName: "unzip", @@ -16,6 +16,6 @@ export const manifest: AmbientAccessPlugManifest = { registerAmbientPlugGlobal(manifest); export default function install() { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, }); } diff --git a/plugs/wasm-opt.ts b/ports/wasm-opt.ts similarity index 86% rename from plugs/wasm-opt.ts rename to ports/wasm-opt.ts index f5a1316f..bcd37e57 100644 --- a/plugs/wasm-opt.ts +++ b/ports/wasm-opt.ts @@ -5,34 +5,34 @@ import { InstallArgs, type InstallConfigBase, logger, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, std_fs, std_path, } from "../port.ts"; -import * as std_plugs from "../modules/ports/std.ts"; +import * as std_ports from "../modules/ports/std.ts"; const manifest = { name: "wasm-opt@cbinst", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_ghrel, + std_ports.cbin_ghrel, ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async listAll() { @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await spawn([ - depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-opt", `--version`, args.installVersion, diff --git a/plugs/wasm-tools.ts b/ports/wasm-tools.ts similarity index 86% rename from plugs/wasm-tools.ts rename to ports/wasm-tools.ts index 9af95891..10abde8b 100644 --- a/plugs/wasm-tools.ts +++ b/ports/wasm-tools.ts @@ -5,34 +5,34 @@ import { InstallArgs, type InstallConfigBase, logger, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, std_fs, std_path, } from "../port.ts"; -import * as std_plugs from "../modules/ports/std.ts"; +import * as std_ports from "../modules/ports/std.ts"; const manifest = { name: "wasm-tools@cbinst", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.cbin_ghrel, + std_ports.cbin_ghrel, ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async listAll() { @@ -62,7 +62,7 @@ export class Plug extends PlugBase { return; } await spawn([ - depBinShimPath(std_plugs.cbin_ghrel, "cargo-binstall", args.depShims), + depBinShimPath(std_ports.cbin_ghrel, "cargo-binstall", args.depShims), "wasm-tools", `--version`, args.installVersion, diff --git a/plugs/wasmedge.ts b/ports/wasmedge.ts similarity index 92% rename from plugs/wasmedge.ts rename to ports/wasmedge.ts index b1280f00..9fe539d6 100644 --- a/plugs/wasmedge.ts +++ b/ports/wasmedge.ts @@ -7,7 +7,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, spawn, @@ -15,18 +15,18 @@ import { std_path, std_url, } from "../port.ts"; -import * as std_plugs from "../modules/ports/std.ts"; +import * as std_ports from "../modules/ports/std.ts"; const manifest = { name: "wasmedge@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, deps: [ - std_plugs.tar_aa, + std_ports.tar_aa, ], }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); // TODO: wasmedge extension and plugin support /* @@ -53,7 +53,7 @@ const supportedPlugins = [ */ export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -62,7 +62,7 @@ const repoOwner = "WasmEdge"; const repoName = "WasmEdge"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; execEnv(args: ExecEnvArgs) { @@ -111,7 +111,7 @@ export class Plug extends PlugBase { const fileDwnPath = std_path.resolve(args.downloadPath, fileName); await spawn([ - depBinShimPath(std_plugs.tar_aa, "tar", args.depShims), + depBinShimPath(std_ports.tar_aa, "tar", args.depShims), "xf", fileDwnPath, `--directory=${args.tmpDirPath}`, @@ -155,7 +155,7 @@ function downloadUrl(installVersion: string, platform: PlatformInfo) { 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 plugs + // we'll need a way to expose that to ports const os = "manylinux2014"; let arch; switch (platform.arch) { diff --git a/plugs/whiz.ts b/ports/whiz.ts similarity index 95% rename from plugs/whiz.ts rename to ports/whiz.ts index 4179b773..a697b9e0 100644 --- a/plugs/whiz.ts +++ b/ports/whiz.ts @@ -5,7 +5,7 @@ import { InstallArgs, type InstallConfigBase, type PlatformInfo, - PlugBase, + PortBase, registerDenoPlugGlobal, removeFile, std_fs, @@ -20,11 +20,11 @@ const manifest = { moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Plug()); +registerDenoPlugGlobal(manifest, () => new Port()); export default function install(config: InstallConfigBase = {}) { addInstallGlobal({ - plugName: manifest.name, + portName: manifest.name, ...config, }); } @@ -33,7 +33,7 @@ const repoOwner = "zifeo"; const repoName = "whiz"; const repoAddress = `https://github.com/${repoOwner}/${repoName}`; -export class Plug extends PlugBase { +export class Port extends PortBase { manifest = manifest; async latestStable(): Promise { diff --git a/setup_globals.ts b/setup_globals.ts deleted file mode 100644 index 1cb9336b..00000000 --- a/setup_globals.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { GhjkConfig } from "./modules/ports/mod.ts"; - -declare global { - interface Window { - ghjk: GhjkConfig; - } -} - -self.ghjk = { - plugs: new Map(), - installs: [], -}; diff --git a/setup_logger.ts b/setup_logger.ts index 5a931608..348b2bf0 100644 --- a/setup_logger.ts +++ b/setup_logger.ts @@ -1,19 +1,3 @@ -import { log } from "./deps/common.ts"; -import { ConsoleErrHandler } from "./core/logger.ts"; +import { setup } from "./utils/logger.ts"; -log.setup({ - handlers: { - console: new ConsoleErrHandler("NOTSET"), - }, - - loggers: { - default: { - level: "DEBUG", - handlers: ["console"], - }, - ghjk: { - level: "DEBUG", - handlers: ["console"], - }, - }, -}); +setup(); diff --git a/tests/ambient.ts b/tests/ambient.ts index 4a778553..10973f06 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -1,12 +1,12 @@ import "../setup_logger.ts"; import { std_assert } from "../deps/dev.ts"; -import { AmbientAccessPlug } from "../modules/ports/ambient.ts"; -import { type AmbientAccessPlugManifest } from "../modules/ports/types.ts"; +import { AmbientAccessPort } from "../modules/ports/ambient.ts"; +import { type AmbientAccessPortManifest } from "../modules/ports/types.ts"; -import * as tar from "../plugs/tar.ts"; -import * as git from "../plugs/git.ts"; -import * as curl from "../plugs/curl.ts"; -import * as unzip from "../plugs/unzip.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 = [ { @@ -24,7 +24,7 @@ const manifests = [ ]; for (const manifest of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { - const plug = new AmbientAccessPlug(manifest as AmbientAccessPlugManifest); + 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 index 02873c3c..00dc6e2a 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,5 +1,5 @@ import "../setup_logger.ts"; -import { spawn } from "../core/utils.ts"; +import { spawn } from "../utils/mod.ts"; type TestCase = { name: string; @@ -77,7 +77,7 @@ await dockerTest([ // 3 megs { name: "protoc", - imports: `import plug from "$ghjk/plugs/protoc.ts"`, + imports: `import port from "$ghjk/ports/protoc.ts"`, confFn: `async () => { plug({ }); }`, @@ -86,7 +86,7 @@ await dockerTest([ // 6 megs { name: "ruff", - imports: `import plug from "$ghjk/plugs/ruff.ts"`, + imports: `import port from "$ghjk/ports/ruff.ts"`, confFn: `async () => { plug({ }); }`, @@ -95,7 +95,7 @@ await dockerTest([ // 7 megs { name: "whiz", - imports: `import plug from "$ghjk/plugs/whiz.ts"`, + imports: `import port from "$ghjk/ports/whiz.ts"`, confFn: `async () => { plug({ }); }`, @@ -104,7 +104,7 @@ await dockerTest([ // 7 megs { name: "act", - imports: `import plug from "$ghjk/plugs/act.ts"`, + imports: `import port from "$ghjk/ports/act.ts"`, confFn: `async () => { plug({ }); }`, @@ -113,7 +113,7 @@ await dockerTest([ // 7 megs { name: "cargo-binstall", - imports: `import plug from "$ghjk/plugs/cargo-binstall.ts"`, + imports: `import port from "$ghjk/ports/cargo-binstall.ts"`, confFn: `async () => { plug({ }); }`, @@ -122,7 +122,7 @@ await dockerTest([ // 8 megs { name: "mold", - imports: `import plug from "$ghjk/plugs/mold.ts"`, + imports: `import port from "$ghjk/ports/mold.ts"`, confFn: `async () => { plug({ }); }`, @@ -131,7 +131,7 @@ await dockerTest([ // 16 megs { name: "wasmedge", - imports: `import plug from "$ghjk/plugs/wasmedge.ts"`, + imports: `import port from "$ghjk/ports/wasmedge.ts"`, confFn: `async () => { plug({ }); }`, @@ -140,7 +140,7 @@ await dockerTest([ // cargo binstall +7 megs { name: "cargo-insta", - imports: `import plug from "$ghjk/plugs/cargo-insta.ts"`, + imports: `import port from "$ghjk/ports/cargo-insta.ts"`, confFn: `async () => { plug({ }); }`, @@ -149,7 +149,7 @@ await dockerTest([ // cargo binsatll 13 megs { name: "wasm-tools", - imports: `import plug from "$ghjk/plugs/wasm-tools.ts"`, + imports: `import port from "$ghjk/ports/wasm-tools.ts"`, confFn: `async () => { plug({ }); }`, @@ -158,7 +158,7 @@ await dockerTest([ // 25 megs { name: "node", - imports: `import plug from "$ghjk/plugs/node.ts"`, + imports: `import port from "$ghjk/ports/node.ts"`, confFn: `async () => { plug({ }); }`, @@ -167,7 +167,7 @@ await dockerTest([ // cargo-binstall + 22 megs { name: "wasm-opt", - imports: `import plug from "$ghjk/plugs/wasm-opt.ts"`, + imports: `import port from "$ghjk/ports/wasm-opt.ts"`, confFn: `async () => { plug({ }); }`, @@ -176,7 +176,7 @@ await dockerTest([ // 42 megs { name: "pnpm", - imports: `import plug from "$ghjk/plugs/earthly.ts"`, + imports: `import port from "$ghjk/ports/earthly.ts"`, confFn: `async () => { plug({ }); }`, @@ -185,7 +185,7 @@ await dockerTest([ // 56 megs { name: "pnpm", - imports: `import plug from "$ghjk/plugs/pnpm.ts"`, + imports: `import port from "$ghjk/ports/pnpm.ts"`, confFn: `async () => { plug({ }); }`, @@ -194,7 +194,7 @@ await dockerTest([ // node + more megs { name: "jco", - imports: `import plug from "$ghjk/plugs/jco.ts"`, + imports: `import port from "$ghjk/ports/jco.ts"`, confFn: `async () => { plug({ }); }`, @@ -203,7 +203,7 @@ await dockerTest([ // big { name: "asdf-zig", - imports: `import plug from "$ghjk/plugs/asdf.ts"`, + imports: `import port from "$ghjk/ports/asdf.ts"`, confFn: `async () => { plug({ plugRepo: "https://github.com/asdf-community/asdf-zig", @@ -215,7 +215,7 @@ await dockerTest([ // // big // { // name: "asdf-python", - // imports: `import plug from "$ghjk/plugs/asdf.ts"`, + // imports: `import port from "$ghjk/ports/asdf.ts"`, // confFn: `async () => { // plug({ // plugRepo: "https://github.com/asdf-community/asdf-python", diff --git a/core/logger.ts b/utils/logger.ts similarity index 81% rename from core/logger.ts rename to utils/logger.ts index 01329032..4322020a 100644 --- a/core/logger.ts +++ b/utils/logger.ts @@ -1,5 +1,6 @@ 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, ) { @@ -21,6 +22,25 @@ function formatter(lr: log.LogRecord) { 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, diff --git a/core/utils.ts b/utils/mod.ts similarity index 94% rename from core/utils.ts rename to utils/mod.ts index 2202a50f..73c95cd9 100644 --- a/core/utils.ts +++ b/utils/mod.ts @@ -4,7 +4,7 @@ import type { AsdfInstallConfig, DepShims, InstallConfig, - PlugDep, + PortDep, } from "../modules/ports/types.ts"; export function dbg(val: T) { logger().debug("inline", val); @@ -98,7 +98,7 @@ export function pathWithDepShims( } export function depBinShimPath( - dep: PlugDep, + dep: PortDep, binName: string, depShims: DepShims, ) { @@ -121,10 +121,15 @@ export function getInstallId(install: InstallConfig | AsdfInstallConfig) { const pluginId = `${url.hostname}-${url.pathname.replaceAll("/", ".")}`; return `asdf-${pluginId}`; } - return install.plugName; + return install.portName; } export const $ = dax.build$( {}, ); $.setPrintCommand(true); + +export function inWorker() { + return typeof WorkerGlobalScope !== "undefined" && + self instanceof WorkerGlobalScope; +} diff --git a/unarchive.ts b/utils/unarchive.ts similarity index 99% rename from unarchive.ts rename to utils/unarchive.ts index 6eba2f3b..5980ed70 100644 --- a/unarchive.ts +++ b/utils/unarchive.ts @@ -6,7 +6,7 @@ import { std_streams, std_tar, zipjs, -} from "./deps/plug.ts"; +} from "../deps/plug.ts"; /// Uses file extension to determine type /// Does not support symlinks From 324906d3abefd795977e252d0baa59219ff88500 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 2 Dec 2023 04:26:41 +0300 Subject: [PATCH 35/43] refactor: big un --- .pre-commit-config.yaml | 22 +- .vscode/settings.json | 2 +- LICENSE.md | 508 +++++++++++++++++------------------ README.md | 4 +- cli/cleanup.ts | 12 - cli/list.ts | 20 -- cli/mod.ts | 26 -- cli/outdated.ts | 12 - cli/sync.ts | 13 - deno.jsonc | 12 +- deps/{plug.ts => ports.ts} | 0 ghjk.ts | 13 +- host/deno.ts | 38 +-- host/mod.ts | 30 ++- host/types.ts | 2 +- install/mod.ts | 2 +- mod.ts | 43 +-- modules/mod.ts | 6 + modules/ports/asdf.ts | 8 +- modules/ports/mod.ts | 100 ++++--- modules/ports/std.ts | 32 +-- modules/ports/sync.ts | 70 ++--- modules/ports/types.ts | 117 ++++---- modules/ports/worker.ts | 18 +- modules/std.ts | 24 +- modules/tasks/mod.ts | 21 ++ modules/types.ts | 14 +- port.ts | 42 ++- ports/act.ts | 9 +- ports/cargo-binstall.ts | 9 +- ports/cargo-insta.ts | 9 +- ports/curl.ts | 5 +- ports/earthly.ts | 9 +- ports/git.ts | 5 +- ports/jco.ts | 11 +- ports/mold.ts | 9 +- ports/node.ts | 11 +- ports/pnpm.ts | 11 +- ports/protoc.ts | 9 +- ports/ruff.ts | 9 +- ports/tar.ts | 5 +- ports/unzip.ts | 5 +- ports/wasm-opt.ts | 9 +- ports/wasm-tools.ts | 9 +- ports/wasmedge.ts | 9 +- ports/whiz.ts | 9 +- tests/e2e.ts | 36 +-- tests/test.Dockerfile | 8 +- cli/utils.ts => utils/cli.ts | 9 +- utils/mod.ts | 7 +- utils/unarchive.ts | 2 +- 51 files changed, 740 insertions(+), 685 deletions(-) delete mode 100644 cli/cleanup.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 rename deps/{plug.ts => ports.ts} (100%) rename cli/utils.ts => utils/cli.ts (80%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6178832b..0eb641d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ 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: @@ -30,25 +30,21 @@ repos: - 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 + - id: deno-lint + name: Deno lint language: system - entry: bash -c 'cd website && [ -f node_modules/.bin/eslint ] && pnpm lint' + entry: bash -c 'deno task lint' pass_filenames: false - types_or: + types: - ts - - tsx - files: ^website/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 408ccda1..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, diff --git a/LICENSE.md b/LICENSE.md index a612ad98..82ffc403 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,28 +1,23 @@ -Mozilla Public License Version 2.0 -================================== +# Mozilla Public License Version 2.0 1. Definitions --------------- -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. +--- -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. +1.1. "Contributor" means each individual or legal entity that creates, +contributes to the creation of, or owns Covered Software. -1.3. "Contribution" - means Covered Software of a particular Contributor. +1.2. "Contributor Version" means the combination of the Contributions of others +(if any) used by a Contributor and that particular Contributor's Contribution. -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. +1.3. "Contribution" means Covered Software of a particular Contributor. -1.5. "Incompatible With Secondary Licenses" - means +1.4. "Covered Software" means Source Code Form to which the initial Contributor +has attached the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case including +portions thereof. + +1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or @@ -31,23 +26,18 @@ Mozilla Public License Version 2.0 version 1.1 or earlier of the License, but not also under the terms of a Secondary License. -1.6. "Executable Form" - means any form of the work other than Source Code Form. +1.6. "Executable Form" means any form of the work other than Source Code Form. -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. +1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. -1.8. "License" - means this document. +1.8. "License" means this document. -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. +1.9. "Licensable" means having the right to grant, to the maximum extent +possible, whether at the time of the initial grant or subsequently, any and all +of the rights conveyed by this License. -1.10. "Modifications" - means any of the following: +1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered @@ -56,318 +46,314 @@ Mozilla Public License Version 2.0 (b) any new file in Source Code Form that contains any Covered Software. -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. +1.11. "Patent Claims" of a Contributor means any patent claim(s), including +without limitation, method, process, and apparatus claims, in any patent +Licensable by such Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having made, import, +or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version +2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making +modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights +under this License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with You. For purposes of this +definition, "control" means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, or (b) +ownership of more than fifty percent (50%) of the outstanding shares or +beneficial ownership of such entity. 2. License Grants and Conditions --------------------------------- + +--- 2.1. Grants -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license: (a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and +Licensable by such Contributor to use, reproduce, make available, modify, +display, perform, distribute, and otherwise exploit its Contributions, either on +an unmodified basis, with Modifications, or as part of a Larger Work; and -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. +(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, +have made, import, and otherwise transfer either its Contributions or its +Contributor Version. 2.2. Effective Date -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. +The licenses granted in Section 2.1 with respect to any Contribution become +effective for each Contribution on the date the Contributor first distributes +such Contribution. 2.3. Limitations on Grant Scope -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: +The licenses granted in this Section 2 are the only rights granted under this +License. No additional rights or licenses will be implied from the distribution +or licensing of Covered Software under this License. Notwithstanding Section +2.1(b) above, no patent license is granted by a Contributor: -(a) for any code that a Contributor has removed from Covered Software; - or +(a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or +modifications of Covered Software, or (ii) the combination of its Contributions +with other software (except as part of its Contributor Version); or -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. +(c) under Patent Claims infringed by Covered Software in the absence of its +Contributions. -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). +This License does not grant any rights in the trademarks, service marks, or +logos of any Contributor (except as may be necessary to comply with the notice +requirements in Section 3.4). 2.4. Subsequent Licenses -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). +No Contributor makes additional grants as a result of Your choice to distribute +the Covered Software under a subsequent version of this License (see Section +10.2) or under the terms of a Secondary License (if permitted under the terms of +Section 3.3). 2.5. Representation -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. +Each Contributor represents that the Contributor believes its Contributions are +its original creation(s) or it has sufficient rights to grant the rights to its +Contributions conveyed by this License. 2.6. Fair Use -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. +This License is not intended to limit any rights You have under applicable +copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. 3. Responsibilities -------------------- + +--- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. +Modifications that You create or to which You contribute, must be under the +terms of this License. You must inform recipients that the Source Code Form of +the Covered Software is governed by the terms of this License, and how they can +obtain a copy of this License. You may not attempt to alter or restrict the +recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and +(a) such Covered Software must also be made available in Source Code Form, as +described in Section 3.1, and You must inform recipients of the Executable Form +how they can obtain a copy of such Source Code Form by reasonable means in a +timely manner, at a charge no more than the cost of distribution to the +recipient; and -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. +(b) You may distribute such Executable Form under the terms of this License, or +sublicense it under different terms, provided that the license for the +Executable Form does not attempt to limit or alter the recipients' rights in the +Source Code Form under this License. 3.3. Distribution of a Larger Work -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). +You may create and distribute a Larger Work under terms of Your choice, provided +that You also comply with the requirements of this License for the Covered +Software. If the Larger Work is a combination of Covered Software with a work +governed by one or more Secondary Licenses, and the Covered Software is not +Incompatible With Secondary Licenses, this License permits You to additionally +distribute such Covered Software under the terms of such Secondary License(s), +so that the recipient of the Larger Work may, at their option, further +distribute the Covered Software under the terms of either this License or such +Secondary License(s). 3.4. Notices -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. +You may not remove or alter the substance of any license notices (including +copyright notices, patent notices, disclaimers of warranty, or limitations of +liability) contained within the Source Code Form of the Covered Software, except +that You may alter any license notices to the extent required to remedy known +factual inaccuracies. 3.5. Application of Additional Terms -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Software. However, +You may do so only on Your own behalf, and not on behalf of any Contributor. You +must make it absolutely clear that any such warranty, support, indemnity, or +liability obligation is offered by You alone, and You hereby agree to indemnify +every Contributor for any liability incurred by such Contributor as a result of +warranty, support, indemnity or liability terms You offer. You may include +additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. + +--- + +If it is impossible for You to comply with any of the terms of this License with +respect to some or all of the Covered Software due to statute, judicial order, +or regulation then You must: (a) comply with the terms of this License to the +maximum extent possible; and (b) describe the limitations and the code they +affect. Such description must be placed in a text file included with all +distributions of the Covered Software under this License. Except to the extent +prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. 5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. + +--- + +5.1. The rights granted under this License will terminate automatically if You +fail to comply with any of its terms. However, if You become compliant, then the +rights granted under this License from a particular Contributor are reinstated +(a) provisionally, unless and until such Contributor explicitly and finally +terminates Your grants, and (b) on an ongoing basis, if such Contributor fails +to notify You of the non-compliance by some reasonable means prior to 60 days +after You have come back into compliance. Moreover, Your grants from a +particular Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the first +time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt of the +notice. 5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ +infringement claim (excluding declaratory judgment actions, counter-claims, and +cross-claims) alleging that a Contributor Version directly or indirectly +infringes any patent, then the rights granted to You by any and all Contributors +for the Covered Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user +license agreements (excluding distributors and resellers) which have been +validly granted by You or Your distributors under this License prior to +termination shall survive termination. + +--- + +- + - +- + 6. Disclaimer of Warranty * +- ------------------------- * +- + - +- Covered Software is provided under this License on an "as is" * +- basis, without warranty of any kind, either expressed, implied, or * +- statutory, including, without limitation, warranties that the * +- Covered Software is free of defects, merchantable, fit for a * +- particular purpose or non-infringing. The entire risk as to the * +- quality and performance of the Covered Software is with You. * +- Should any Covered Software prove defective in any respect, You * +- (not any Contributor) assume the cost of any necessary servicing, * +- repair, or correction. This disclaimer of warranty constitutes an * +- essential part of this License. No use of any Covered Software is * +- authorized under this License except under this disclaimer. * +- + - + +--- + +--- + +- + - +- + 7. Limitation of Liability * +- -------------------------- * +- + - +- Under no circumstances and under no legal theory, whether tort * +- (including negligence), contract, or otherwise, shall any * +- Contributor, or anyone who distributes Covered Software as * +- permitted above, be liable to You for any direct, indirect, * +- special, incidental, or consequential damages of any character * +- including, without limitation, damages for lost profits, loss of * +- goodwill, work stoppage, computer failure or malfunction, or any * +- and all other commercial damages or losses, even if such party * +- shall have been informed of the possibility of such damages. This * +- limitation of liability shall not apply to liability for death or * +- personal injury resulting from such party's negligence to the * +- extent applicable law prohibits such limitation. Some * +- jurisdictions do not allow the exclusion or limitation of * +- incidental or consequential damages, so this exclusion and * +- limitation may not apply to You. * +- + - + +--- 8. Litigation -------------- -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. +--- + +Any litigation relating to this License may be brought only in the courts of a +jurisdiction where the defendant maintains its principal place of business and +such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ----------------- -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. +--- + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it enforceable. +Any law or regulation which provides that the language of a contract shall be +construed against the drafter shall not be used to construe this License against +a Contributor. 10. Versions of the License ---------------------------- + +--- 10.1. New Versions -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. +Mozilla Foundation is the license steward. Except as provided in Section 10.3, +no one other than the license steward has the right to modify or publish new +versions of this License. Each version will be given a distinguishing version +number. 10.2. Effect of New Versions -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. +You may distribute the Covered Software under the terms of the version of the +License under which You originally received the Covered Software, or under the +terms of any subsequent version published by the license steward. 10.3. Modified Versions -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). +If you create software not governed by this License, and you want to create a +new license for such software, you may create and use a modified version of this +License if you rename the license and remove any references to the name of the +license steward (except to note that such modified license differs from this +License). -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. +If You choose to distribute Source Code Form that is Incompatible With Secondary +Licenses under the terms of this version of the License, the notice described in +Exhibit B of this License must be attached. -Exhibit A - Source Code Form License Notice -------------------------------------------- +## 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/. +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/. -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 -file in a relevant directory) where a recipient would be likely to look -for such a notice. +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 file in a relevant +directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- +## Exhibit B - "Incompatible With Secondary Licenses" Notice - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. +This Source Code Form is "Incompatible With Secondary Licenses", as defined by +the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index caebb109..d7102d0d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ghjk (jk) is a programmable runtime manager. Install the hooks: ```bash -deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/init.ts +deno run -A https://raw.githubusercontent.com/metatypedev/ghjk/main/install.ts ``` In your project, create a configuration file `ghjk.ts`: @@ -51,7 +51,7 @@ and looks as follows (abstracting away some implementation details): and clear previously loaded ones (if any) - defines an alias `ghjk` running `deno run -A $PWD/ghjk.ts` - you can then - - sync your runtime with `ghjk sync` which + - sync your runtime with `ghjk ports sync` which - installs the missing tools at `$HOME/.local/share/ghjk/installs` - regenerates the shims with symlinks and environment variables - detects any violation of the enforced rules diff --git a/cli/cleanup.ts b/cli/cleanup.ts deleted file mode 100644 index e6578314..00000000 --- a/cli/cleanup.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from "../deps/cli.ts"; - -export class CleanupCommand extends Command { - constructor() { - super(); - this - .description("") - .action(async () => { - console.log("cleanup"); - }); - } -} diff --git a/cli/list.ts b/cli/list.ts deleted file mode 100644 index 69a0291e..00000000 --- a/cli/list.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Command } from "../deps/cli.ts"; -import { GhjkCtx } from "../modules/ports/mod.ts"; - -export class ListCommand extends Command { - constructor( - public cx: GhjkCtx, - ) { - super(); - this - .description("") - .action(() => { - console.log( - cx.installs.map((install) => ({ - install, - plug: cx.ports.get(install.portName), - })), - ); - }); - } -} diff --git a/cli/mod.ts b/cli/mod.ts deleted file mode 100644 index 76523807..00000000 --- a/cli/mod.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Command, CommandResult, CompletionsCommand } from "../deps/cli.ts"; - -import { SyncCommand } from "./sync.ts"; -import { ListCommand } from "./list.ts"; -import { OutdatedCommand } from "./outdated.ts"; -import { CleanupCommand } from "./cleanup.ts"; -import { GhjkCtx } from "../modules/ports/mod.ts"; - -export function runCli( - args: string[], - cx: GhjkCtx, -): Promise { - return new Command() - .name("ghjk") - .version("0.1.0") - .description("Programmable runtime manager.") - .action(function () { - this.showHelp(); - }) - .command("sync", new SyncCommand(cx)) - .command("list", new ListCommand(cx)) - .command("outdated", new OutdatedCommand()) - .command("cleanup", new CleanupCommand()) - .command("completions", new CompletionsCommand()) - .parse(args); -} diff --git a/cli/outdated.ts b/cli/outdated.ts deleted file mode 100644 index 690a692a..00000000 --- a/cli/outdated.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from "../deps/cli.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 721cae36..00000000 --- a/cli/sync.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Command } from "../deps/cli.ts"; -import { GhjkCtx } from "../modules/ports/mod.ts"; -import { sync } from "../modules/ports/sync.ts"; -export class SyncCommand extends Command { - constructor( - public cx: GhjkCtx, - ) { - super(); - this - .description("Syncs the runtime.") - .action(() => sync(cx)); - } -} diff --git a/deno.jsonc b/deno.jsonc index 26fb7497..a883c5e4 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,7 +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/*" - } + "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/deps/plug.ts b/deps/ports.ts similarity index 100% rename from deps/plug.ts rename to deps/ports.ts diff --git a/ghjk.ts b/ghjk.ts index 20ff7615..75ec7916 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,6 +1,5 @@ export { ghjk } from "./mod.ts"; import node from "./ports/node.ts"; -import install from "./ports/wasmedge.ts"; import pnpm from "./ports/pnpm.ts"; import cargo_binstall from "./ports/cargo-binstall.ts"; import wasmedge from "./ports/wasmedge.ts"; @@ -17,7 +16,7 @@ import ruff from "./ports/ruff.ts"; import whiz from "./ports/whiz.ts"; // node({}); -wasmedge({}); +// wasmedge({}); // pnpm({}); // cargo_binstall({}); // wasm_tools({}); @@ -26,11 +25,11 @@ wasmedge({}); // jco({}); // mold({}); // act({}); -asdf({ - plugRepo: "https://github.com/asdf-community/asdf-python", - installType: "version", -}); -// protoc({}); +// asdf({ +// pluginRepo: "https://github.com/asdf-community/asdf-python", +// installType: "version", +// }); +protoc({}); // earthly({}); // ruff({}); // whiz({}); diff --git a/host/deno.ts b/host/deno.ts index de9591d3..56dd4267 100644 --- a/host/deno.ts +++ b/host/deno.ts @@ -7,7 +7,7 @@ import { std_url } from "../deps/common.ts"; import { inWorker } from "../utils/mod.ts"; import logger, { setup as setupLogger } from "../utils/logger.ts"; -import type { GhjkConfig } from "../modules/ports/mod.ts"; +import type { PortsModuleConfigBase } from "../modules/ports/mod.ts"; if (inWorker()) { initWorker(); @@ -15,14 +15,14 @@ if (inWorker()) { declare global { interface Window { - ghjk: GhjkConfig; + ports: PortsModuleConfigBase; } } function initWorker() { setupLogger(); - self.ghjk = { - ports: new Map(), + self.ports = { + ports: {}, installs: [], }; @@ -58,7 +58,8 @@ async function onMsg(msg: MessageEvent) { async function serializeConfig(uri: string) { const mod = await import(uri); - return JSON.parse(JSON.stringify(mod)); + const config = mod.ghjk.getConfig(mod.secureConfig); + return JSON.parse(JSON.stringify(config)); } export async function getSerializedConfig(configUri: string) { @@ -78,19 +79,20 @@ async function rpc(moduleUri: string, req: DriverRequests) { const worker = new Worker(import.meta.url, { name: `${dirBaseName}/${baseName}`, type: "module", - deno: { - namespace: true, - permissions: { - sys: true, - net: true, - read: ["."], - hrtime: false, - write: false, - run: false, - ffi: false, - env: true, - } as Deno.PermissionOptions, - }, + // 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) => { diff --git a/host/mod.ts b/host/mod.ts index f9a24967..de81d637 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -1,15 +1,16 @@ 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 { $ } from "../utils/mod.ts"; -import validators, { type SerializedConfig } from "./types.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 configPath = Deno.args[0]; + const configPath = Deno.args[0]; // FIXME: might be better to get this from env var logger().debug("config", configPath); @@ -33,7 +34,28 @@ export async function main() { ); } - console.log(serializedJson); + // console.log(JSON.stringify(serializedJson, null, " ")); + + const serializedConfig = validators.serializedConfig.parse(serializedJson); + + let cmd: cliffy_cmd.Command = new cliffy_cmd.Command() + .name("ghjk") + .version("0.1.0") // FIXME: get better version + .description("Programmable runtime manager.") + .action(function () { + this.showHelp(); + }); + 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(man); + cmd = cmd.command(man.id, instance.command()); + } + cmd + .command("completions", new cliffy_cmd.CompletionsCommand()) + .parse(Deno.args.slice(1)); // const serializedConfig = validators.serializedConfig.parse( // serializedJson, // ); diff --git a/host/types.ts b/host/types.ts index aa18e608..73aeb410 100644 --- a/host/types.ts +++ b/host/types.ts @@ -3,7 +3,7 @@ import moduleValidators from "../modules/types.ts"; const serializedConfig = zod.object( { - modules: zod.array(moduleValidators.module), + modules: zod.array(moduleValidators.moduleManifest), }, ); diff --git a/install/mod.ts b/install/mod.ts index bb35a016..412c6aa4 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -1,5 +1,5 @@ import { std_path } from "../deps/cli.ts"; -import { dirs, importRaw } from "../cli/utils.ts"; +import { dirs, importRaw } from "../utils/cli.ts"; import { spawnOutput } from "../utils/mod.ts"; let LD_LIBRARY_ENV: string; diff --git a/mod.ts b/mod.ts index a2f4faf3..3c2f2857 100644 --- a/mod.ts +++ b/mod.ts @@ -3,12 +3,15 @@ import "./setup_logger.ts"; -import { type GhjkConfig } from "./modules/ports/types.ts"; -// this is only a shortcut for the cli -import { runCli } from "./cli/mod.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 { GhjkSecureConfig } from "./port.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. @@ -16,38 +19,42 @@ import * as std_ports from "./modules/ports/std.ts"; // as ports might import a different version of this module. declare global { interface Window { - ghjk: GhjkConfig; + ports: PortsModuleConfigBase; } } -function runCliShim( - args: string[], - secureConfig: GhjkSecureConfig | undefined, -) { +function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { let allowedDeps; if (secureConfig?.allowedPortDeps) { - allowedDeps = new Map(); + allowedDeps = {} as RegisteredPorts; for (const depId of secureConfig.allowedPortDeps) { - const regPort = std_ports.map.get(depId.id); + const regPort = std_ports.map[depId.id]; if (!regPort) { throw new Error( `unrecognized dep "${depId.id}" found in "allowedPluginDeps"`, ); } - allowedDeps.set(depId.id, regPort); + allowedDeps[depId.id] = regPort; } } else { - allowedDeps = new Map(std_ports.map.entries()); + allowedDeps = std_ports.map; } - runCli(args, { - ...self.ghjk, - allowedDeps, - }); + 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({ - runCli: Object.freeze(runCliShim), + getConfig: Object.freeze(getConfig), }); export { logger }; diff --git a/modules/mod.ts b/modules/mod.ts index e69de29b..849201d9 100644 --- a/modules/mod.ts +++ 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/asdf.ts b/modules/ports/asdf.ts index 8866d1c5..b88637be 100644 --- a/modules/ports/asdf.ts +++ b/modules/ports/asdf.ts @@ -6,6 +6,7 @@ import { type ListAllArgs, type ListBinPathsArgs, PortBase, + type TheAsdfPortManifest, } from "./types.ts"; import { depBinShimPath, @@ -27,11 +28,14 @@ const git_aa_id = { id: "git@aa", }; -export const manifest = { +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 { @@ -60,7 +64,7 @@ export class AsdfPort extends PortBase { [ depBinShimPath(git_aa_id, "git", depShims), "clone", - installConfig.plugRepo, + installConfig.pluginRepo, "--depth", "1", tmpCloneDirPath, diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 5cd3d5d1..a68c9ac2 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -1,62 +1,94 @@ export * from "./types.ts"; + +import { cliffy_cmd } from "../../deps/cli.ts"; import { semver } from "../../deps/common.ts"; import validators, { type AmbientAccessPortManifest, type DenoWorkerPortManifest, - type GhjkConfig, type InstallConfig, - type RegisteredPort, + type PortManifest, + type PortsModuleConfig, + type PortsModuleConfigBase, } from "./types.ts"; import logger from "../../utils/logger.ts"; +import { ModuleBase } from "../mod.ts"; +import { sync } from "./sync.ts"; -export const Ghjk = { - cwd: Deno.cwd, -}; +export class PortsModule extends ModuleBase { + constructor(public config: PortsModuleConfig) { + super(); + } + command() { + return new cliffy_cmd.Command() + .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.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 registerDenoPlug( - cx: GhjkConfig, - manifestUnclean: DenoWorkerPortManifest, +export function registerDenoPort( + cx: PortsModuleConfigBase, + manifest: DenoWorkerPortManifest, ) { - const manifest = validators.denoWorkerPortManifest.parse(manifestUnclean); - registerPlug(cx, { ty: "denoWorker", manifest }); + registerPort(cx, manifest); } -export function registerAmbientPlug( - cx: GhjkConfig, - manifestUnclean: AmbientAccessPortManifest, +export function registerAmbientPort( + cx: PortsModuleConfigBase, + manifest: AmbientAccessPortManifest, ) { - const manifest = validators.ambientAccessPortManifest.parse(manifestUnclean); - registerPlug(cx, { ty: "ambientAccess", manifest }); + registerPort(cx, manifest); } -export function registerPlug( - cx: GhjkConfig, - plug: RegisteredPort, +export function registerPort( + cx: PortsModuleConfigBase, + manifestUnclean: PortManifest, ) { - const { manifest } = plug; - const conflict = cx.ports.get(manifest.name)?.manifest; + 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 plugin "${manifest.name}" found with ` + + `Two instances of port "${manifest.name}" found with ` + `both set to "${manifest.conflictResolution}" conflictResolution"`, ); } else if (conflict.conflictResolution == "override") { - logger().debug("plug rejected due to override", { + logger().debug("port rejected due to override", { retained: conflict, rejected: manifest, }); // do nothing } else if (manifest.conflictResolution == "override") { - logger().debug("plug override", { + logger().debug("port override", { new: manifest, replaced: conflict, }); - cx.ports.set(manifest.name, plug); + cx.ports[manifest.name] = manifest; } else if ( semver.compare( semver.parse(manifest.version), @@ -64,7 +96,7 @@ export function registerPlug( ) == 0 ) { throw new Error( - `Two instances of the plug "${manifest.name}" found with an identical version` + + `Two instances of the port "${manifest.name}" found with an identical version` + `and both set to "deferToNewer" conflictResolution.`, ); } else if ( @@ -73,34 +105,34 @@ export function registerPlug( semver.parse(conflict.version), ) > 0 ) { - logger().debug("plug replaced after version defer", { + logger().debug("port replaced after version defer", { new: manifest, replaced: conflict, }); - cx.ports.set(manifest.name, plug); + cx.ports[manifest.name] = manifest; } else { - logger().debug("plug rejected due after defer", { + logger().debug("port rejected due after defer", { retained: conflict, rejected: manifest, }); } } else { - logger().debug("plug registered", manifest.name); - cx.ports.set(manifest.name, plug); + logger().debug("port registered", manifest.name); + cx.ports[manifest.name] = manifest; } } export function addInstall( - cx: GhjkConfig, + cx: PortsModuleConfigBase, config: InstallConfig, ) { - if (!cx.ports.has(config.portName)) { + if (!cx.ports[config.portName]) { throw new Error( - `unrecognized plug "${config.portName}" specified by install ${ + `unrecognized port "${config.portName}" specified by install ${ JSON.stringify(config) }`, ); } logger().debug("install added", config); - cx.installs.push(config); + cx.installs.push(validators.installConfig.parse(config)); } diff --git a/modules/ports/std.ts b/modules/ports/std.ts index 2167b011..ba211466 100644 --- a/modules/ports/std.ts +++ b/modules/ports/std.ts @@ -1,6 +1,6 @@ -//! This plugin exports the list of standard plugins other +//! This plugin exports the list of standard ports other //! plugins are allowed to depend on. -import validators, { PortDep, RegisteredPort } from "./types.ts"; +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"; @@ -9,32 +9,28 @@ 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: RegisteredPort[] = [ +const aaPorts: PortManifest[] = [ man_tar_aa, man_git_aa, man_curl_aa, man_unzip_aa, -] - .map((man) => ({ - ty: "ambientAccess", - manifest: validators.ambientAccessPortManifest.parse(man), - })); +]; -const denoPlugs: RegisteredPort[] = [ +const denoPorts: PortManifest[] = [ man_cbin_ghrel, man_node_org, man_pnpm_ghrel, -] - .map((man) => ({ - ty: "denoWorker", - manifest: validators.denoWorkerPortManifest.parse(man), - })); +]; export const map = Object.freeze( - new Map([ - ...aaPorts, - ...denoPlugs, - ].map((plug) => [plug.manifest.name, plug])), + Object.fromEntries( + [ + ...aaPorts, + ...denoPorts, + ] + .map((man) => validators.portManifest.parse(man)) + .map((man) => [man.name, man]), + ), ); export const tar_aa = Object.freeze({ diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 57b6568f..10d86b16 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -4,14 +4,14 @@ import validators, { type AmbientAccessPortManifestX, type DenoWorkerPortManifestX, type DepShims, - type GhjkCtx, type InstallConfig, InstallConfigX, type PortArgsBase, - type RegisteredPort, + type PortManifestX, + type PortsModuleConfig, } from "./types.ts"; import { DenoWorkerPort } from "./worker.ts"; -import { AVAIL_CONCURRENCY, dirs } from "../../cli/utils.ts"; +import { AVAIL_CONCURRENCY, dirs } from "../../utils/cli.ts"; import { AmbientAccessPort } from "./ambient.ts"; import { AsdfPort } from "./asdf.ts"; import { getInstallId } from "../../utils/mod.ts"; @@ -54,13 +54,13 @@ async function writeLoader(envDir: string, env: Record) { ); } -export async function sync(cx: GhjkCtx) { +export async function sync(cx: PortsModuleConfig) { const config = await findConfig(Deno.cwd()); if (!config) { logger().error("ghjk did not find any `ghjk.ts` config."); return; } - logger().debug("syncnig", config); + logger().debug("syncing"); const envDir = envDirFromConfig(config); logger().debug({ envDir }); @@ -72,9 +72,8 @@ export async function sync(cx: GhjkCtx) { const installId = pendingInstalls.pop()!; const inst = installs.all.get(installId)!; - const regPort = cx.ports.get(inst.portName) ?? - cx.allowedDeps.get(inst.portName)!; - const { manifest } = regPort; + const manifest = cx.ports[inst.portName] ?? + cx.allowedDeps[inst.portName]!; const depShims: DepShims = {}; // create the shims for the deps @@ -82,9 +81,9 @@ export async function sync(cx: GhjkCtx) { prefix: `ghjk_dep_shims_${installId}_`, }); for (const depId of manifest.deps ?? []) { - const depPort = cx.allowedDeps.get(depId.id)!; + const depPort = cx.allowedDeps[depId.id]!; const depInstall = { - portName: depPort.manifest.name, + portName: depPort.name, }; const depInstallId = getInstallId(depInstall); const depArtifacts = artifacts.get(depInstallId); @@ -107,7 +106,7 @@ export async function sync(cx: GhjkCtx) { let thisArtifacts; try { - thisArtifacts = await doInstall(envDir, inst, regPort, depShims); + thisArtifacts = await doInstall(envDir, inst, manifest, depShims); } catch (err) { throw new Error(`error installing ${installId}`, { cause: err }); } @@ -190,7 +189,7 @@ export async function sync(cx: GhjkCtx) { ), ); } -function buildInstallGraph(cx: GhjkCtx) { +function buildInstallGraph(cx: PortsModuleConfig) { const installs = { all: new Map(), indie: [] as string[], @@ -213,9 +212,9 @@ function buildInstallGraph(cx: GhjkCtx) { while (foundInstalls.length > 0) { const inst = foundInstalls.pop()!; - const regPort = cx.ports.get(inst.portName) ?? - cx.allowedDeps.get(inst.portName); - if (!regPort) { + 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) @@ -233,20 +232,19 @@ function buildInstallGraph(cx: GhjkCtx) { installs.all.set(installId, inst); - const { manifest } = regPort; if (!manifest.deps || manifest.deps.length == 0) { installs.indie.push(installId); } else { const deps = []; for (const depId of manifest.deps) { - const depPort = cx.allowedDeps.get(depId.id); + const depPort = cx.allowedDeps[depId.id]; if (!depPort) { throw new Error( `unrecognized dependency "${depId.id}" specified by plug "${manifest.name}"`, ); } const depInstall = { - portName: depPort.manifest.name, + portName: depPort.name, }; const depInstallId = getInstallId(depInstall); @@ -323,34 +321,36 @@ type InstallArtifacts = DePromisify>; async function doInstall( envDir: string, instUnclean: InstallConfig, - regPort: RegisteredPort, + manifest: PortManifestX, depShims: DepShims, ) { - const { ty: plugType, manifest } = regPort; - let plug; + logger().debug("installing", { envDir, instUnclean, port: manifest }); + let port; let inst: InstallConfigX; - if (plugType == "denoWorker") { + if (manifest.ty == "denoWorker") { inst = validators.installConfig.parse(instUnclean); - plug = new DenoWorkerPort( + port = new DenoWorkerPort( manifest as DenoWorkerPortManifestX, ); - } else if (plugType == "ambientAccess") { + } else if (manifest.ty == "ambientAccess") { inst = validators.installConfig.parse(instUnclean); - plug = new AmbientAccessPort( + port = new AmbientAccessPort( manifest as AmbientAccessPortManifestX, ); - } else if (plugType == "asdf") { + } else if (manifest.ty == "asdf") { const asdfInst = validators.asdfInstallConfig.parse(instUnclean); inst = asdfInst; - plug = await AsdfPort.init(envDir, asdfInst, depShims); + port = await AsdfPort.init(envDir, asdfInst, depShims); } else { throw new Error( - `unsupported plugin type "${plugType}": ${JSON.stringify(manifest)}`, + `unsupported plugin type "${(manifest as unknown as any).ty}": ${ + JSON.stringify(manifest) + }`, ); } const installId = getInstallId(inst); const installVersion = validators.string.parse( - inst.version ?? await plug.latestStable({ + inst.version ?? await port.latestStable({ depShims, }), ); @@ -379,7 +379,7 @@ async function doInstall( const tmpDirPath = await Deno.makeTempDir({ prefix: `ghjk_download_${installId}:${installVersion}_`, }); - await plug.download({ + await port.download({ ...baseArgs, downloadPath: downloadPath, tmpDirPath, @@ -391,7 +391,7 @@ async function doInstall( const tmpDirPath = await Deno.makeTempDir({ prefix: `ghjk_install_${installId}@${installVersion}_`, }); - await plug.install({ + await port.install({ ...baseArgs, availConcurrency: AVAIL_CONCURRENCY, downloadPath: downloadPath, @@ -400,22 +400,22 @@ async function doInstall( void Deno.remove(tmpDirPath, { recursive: true }); } const binPaths = validators.stringArray.parse( - await plug.listBinPaths({ + await port.listBinPaths({ ...baseArgs, }), ); const libPaths = validators.stringArray.parse( - await plug.listLibPaths({ + await port.listLibPaths({ ...baseArgs, }), ); const includePaths = validators.stringArray.parse( - await plug.listIncludePaths({ + await port.listIncludePaths({ ...baseArgs, }), ); const env = zod.record(zod.string()).parse( - await plug.execEnv({ + await port.execEnv({ ...baseArgs, }), ); diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 0f167373..4466bf52 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -9,6 +9,7 @@ const portDep = zod.object({ }); const portManifestBase = zod.object({ + ty: zod.string(), name: zod.string().min(1), version: zod.string() .refine((str) => semver.parse(str), { @@ -23,12 +24,14 @@ const portManifestBase = zod.object({ 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", @@ -49,6 +52,18 @@ const ambientAccessPortManifest = portManifestBase.merge( // 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() @@ -57,22 +72,34 @@ const installConfigBase = zod.object({ .enum(["deferToNewer", "override"]) .nullish() .default("deferToNewer"), + portName: zod.string().min(1), }).passthrough(); -const installConfig = installConfigBase.merge( - zod.object({ - portName: zod.string().min(1), - }), -); +const stdInstallConfig = installConfigBase.merge(zod.object({})); -const asdfInstallConfig = installConfig.merge( +const asdfInstallConfig = installConfigBase.merge( zod.object({ - plugRepo: zod.string().url(), + pluginRepo: zod.string().url(), installType: zod .enum(["version", "ref"]), }), ); +const installConfig = zod.union([stdInstallConfig, asdfInstallConfig]); + +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, @@ -80,8 +107,14 @@ const validators = { ambientAccessPortManifest, string: zod.string(), installConfigBase, + stdInstallConfig, installConfig, asdfInstallConfig, + portManifest, + portsModuleConfigBase, + portsModuleSecureConfig, + portsModuleConfig, + theAsdfPortManifest, stringArray: zod.string().min(1).array(), }; export default validators; @@ -96,14 +129,15 @@ export type DenoWorkerPortManifest = zod.input< export type AmbientAccessPortManifest = zod.input< typeof validators.ambientAccessPortManifest >; +export type TheAsdfPortManifest = zod.input< + typeof validators.theAsdfPortManifest +>; // Describes the plugin itself -export type PortManifest = - | PortManifestBase - | DenoWorkerPortManifest - | AmbientAccessPortManifest; +export type PortManifest = zod.input< + typeof validators.portManifest +>; -export type PortDep = zod.infer; export type PortManifestBaseX = zod.infer; export type DenoWorkerPortManifestX = zod.infer< typeof validators.denoWorkerPortManifest @@ -112,49 +146,39 @@ export type AmbientAccessPortManifestX = zod.infer< typeof validators.ambientAccessPortManifest >; // This is the transformed version of PortManifest, ready for consumption -export type PortManifestX = - | PortManifestBaseX - | DenoWorkerPortManifestX - | AmbientAccessPortManifestX; - -export type RegisteredPort = { - ty: "ambientAccess"; - manifest: AmbientAccessPortManifestX; -} | { - ty: "denoWorker"; - manifest: DenoWorkerPortManifestX; -} | { - ty: "asdf"; - manifest: PortManifestBaseX; -}; +export type PortManifestX = zod.infer< + typeof validators.portManifest +>; -export type RegisteredPorts = Map; +export type PortDep = zod.infer; -export type InstallConfigBase = zod.input; +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 AsdfInstallConfig = zod.input; -export type AsdfInstallConfigX = zod.infer; -export interface GhjkConfig { - /// Ports explicitly added by the user - ports: RegisteredPorts; - installs: InstallConfig[]; -} +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 interface GhjkSecureConfig { - allowedPortDeps?: PortDep[]; -} +export type PortsModuleSecureConfig = zod.infer< + typeof validators.portsModuleSecureConfig +>; -export type GhjkCtx = GhjkConfig & { - /// Standard list of ports allowed to be use as deps by other ports - allowedDeps: RegisteredPorts; -}; +export type PortsModuleConfig = zod.infer; export abstract class PortBase { abstract manifest: PortManifest; @@ -242,11 +266,8 @@ export interface ListAllArgs { depShims: DepShims; } -export interface ListBinPathsArgs extends PortArgsBase { -} - -export interface ExecEnvArgs extends PortArgsBase { -} +export type ListBinPathsArgs = PortArgsBase; +export type ExecEnvArgs = PortArgsBase; export interface DownloadArgs extends PortArgsBase { downloadPath: string; diff --git a/modules/ports/worker.ts b/modules/ports/worker.ts index 2594b39c..e1e54a69 100644 --- a/modules/ports/worker.ts +++ b/modules/ports/worker.ts @@ -73,7 +73,7 @@ type WorkerResp = { /// Make sure to call this before any `await` point or your /// plug might miss messages -export function initDenoWorkerPlug

(plugInit: () => P) { +export function initDenoWorkerPort

(portCtor: () => P) { if (inWorker()) { // let plugClass: (new () => PlugBase) | undefined; // const plugInit = () => { @@ -105,40 +105,40 @@ export function initDenoWorkerPlug

(plugInit: () => P) { res = { ty: req.ty, // id: req.id, - payload: await plugInit().listAll(req.arg), + payload: await portCtor().listAll(req.arg), }; } else if (req.ty === "latestStable") { res = { ty: req.ty, - payload: await plugInit().latestStable(req.arg), + payload: await portCtor().latestStable(req.arg), }; } else if (req.ty === "execEnv") { res = { ty: req.ty, - payload: await plugInit().execEnv(req.arg), + payload: await portCtor().execEnv(req.arg), }; } else if (req.ty === "listBinPaths") { res = { ty: req.ty, - payload: await plugInit().listBinPaths(req.arg), + payload: await portCtor().listBinPaths(req.arg), }; } else if (req.ty === "listLibPaths") { res = { ty: req.ty, - payload: await plugInit().listLibPaths(req.arg), + payload: await portCtor().listLibPaths(req.arg), }; } else if (req.ty === "listIncludePaths") { res = { ty: req.ty, - payload: await plugInit().listIncludePaths(req.arg), + payload: await portCtor().listIncludePaths(req.arg), }; } else if (req.ty === "download") { - await plugInit().download(req.arg), + await portCtor().download(req.arg), res = { ty: req.ty, }; } else if (req.ty === "install") { - await plugInit().install(req.arg), + await portCtor().install(req.arg), res = { ty: req.ty, }; diff --git a/modules/std.ts b/modules/std.ts index 0aeb6c3f..e7f0a3ab 100644 --- a/modules/std.ts +++ b/modules/std.ts @@ -1 +1,23 @@ -export const map = new Map(); +import { PortsModule } from "./ports/mod.ts"; +import portsValidators from "./ports/types.ts"; +import { type ModuleManifest } from "./types.ts"; + +export const ports = "ports"; + +export const tasks = "tasks"; + +export const map = { + [ports as string]: { + ctor: (manifest: ModuleManifest) => + new PortsModule( + portsValidators.portsModuleConfig.parse(manifest.config), + ), + }, + [tasks as string]: { + // TODO: impl tasks module + ctor: (manifest: ModuleManifest) => + new PortsModule( + portsValidators.portsModuleConfig.parse(manifest.config), + ), + }, +}; diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index e69de29b..c4d982b7 100644 --- a/modules/tasks/mod.ts +++ 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 index 5be9a484..651b2ce2 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -1,11 +1,17 @@ import { zod } from "../deps/common.ts"; -const module = zod.object({ - id: zod.string(), +// FIXME: better module ident/versioning +const moduleId = zod.string(); + +const moduleManifest = zod.object({ + id: moduleId, + config: zod.unknown(), }); -export type Module = zod.infer; +export type ModuleId = zod.infer; +export type ModuleManifest = zod.infer; export default { - module, + moduleManifest, + moduleId, }; diff --git a/port.ts b/port.ts index 6d67c581..1106dea0 100644 --- a/port.ts +++ b/port.ts @@ -5,25 +5,24 @@ import { type AmbientAccessPortManifest, type DenoWorkerPortManifest, type DownloadArgs, - type GhjkConfig, type InstallConfig, type PortBase, - registerAmbientPlug, - registerDenoPlug, - registerPlug, + type PortsModuleConfigBase, + registerAmbientPort, + registerDenoPort, + registerPort, } from "./modules/ports/mod.ts"; -import validators from "./modules/ports/types.ts"; -import { log, std_fs, std_path, std_url } from "./deps/plug.ts"; -import { initDenoWorkerPlug } from "./modules/ports/worker.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/plug.ts"; +export * from "./deps/ports.ts"; export { default as logger } from "./utils/logger.ts"; -export { initDenoWorkerPlug } from "./modules/ports/worker.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"; @@ -36,39 +35,36 @@ 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 - ghjk: GhjkConfig; + ports: PortsModuleConfigBase; } } function isInConfig() { - return !!self.ghjk; + return !!self.ports; } -export function registerDenoPlugGlobal

( - manifestUnclean: DenoWorkerPortManifest, - plugInit: () => P, +export function registerDenoPortGlobal

( + manifest: DenoWorkerPortManifest, + portCtor: () => P, ) { if (isInConfig()) { - registerDenoPlug(self.ghjk, manifestUnclean); + registerDenoPort(self.ports, manifest); } else if (inWorker()) { - initDenoWorkerPlug(plugInit); + initDenoWorkerPort(portCtor); } } export function registerAsdfPort() { if (isInConfig()) { - registerPlug(self.ghjk, { - ty: "asdf", - manifest: validators.portManifestBase.parse(asdf.manifest), - }); + registerPort(self.ports, asdf.manifest); } } -export function registerAmbientPlugGlobal( +export function registerAmbientPortGlobal( manifestUnclean: AmbientAccessPortManifest, ) { if (isInConfig()) { - registerAmbientPlug(self.ghjk, manifestUnclean); + registerAmbientPort(self.ports, manifestUnclean); } } @@ -76,7 +72,7 @@ export function addInstallGlobal( config: InstallConfig, ) { if (isInConfig()) { - addInstall(self.ghjk, config); + addInstall(self.ports, config); } } diff --git a/ports/act.ts b/ports/act.ts index eacc8416..d2bfbe1a 100644 --- a/ports/act.ts +++ b/ports/act.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; const manifest = { + ty: "denoWorker" as const, name: "act@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts index 03126827..e1ef6d24 100644 --- a/ports/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; export const manifest = { + ty: "denoWorker" as const, name: "cargo-binstall@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/cargo-insta.ts b/ports/cargo-insta.ts index c86047f6..1d184c52 100644 --- a/ports/cargo-insta.ts +++ b/ports/cargo-insta.ts @@ -3,10 +3,10 @@ import { depBinShimPath, type DownloadArgs, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, logger, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -15,6 +15,7 @@ import { 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, @@ -23,9 +24,9 @@ const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/curl.ts b/ports/curl.ts index f9ca1e8e..0b7f3753 100644 --- a/ports/curl.ts +++ b/ports/curl.ts @@ -1,10 +1,11 @@ import { addInstallGlobal, type AmbientAccessPortManifest, - registerAmbientPlugGlobal, + registerAmbientPortGlobal, } from "../port.ts"; export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, name: "curl@aa", version: "0.1.0", execName: "curl", @@ -13,7 +14,7 @@ export const manifest: AmbientAccessPortManifest = { versionExtractRegexFlags: "", }; -registerAmbientPlugGlobal(manifest); +registerAmbientPortGlobal(manifest); export default function install() { addInstallGlobal({ portName: manifest.name, diff --git a/ports/earthly.ts b/ports/earthly.ts index 63ee110d..3ac4b167 100644 --- a/ports/earthly.ts +++ b/ports/earthly.ts @@ -3,24 +3,25 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + 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, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/git.ts b/ports/git.ts index e54c6fff..c6bad39d 100644 --- a/ports/git.ts +++ b/ports/git.ts @@ -1,10 +1,11 @@ import { addInstallGlobal, type AmbientAccessPortManifest, - registerAmbientPlugGlobal, + registerAmbientPortGlobal, } from "../port.ts"; export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, name: "git@aa", version: "0.1.0", execName: "git", @@ -13,7 +14,7 @@ export const manifest: AmbientAccessPortManifest = { versionExtractRegexFlags: "", }; -registerAmbientPlugGlobal(manifest); +registerAmbientPortGlobal(manifest); export default function git() { addInstallGlobal({ portName: manifest.name, diff --git a/ports/jco.ts b/ports/jco.ts index 37292b6d..d13886d6 100644 --- a/ports/jco.ts +++ b/ports/jco.ts @@ -4,12 +4,12 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, ListAllArgs, pathWithDepShims, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -21,6 +21,7 @@ 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, @@ -28,12 +29,12 @@ const manifest = { std_ports.node_org, ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install({ version }: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, - version, + ...config, }); // FIXME: conflict flags for install configs node({}); diff --git a/ports/mold.ts b/ports/mold.ts index 03388818..f0ae0e3a 100644 --- a/ports/mold.ts +++ b/ports/mold.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; const manifest = { + ty: "denoWorker" as const, name: "mold@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/node.ts b/ports/node.ts index 9267e3bb..0d133016 100644 --- a/ports/node.ts +++ b/ports/node.ts @@ -5,11 +5,11 @@ import { downloadFile, ExecEnvArgs, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, ListAllArgs, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -24,6 +24,7 @@ const tar_aa_id = { // 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, @@ -32,13 +33,13 @@ export const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); // FIXME: improve multi platform support story -export default function install({ version }: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, - version, + ...config, }); } diff --git a/ports/pnpm.ts b/ports/pnpm.ts index 3a706baa..41b58a6d 100644 --- a/ports/pnpm.ts +++ b/ports/pnpm.ts @@ -3,11 +3,11 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, ListAllArgs, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,16 +15,17 @@ import { } from "../port.ts"; export const manifest = { + ty: "denoWorker" as const, name: "pnpm@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install({ version }: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, - version, + ...config, }); } diff --git a/ports/protoc.ts b/ports/protoc.ts index 98be5b7c..cb81281f 100644 --- a/ports/protoc.ts +++ b/ports/protoc.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; const manifest = { + ty: "denoWorker" as const, name: "protoc@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/ruff.ts b/ports/ruff.ts index 47d57004..2455529d 100644 --- a/ports/ruff.ts +++ b/ports/ruff.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; const manifest = { + ty: "denoWorker" as const, name: "ruff@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/tar.ts b/ports/tar.ts index 1fc3ce42..d6825d19 100644 --- a/ports/tar.ts +++ b/ports/tar.ts @@ -1,10 +1,11 @@ import { addInstallGlobal, type AmbientAccessPortManifest, - registerAmbientPlugGlobal, + registerAmbientPortGlobal, } from "../port.ts"; export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, name: "tar@aa", version: "0.1.0", execName: "tar", @@ -13,7 +14,7 @@ export const manifest: AmbientAccessPortManifest = { versionExtractRegexFlags: "", }; -registerAmbientPlugGlobal(manifest); +registerAmbientPortGlobal(manifest); export default function install() { addInstallGlobal({ portName: manifest.name, diff --git a/ports/unzip.ts b/ports/unzip.ts index 24e06fbe..b7a617c6 100644 --- a/ports/unzip.ts +++ b/ports/unzip.ts @@ -1,10 +1,11 @@ import { addInstallGlobal, type AmbientAccessPortManifest, - registerAmbientPlugGlobal, + registerAmbientPortGlobal, } from "../port.ts"; export const manifest: AmbientAccessPortManifest = { + ty: "ambientAccess" as const, name: "unzip@aa", version: "0.1.0", execName: "unzip", @@ -13,7 +14,7 @@ export const manifest: AmbientAccessPortManifest = { versionExtractRegexFlags: "", }; -registerAmbientPlugGlobal(manifest); +registerAmbientPortGlobal(manifest); export default function install() { addInstallGlobal({ portName: manifest.name, diff --git a/ports/wasm-opt.ts b/ports/wasm-opt.ts index bcd37e57..95f5bbe0 100644 --- a/ports/wasm-opt.ts +++ b/ports/wasm-opt.ts @@ -3,10 +3,10 @@ import { depBinShimPath, type DownloadArgs, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, logger, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -15,6 +15,7 @@ import { 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, @@ -23,9 +24,9 @@ const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/wasm-tools.ts b/ports/wasm-tools.ts index 10abde8b..6f5fe833 100644 --- a/ports/wasm-tools.ts +++ b/ports/wasm-tools.ts @@ -3,10 +3,10 @@ import { depBinShimPath, type DownloadArgs, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, logger, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -15,6 +15,7 @@ import { 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, @@ -23,9 +24,9 @@ const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/wasmedge.ts b/ports/wasmedge.ts index 9fe539d6..7fc779da 100644 --- a/ports/wasmedge.ts +++ b/ports/wasmedge.ts @@ -5,10 +5,10 @@ import { downloadFile, ExecEnvArgs, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, spawn, std_fs, @@ -18,6 +18,7 @@ import { 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, @@ -26,7 +27,7 @@ const manifest = { ], }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); // TODO: wasmedge extension and plugin support /* @@ -51,7 +52,7 @@ const supportedPlugins = [ "wasmedge_bpf" as const, ]; */ -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/ports/whiz.ts b/ports/whiz.ts index a697b9e0..ab68f32e 100644 --- a/ports/whiz.ts +++ b/ports/whiz.ts @@ -3,10 +3,10 @@ import { DownloadArgs, downloadFile, InstallArgs, - type InstallConfigBase, + type InstallConfigSimple, type PlatformInfo, PortBase, - registerDenoPlugGlobal, + registerDenoPortGlobal, removeFile, std_fs, std_path, @@ -15,14 +15,15 @@ import { } from "../port.ts"; const manifest = { + ty: "denoWorker" as const, name: "whiz@ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, }; -registerDenoPlugGlobal(manifest, () => new Port()); +registerDenoPortGlobal(manifest, () => new Port()); -export default function install(config: InstallConfigBase = {}) { +export default function install(config: InstallConfigSimple = {}) { addInstallGlobal({ portName: manifest.name, ...config, diff --git a/tests/e2e.ts b/tests/e2e.ts index 00dc6e2a..89ca505c 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -79,7 +79,7 @@ await dockerTest([ name: "protoc", imports: `import port from "$ghjk/ports/protoc.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `protoc --version`, }, @@ -88,7 +88,7 @@ await dockerTest([ name: "ruff", imports: `import port from "$ghjk/ports/ruff.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `ruff --version`, }, @@ -97,7 +97,7 @@ await dockerTest([ name: "whiz", imports: `import port from "$ghjk/ports/whiz.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `whiz --version`, }, @@ -106,7 +106,7 @@ await dockerTest([ name: "act", imports: `import port from "$ghjk/ports/act.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `act --version`, }, @@ -115,7 +115,7 @@ await dockerTest([ name: "cargo-binstall", imports: `import port from "$ghjk/ports/cargo-binstall.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `cargo-binstall -V`, }, @@ -124,7 +124,7 @@ await dockerTest([ name: "mold", imports: `import port from "$ghjk/ports/mold.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `mold -V`, }, @@ -133,7 +133,7 @@ await dockerTest([ name: "wasmedge", imports: `import port from "$ghjk/ports/wasmedge.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `wasmedge --version`, }, @@ -142,7 +142,7 @@ await dockerTest([ name: "cargo-insta", imports: `import port from "$ghjk/ports/cargo-insta.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `cargo-insta -V`, }, @@ -151,7 +151,7 @@ await dockerTest([ name: "wasm-tools", imports: `import port from "$ghjk/ports/wasm-tools.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `wasm-tools -V`, }, @@ -160,7 +160,7 @@ await dockerTest([ name: "node", imports: `import port from "$ghjk/ports/node.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `node --version`, }, @@ -169,7 +169,7 @@ await dockerTest([ name: "wasm-opt", imports: `import port from "$ghjk/ports/wasm-opt.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `wasm-opt --version`, }, @@ -178,7 +178,7 @@ await dockerTest([ name: "pnpm", imports: `import port from "$ghjk/ports/earthly.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `earthly --version`, }, @@ -187,7 +187,7 @@ await dockerTest([ name: "pnpm", imports: `import port from "$ghjk/ports/pnpm.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `pnpm --version`, }, @@ -196,7 +196,7 @@ await dockerTest([ name: "jco", imports: `import port from "$ghjk/ports/jco.ts"`, confFn: `async () => { - plug({ }); + port({ }); }`, ePoint: `jco --version`, }, @@ -205,8 +205,8 @@ await dockerTest([ name: "asdf-zig", imports: `import port from "$ghjk/ports/asdf.ts"`, confFn: `async () => { - plug({ - plugRepo: "https://github.com/asdf-community/asdf-zig", + port({ + portRepo: "https://github.com/asdf-community/asdf-zig", installType: "version", }); }`, @@ -217,8 +217,8 @@ await dockerTest([ // name: "asdf-python", // imports: `import port from "$ghjk/ports/asdf.ts"`, // confFn: `async () => { - // plug({ - // plugRepo: "https://github.com/asdf-community/asdf-python", + // port({ + // portRepo: "https://github.com/asdf-community/asdf-python", // installType: "version", // }); // }`, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index ff81e07c..402ec4fb 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -22,9 +22,9 @@ WORKDIR /app # 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/init.ts -RUN SHELL=/bin/fish deno run -A /ghjk/init.ts -RUN SHELL=/bin/zsh deno run -A /ghjk/init.ts +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 # activate ghjk non-interactive shells execs ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh @@ -37,6 +37,6 @@ RUN cat > ghjk.ts < Date: Mon, 4 Dec 2023 13:42:47 +0000 Subject: [PATCH 36/43] fix: apply feedback --- LICENSE.md | 508 +++++++++++++++++++++------------------- ghjk.ts | 10 +- host/mod.ts | 17 +- install/hooks/bash.sh | 32 +-- install/hooks/fish.fish | 17 -- install/hooks/zsh.zsh | 3 +- modules/ports/mod.ts | 6 +- modules/ports/sync.ts | 66 +++++- modules/ports/types.ts | 10 +- tests/e2e.ts | 84 +------ tests/test.Dockerfile | 5 +- tests/utils.ts | 73 ++++++ utils/mod.ts | 22 ++ 13 files changed, 462 insertions(+), 391 deletions(-) create mode 100644 tests/utils.ts diff --git a/LICENSE.md b/LICENSE.md index 82ffc403..a612ad98 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,23 +1,28 @@ -# Mozilla Public License Version 2.0 +Mozilla Public License Version 2.0 +================================== 1. Definitions +-------------- ---- +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. -1.1. "Contributor" means each individual or legal entity that creates, -contributes to the creation of, or owns Covered Software. +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. -1.2. "Contributor Version" means the combination of the Contributions of others -(if any) used by a Contributor and that particular Contributor's Contribution. +1.3. "Contribution" + means Covered Software of a particular Contributor. -1.3. "Contribution" means Covered Software of a particular Contributor. +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. -1.4. "Covered Software" means Source Code Form to which the initial Contributor -has attached the notice in Exhibit A, the Executable Form of such Source Code -Form, and Modifications of such Source Code Form, in each case including -portions thereof. - -1.5. "Incompatible With Secondary Licenses" means +1.5. "Incompatible With Secondary Licenses" + means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or @@ -26,18 +31,23 @@ portions thereof. version 1.1 or earlier of the License, but not also under the terms of a Secondary License. -1.6. "Executable Form" means any form of the work other than Source Code Form. +1.6. "Executable Form" + means any form of the work other than Source Code Form. -1.7. "Larger Work" means a work that combines Covered Software with other -material, in a separate file or files, that is not Covered Software. +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. -1.8. "License" means this document. +1.8. "License" + means this document. -1.9. "Licensable" means having the right to grant, to the maximum extent -possible, whether at the time of the initial grant or subsequently, any and all -of the rights conveyed by this License. +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. -1.10. "Modifications" means any of the following: +1.10. "Modifications" + means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered @@ -46,314 +56,318 @@ of the rights conveyed by this License. (b) any new file in Source Code Form that contains any Covered Software. -1.11. "Patent Claims" of a Contributor means any patent claim(s), including -without limitation, method, process, and apparatus claims, in any patent -Licensable by such Contributor that would be infringed, but for the grant of the -License, by the making, using, selling, offering for sale, having made, import, -or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" means either the GNU General Public License, Version -2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General -Public License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" means the form of the work preferred for making -modifications. - -1.14. "You" (or "Your") means an individual or a legal entity exercising rights -under this License. For legal entities, "You" includes any entity that controls, -is controlled by, or is under common control with You. For purposes of this -definition, "control" means (a) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or otherwise, or (b) -ownership of more than fifty percent (50%) of the outstanding shares or -beneficial ownership of such entity. +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. 2. License Grants and Conditions - ---- +-------------------------------- 2.1. Grants -Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive -license: +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: (a) under intellectual property rights (other than patent or trademark) -Licensable by such Contributor to use, reproduce, make available, modify, -display, perform, distribute, and otherwise exploit its Contributions, either on -an unmodified basis, with Modifications, or as part of a Larger Work; and + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and -(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, -have made, import, and otherwise transfer either its Contributions or its -Contributor Version. +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. 2.2. Effective Date -The licenses granted in Section 2.1 with respect to any Contribution become -effective for each Contribution on the date the Contributor first distributes -such Contribution. +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. 2.3. Limitations on Grant Scope -The licenses granted in this Section 2 are the only rights granted under this -License. No additional rights or licenses will be implied from the distribution -or licensing of Covered Software under this License. Notwithstanding Section -2.1(b) above, no patent license is granted by a Contributor: +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: -(a) for any code that a Contributor has removed from Covered Software; or +(a) for any code that a Contributor has removed from Covered Software; + or (b) for infringements caused by: (i) Your and any other third party's -modifications of Covered Software, or (ii) the combination of its Contributions -with other software (except as part of its Contributor Version); or + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or -(c) under Patent Claims infringed by Covered Software in the absence of its -Contributions. +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. -This License does not grant any rights in the trademarks, service marks, or -logos of any Contributor (except as may be necessary to comply with the notice -requirements in Section 3.4). +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). 2.4. Subsequent Licenses -No Contributor makes additional grants as a result of Your choice to distribute -the Covered Software under a subsequent version of this License (see Section -10.2) or under the terms of a Secondary License (if permitted under the terms of -Section 3.3). +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). 2.5. Representation -Each Contributor represents that the Contributor believes its Contributions are -its original creation(s) or it has sufficient rights to grant the rights to its -Contributions conveyed by this License. +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use -This License is not intended to limit any rights You have under applicable -copyright doctrines of fair use, fair dealing, or other equivalents. +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. 2.7. Conditions -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in -Section 2.1. +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. 3. Responsibilities - ---- +------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under the -terms of this License. You must inform recipients that the Source Code Form of -the Covered Software is governed by the terms of this License, and how they can -obtain a copy of this License. You may not attempt to alter or restrict the -recipients' rights in the Source Code Form. +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: -(a) such Covered Software must also be made available in Source Code Form, as -described in Section 3.1, and You must inform recipients of the Executable Form -how they can obtain a copy of such Source Code Form by reasonable means in a -timely manner, at a charge no more than the cost of distribution to the -recipient; and +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and -(b) You may distribute such Executable Form under the terms of this License, or -sublicense it under different terms, provided that the license for the -Executable Form does not attempt to limit or alter the recipients' rights in the -Source Code Form under this License. +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work -You may create and distribute a Larger Work under terms of Your choice, provided -that You also comply with the requirements of this License for the Covered -Software. If the Larger Work is a combination of Covered Software with a work -governed by one or more Secondary Licenses, and the Covered Software is not -Incompatible With Secondary Licenses, this License permits You to additionally -distribute such Covered Software under the terms of such Secondary License(s), -so that the recipient of the Larger Work may, at their option, further -distribute the Covered Software under the terms of either this License or such -Secondary License(s). +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). 3.4. Notices -You may not remove or alter the substance of any license notices (including -copyright notices, patent notices, disclaimers of warranty, or limitations of -liability) contained within the Source Code Form of the Covered Software, except -that You may alter any license notices to the extent required to remedy known -factual inaccuracies. +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms -You may choose to offer, and to charge a fee for, warranty, support, indemnity -or liability obligations to one or more recipients of Covered Software. However, -You may do so only on Your own behalf, and not on behalf of any Contributor. You -must make it absolutely clear that any such warranty, support, indemnity, or -liability obligation is offered by You alone, and You hereby agree to indemnify -every Contributor for any liability incurred by such Contributor as a result of -warranty, support, indemnity or liability terms You offer. You may include -additional disclaimers of warranty and limitations of liability specific to any +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation - ---- - -If it is impossible for You to comply with any of the terms of this License with -respect to some or all of the Covered Software due to statute, judicial order, -or regulation then You must: (a) comply with the terms of this License to the -maximum extent possible; and (b) describe the limitations and the code they -affect. Such description must be placed in a text file included with all -distributions of the Covered Software under this License. Except to the extent -prohibited by statute or regulation, such description must be sufficiently -detailed for a recipient of ordinary skill to be able to understand it. +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. 5. Termination - ---- - -5.1. The rights granted under this License will terminate automatically if You -fail to comply with any of its terms. However, if You become compliant, then the -rights granted under this License from a particular Contributor are reinstated -(a) provisionally, unless and until such Contributor explicitly and finally -terminates Your grants, and (b) on an ongoing basis, if such Contributor fails -to notify You of the non-compliance by some reasonable means prior to 60 days -after You have come back into compliance. Moreover, Your grants from a -particular Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the first -time You have received notice of non-compliance with this License from such -Contributor, and You become compliant prior to 30 days after Your receipt of the -notice. +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, counter-claims, and -cross-claims) alleging that a Contributor Version directly or indirectly -infringes any patent, then the rights granted to You by any and all Contributors -for the Covered Software under Section 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user -license agreements (excluding distributors and resellers) which have been -validly granted by You or Your distributors under this License prior to -termination shall survive termination. - ---- - -- - - -- - 6. Disclaimer of Warranty * -- ------------------------- * -- - - -- Covered Software is provided under this License on an "as is" * -- basis, without warranty of any kind, either expressed, implied, or * -- statutory, including, without limitation, warranties that the * -- Covered Software is free of defects, merchantable, fit for a * -- particular purpose or non-infringing. The entire risk as to the * -- quality and performance of the Covered Software is with You. * -- Should any Covered Software prove defective in any respect, You * -- (not any Contributor) assume the cost of any necessary servicing, * -- repair, or correction. This disclaimer of warranty constitutes an * -- essential part of this License. No use of any Covered Software is * -- authorized under this License except under this disclaimer. * -- - - - ---- - ---- - -- - - -- - 7. Limitation of Liability * -- -------------------------- * -- - - -- Under no circumstances and under no legal theory, whether tort * -- (including negligence), contract, or otherwise, shall any * -- Contributor, or anyone who distributes Covered Software as * -- permitted above, be liable to You for any direct, indirect, * -- special, incidental, or consequential damages of any character * -- including, without limitation, damages for lost profits, loss of * -- goodwill, work stoppage, computer failure or malfunction, or any * -- and all other commercial damages or losses, even if such party * -- shall have been informed of the possibility of such damages. This * -- limitation of liability shall not apply to liability for death or * -- personal injury resulting from such party's negligence to the * -- extent applicable law prohibits such limitation. Some * -- jurisdictions do not allow the exclusion or limitation of * -- incidental or consequential damages, so this exclusion and * -- limitation may not apply to You. * -- - - - ---- +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ 8. Litigation +------------- ---- - -Any litigation relating to this License may be brought only in the courts of a -jurisdiction where the defendant maintains its principal place of business and -such litigation shall be governed by laws of that jurisdiction, without -reference to its conflict-of-law provisions. Nothing in this Section shall -prevent a party's ability to bring cross-claims or counter-claims. +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. 9. Miscellaneous +---------------- ---- - -This License represents the complete agreement concerning the subject matter -hereof. If any provision of this License is held to be unenforceable, such -provision shall be reformed only to the extent necessary to make it enforceable. -Any law or regulation which provides that the language of a contract shall be -construed against the drafter shall not be used to construe this License against -a Contributor. +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. 10. Versions of the License - ---- +--------------------------- 10.1. New Versions -Mozilla Foundation is the license steward. Except as provided in Section 10.3, -no one other than the license steward has the right to modify or publish new -versions of this License. Each version will be given a distinguishing version -number. +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. 10.2. Effect of New Versions -You may distribute the Covered Software under the terms of the version of the -License under which You originally received the Covered Software, or under the -terms of any subsequent version published by the license steward. +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. 10.3. Modified Versions -If you create software not governed by this License, and you want to create a -new license for such software, you may create and use a modified version of this -License if you rename the license and remove any references to the name of the -license steward (except to note that such modified license differs from this -License). +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses -If You choose to distribute Source Code Form that is Incompatible With Secondary -Licenses under the terms of this version of the License, the notice described in -Exhibit B of this License must be attached. +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. -## Exhibit A - Source Code Form License Notice +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/. + 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/. -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 file in a relevant -directory) where a recipient would be likely to look for such a notice. +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 +file in a relevant directory) where a recipient would be likely to look +for such a notice. You may add additional accurate notices of copyright ownership. -## Exhibit B - "Incompatible With Secondary Licenses" Notice +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- -This Source Code Form is "Incompatible With Secondary Licenses", as defined by -the Mozilla Public License, v. 2.0. + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/ghjk.ts b/ghjk.ts index 75ec7916..d16386cd 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -25,11 +25,11 @@ import whiz from "./ports/whiz.ts"; // jco({}); // mold({}); // act({}); -// asdf({ -// pluginRepo: "https://github.com/asdf-community/asdf-python", -// installType: "version", -// }); -protoc({}); +asdf({ + pluginRepo: "https://github.com/asdf-community/asdf-cmake", + installType: "version", +}); +// protoc({ }); // earthly({}); // ruff({}); // whiz({}); diff --git a/host/mod.ts b/host/mod.ts index de81d637..f4a9f731 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -5,6 +5,7 @@ import { cliffy_cmd } from "../deps/cli.ts"; import logger from "../utils/logger.ts"; // import { $ } from "../utils/mod.ts"; +import { isColorfulTty } from "../utils/mod.ts"; import validators from "./types.ts"; import * as std_modules from "../modules/std.ts"; import * as deno from "./deno.ts"; @@ -33,9 +34,6 @@ export async function main() { `unrecognized ghjk config type provided at path: ${configPath}`, ); } - - // console.log(JSON.stringify(serializedJson, null, " ")); - const serializedConfig = validators.serializedConfig.parse(serializedJson); let cmd: cliffy_cmd.Command = new cliffy_cmd.Command() @@ -44,7 +42,18 @@ export async function main() { .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) { diff --git a/install/hooks/bash.sh b/install/hooks/bash.sh index 6131b0ff..0bbb0a5c 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -1,17 +1,4 @@ -__ghjk_clean_up_paths() { - PATH=$(echo "$PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - PATH="${PATH%:}" - LIBRARY_PATH=$(echo "$LIBRARY_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - LIBRARY_PATH="${LIBRARY_PATH%:}" - __var_LD_LIBRARY_ENV__=$(echo "$__var_LD_LIBRARY_ENV__" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - __var_LD_LIBRARY_ENV__="${__var_LD_LIBRARY_ENV__%:}" - C_INCLUDE_PATH=$(echo "$C_INCLUDE_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - C_INCLUDE_PATH="${C_INCLUDE_PATH%:}" - CPLUS_INCLUDE_PATH=$(echo "$CPLUS_INCLUDE_PATH" | tr ':' '\n' | grep -vE "^$HOME/\.local/share/ghjk/envs" | tr '\n' ':') - CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH%:}" -} - # Define color variables ansi_red='\033[0;31m' # GREEN='\033[0;32m' @@ -29,14 +16,6 @@ init_ghjk() { if [ -e "$cur_dir/ghjk.ts" ]; then envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" if [ -d "$envDir" ]; then - __ghjk_clean_up_paths - - PATH="$envDir/shims/bin:$PATH" - LIBRARY_PATH="$envDir/shims/lib:$LIBRARY_PATH" - LD_LIBRARY_PATH="$envDir/shims/lib:$LD_LIBRARY_PATH" - C_INCLUDE_PATH="$envDir/shims/include:$C_INCLUDE_PATH" - CPLUS_INCLUDE_PATH="$envDir/shims/include:$CPLUS_INCLUDE_PATH" - . "$envDir/loader.sh" # FIXME: -ot not valid in POSIX # shellcheck disable=SC3000-SC4000 @@ -52,7 +31,6 @@ init_ghjk() { fi cur_dir="$(dirname "$cur_dir")" done - __ghjk_clean_up_paths export ghjk_alias="echo '${ansi_red}No ghjk.ts config found.${ansi_nc}'" } @@ -66,11 +44,15 @@ ghjk () { # export function for non-interactive use export -f ghjk export -f init_ghjk -export -f __ghjk_clean_up_paths + +# bash-preexec only executes if it detects bash +if [ -n "${BASH_SOURCE+x}" ]; then + hooksDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") + . "$hooksDir/bash-preexec.sh" +fi # use precmd to check for ghjk.ts before every prompt draw -hooksDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") -. "$hooksDir/bash-preexec.sh" +# precmd is avail natively for zsh precmd() { init_ghjk } diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index f09dd3ce..fa0a25ae 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -1,11 +1,3 @@ -function clean_up_paths - set --global --path PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $PATH) - set --global --path LIBRARY_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $LIBRARY_PATH) - set --global --path __var_LD_LIBRARY_ENV__ (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $__var_LD_LIBRARY_ENV__) - set --global --path C_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $C_INCLUDE_PATH) - set --global --path CPLUS_INCLUDE_PATH (string match --invert --regex "^$HOME\/\.local\/share\/ghjk\/envs" $CPLUS_INCLUDE_PATH) -end - function init_ghjk if set --query GHJK_CLEANUP eval $GHJK_CLEANUP @@ -16,14 +8,6 @@ function init_ghjk if test -e $cur_dir/ghjk.ts set --local envDir $HOME/.local/share/ghjk/envs/(string replace --all / . $cur_dir) if test -d $envDir - clean_up_paths - - set --global --prepend PATH $envDir/shims/bin - set --global --prepend LIBRARY_PATH $envDir/shims/lib - set --global --prepend __var_LD_LIBRARY_ENV__ $envDir/shims/lib - set --global --prepend C_INCLUDE_PATH $envDir/shims/include - set --global --prepend CPLUS_INCLUDE_PATH $envDir/shims/include - source $envDir/loader.fish if test $envDir/loader.fish -ot $cur_dir/ghjk.ts set_color FF4500 @@ -41,7 +25,6 @@ function init_ghjk end set cur_dir (dirname $cur_dir) end - clean_up_paths set ghjk_alias "echo 'No ghjk.ts config found.'" end diff --git a/install/hooks/zsh.zsh b/install/hooks/zsh.zsh index df45a327..94e06225 100644 --- a/install/hooks/zsh.zsh +++ b/install/hooks/zsh.zsh @@ -1,3 +1,4 @@ if [ -e ~/.zshenv ]; then . ~/.zshenv; fi -hooksDir=$(dirname -- "$(readlink -f -- "\${(%):-%x}")") +hooksDir=$(dirname -- "$(readlink -f -- "${(%):-%x}")") +echo $hooksDir . $hooksDir/hook.sh diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index a68c9ac2..d3929263 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -21,6 +21,7 @@ export class PortsModule extends ModuleBase { } command() { return new cliffy_cmd.Command() + // .alias("port") .action(function () { this.showHelp(); }) @@ -124,8 +125,9 @@ export function registerPort( export function addInstall( cx: PortsModuleConfigBase, - config: InstallConfig, + configUnclean: InstallConfig, ) { + const config = validators.installConfig.parse(configUnclean); if (!cx.ports[config.portName]) { throw new Error( `unrecognized port "${config.portName}" specified by install ${ @@ -134,5 +136,5 @@ export function addInstall( ); } logger().debug("install added", config); - cx.installs.push(validators.installConfig.parse(config)); + cx.installs.push(config); } diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 10d86b16..3818a67e 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -37,20 +37,48 @@ function envDirFromConfig(config: string): string { ); } -async function writeLoader(envDir: string, env: Record) { +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`, - Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP "set --global --export ${k} '$${k}';";\nset --global --export ${k} '${v}';` - ).join("\n"), + loader.fish, ); await Deno.writeTextFile( `${envDir}/loader.sh`, - `export GHJK_CLEANUP="";\n` + - Object.entries(env).map(([k, v]) => - `GHJK_CLEANUP+="export ${k}='$${k}';";\nexport ${k}='${v}';` - ).join("\n"), + loader.posix, ); } @@ -173,7 +201,7 @@ export async function sync(cx: PortsModuleConfig) { const conflict = env[key]; if (conflict) { throw new Error( - `duplicate env var found ${key} from installs ${instId} & ${ + `duplicate env var found ${key} from sources ${instId} & ${ conflict[1] }`, ); @@ -181,12 +209,31 @@ export async function sync(cx: PortsModuleConfig) { 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) { @@ -339,6 +386,7 @@ async function doInstall( ); } else if (manifest.ty == "asdf") { const asdfInst = validators.asdfInstallConfig.parse(instUnclean); + logger().debug(instUnclean); inst = asdfInst; port = await AsdfPort.init(envDir, asdfInst, depShims); } else { diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 4466bf52..331b876c 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -85,7 +85,15 @@ const asdfInstallConfig = installConfigBase.merge( }), ); -const installConfig = zod.union([stdInstallConfig, asdfInstallConfig]); +// 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), diff --git a/tests/e2e.ts b/tests/e2e.ts index 89ca505c..806aea1b 100644 --- a/tests/e2e.ts +++ b/tests/e2e.ts @@ -1,79 +1,7 @@ -import "../setup_logger.ts"; -import { spawn } from "../utils/mod.ts"; - -type TestCase = { - name: string; - imports: string; - confFn: string | (() => Promise); - envs?: Record; - ePoint: string; -}; - -async function dockerTest(cases: TestCase[]) { - // 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"]) { - 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, - ]); - }); - } -} +import { dockerE2eTest } from "./utils.ts"; // order tests by download size to make failed runs less expensive -await dockerTest([ +await dockerE2eTest([ // 3 megs { name: "protoc", @@ -200,17 +128,17 @@ await dockerTest([ }`, ePoint: `jco --version`, }, - // big + // 77 meg + { - name: "asdf-zig", + name: "asdf-cmake", imports: `import port from "$ghjk/ports/asdf.ts"`, confFn: `async () => { port({ - portRepo: "https://github.com/asdf-community/asdf-zig", + pluginRepo: "https://github.com/asdf-community/asdf-cmake", installType: "version", }); }`, - ePoint: `zig version`, + ePoint: `cmake --version`, }, // // big // { diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 402ec4fb..f3cbfd04 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -3,11 +3,11 @@ FROM docker.io/denoland/deno:debian-1.38.0 RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ - apt install --yes \ + apt install --yes --no-install-recommends \ # test deps fish zsh \ # asdf deps - git curl xz-utils unzip \ + git curl xz-utils unzip ca-certificates \ ;\ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; @@ -37,6 +37,7 @@ 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/utils/mod.ts b/utils/mod.ts index 7d679363..41b24256 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -132,3 +132,25 @@ 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; +} From cbb0f390eb3b84e2bfb9dc360d43b7d8e65e1198 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:10:07 +0000 Subject: [PATCH 37/43] fix: pin debian pkg versions for test dockerfile --- LICENSE.md | 508 ++++++++++++++++++++---------------------- tests/test.Dockerfile | 21 +- 2 files changed, 265 insertions(+), 264 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index a612ad98..82ffc403 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,28 +1,23 @@ -Mozilla Public License Version 2.0 -================================== +# Mozilla Public License Version 2.0 1. Definitions --------------- -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. +--- -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. +1.1. "Contributor" means each individual or legal entity that creates, +contributes to the creation of, or owns Covered Software. -1.3. "Contribution" - means Covered Software of a particular Contributor. +1.2. "Contributor Version" means the combination of the Contributions of others +(if any) used by a Contributor and that particular Contributor's Contribution. -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. +1.3. "Contribution" means Covered Software of a particular Contributor. -1.5. "Incompatible With Secondary Licenses" - means +1.4. "Covered Software" means Source Code Form to which the initial Contributor +has attached the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case including +portions thereof. + +1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or @@ -31,23 +26,18 @@ Mozilla Public License Version 2.0 version 1.1 or earlier of the License, but not also under the terms of a Secondary License. -1.6. "Executable Form" - means any form of the work other than Source Code Form. +1.6. "Executable Form" means any form of the work other than Source Code Form. -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. +1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. -1.8. "License" - means this document. +1.8. "License" means this document. -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. +1.9. "Licensable" means having the right to grant, to the maximum extent +possible, whether at the time of the initial grant or subsequently, any and all +of the rights conveyed by this License. -1.10. "Modifications" - means any of the following: +1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered @@ -56,318 +46,314 @@ Mozilla Public License Version 2.0 (b) any new file in Source Code Form that contains any Covered Software. -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. +1.11. "Patent Claims" of a Contributor means any patent claim(s), including +without limitation, method, process, and apparatus claims, in any patent +Licensable by such Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having made, import, +or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version +2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making +modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights +under this License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with You. For purposes of this +definition, "control" means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, or (b) +ownership of more than fifty percent (50%) of the outstanding shares or +beneficial ownership of such entity. 2. License Grants and Conditions --------------------------------- + +--- 2.1. Grants -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license: (a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and +Licensable by such Contributor to use, reproduce, make available, modify, +display, perform, distribute, and otherwise exploit its Contributions, either on +an unmodified basis, with Modifications, or as part of a Larger Work; and -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. +(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, +have made, import, and otherwise transfer either its Contributions or its +Contributor Version. 2.2. Effective Date -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. +The licenses granted in Section 2.1 with respect to any Contribution become +effective for each Contribution on the date the Contributor first distributes +such Contribution. 2.3. Limitations on Grant Scope -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: +The licenses granted in this Section 2 are the only rights granted under this +License. No additional rights or licenses will be implied from the distribution +or licensing of Covered Software under this License. Notwithstanding Section +2.1(b) above, no patent license is granted by a Contributor: -(a) for any code that a Contributor has removed from Covered Software; - or +(a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or +modifications of Covered Software, or (ii) the combination of its Contributions +with other software (except as part of its Contributor Version); or -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. +(c) under Patent Claims infringed by Covered Software in the absence of its +Contributions. -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). +This License does not grant any rights in the trademarks, service marks, or +logos of any Contributor (except as may be necessary to comply with the notice +requirements in Section 3.4). 2.4. Subsequent Licenses -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). +No Contributor makes additional grants as a result of Your choice to distribute +the Covered Software under a subsequent version of this License (see Section +10.2) or under the terms of a Secondary License (if permitted under the terms of +Section 3.3). 2.5. Representation -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. +Each Contributor represents that the Contributor believes its Contributions are +its original creation(s) or it has sufficient rights to grant the rights to its +Contributions conveyed by this License. 2.6. Fair Use -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. +This License is not intended to limit any rights You have under applicable +copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. 3. Responsibilities -------------------- + +--- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. +Modifications that You create or to which You contribute, must be under the +terms of this License. You must inform recipients that the Source Code Form of +the Covered Software is governed by the terms of this License, and how they can +obtain a copy of this License. You may not attempt to alter or restrict the +recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and +(a) such Covered Software must also be made available in Source Code Form, as +described in Section 3.1, and You must inform recipients of the Executable Form +how they can obtain a copy of such Source Code Form by reasonable means in a +timely manner, at a charge no more than the cost of distribution to the +recipient; and -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. +(b) You may distribute such Executable Form under the terms of this License, or +sublicense it under different terms, provided that the license for the +Executable Form does not attempt to limit or alter the recipients' rights in the +Source Code Form under this License. 3.3. Distribution of a Larger Work -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). +You may create and distribute a Larger Work under terms of Your choice, provided +that You also comply with the requirements of this License for the Covered +Software. If the Larger Work is a combination of Covered Software with a work +governed by one or more Secondary Licenses, and the Covered Software is not +Incompatible With Secondary Licenses, this License permits You to additionally +distribute such Covered Software under the terms of such Secondary License(s), +so that the recipient of the Larger Work may, at their option, further +distribute the Covered Software under the terms of either this License or such +Secondary License(s). 3.4. Notices -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. +You may not remove or alter the substance of any license notices (including +copyright notices, patent notices, disclaimers of warranty, or limitations of +liability) contained within the Source Code Form of the Covered Software, except +that You may alter any license notices to the extent required to remedy known +factual inaccuracies. 3.5. Application of Additional Terms -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Software. However, +You may do so only on Your own behalf, and not on behalf of any Contributor. You +must make it absolutely clear that any such warranty, support, indemnity, or +liability obligation is offered by You alone, and You hereby agree to indemnify +every Contributor for any liability incurred by such Contributor as a result of +warranty, support, indemnity or liability terms You offer. You may include +additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. + +--- + +If it is impossible for You to comply with any of the terms of this License with +respect to some or all of the Covered Software due to statute, judicial order, +or regulation then You must: (a) comply with the terms of this License to the +maximum extent possible; and (b) describe the limitations and the code they +affect. Such description must be placed in a text file included with all +distributions of the Covered Software under this License. Except to the extent +prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. 5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. + +--- + +5.1. The rights granted under this License will terminate automatically if You +fail to comply with any of its terms. However, if You become compliant, then the +rights granted under this License from a particular Contributor are reinstated +(a) provisionally, unless and until such Contributor explicitly and finally +terminates Your grants, and (b) on an ongoing basis, if such Contributor fails +to notify You of the non-compliance by some reasonable means prior to 60 days +after You have come back into compliance. Moreover, Your grants from a +particular Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the first +time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt of the +notice. 5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ +infringement claim (excluding declaratory judgment actions, counter-claims, and +cross-claims) alleging that a Contributor Version directly or indirectly +infringes any patent, then the rights granted to You by any and all Contributors +for the Covered Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user +license agreements (excluding distributors and resellers) which have been +validly granted by You or Your distributors under this License prior to +termination shall survive termination. + +--- + +- + - +- + 6. Disclaimer of Warranty * +- ------------------------- * +- + - +- Covered Software is provided under this License on an "as is" * +- basis, without warranty of any kind, either expressed, implied, or * +- statutory, including, without limitation, warranties that the * +- Covered Software is free of defects, merchantable, fit for a * +- particular purpose or non-infringing. The entire risk as to the * +- quality and performance of the Covered Software is with You. * +- Should any Covered Software prove defective in any respect, You * +- (not any Contributor) assume the cost of any necessary servicing, * +- repair, or correction. This disclaimer of warranty constitutes an * +- essential part of this License. No use of any Covered Software is * +- authorized under this License except under this disclaimer. * +- + - + +--- + +--- + +- + - +- + 7. Limitation of Liability * +- -------------------------- * +- + - +- Under no circumstances and under no legal theory, whether tort * +- (including negligence), contract, or otherwise, shall any * +- Contributor, or anyone who distributes Covered Software as * +- permitted above, be liable to You for any direct, indirect, * +- special, incidental, or consequential damages of any character * +- including, without limitation, damages for lost profits, loss of * +- goodwill, work stoppage, computer failure or malfunction, or any * +- and all other commercial damages or losses, even if such party * +- shall have been informed of the possibility of such damages. This * +- limitation of liability shall not apply to liability for death or * +- personal injury resulting from such party's negligence to the * +- extent applicable law prohibits such limitation. Some * +- jurisdictions do not allow the exclusion or limitation of * +- incidental or consequential damages, so this exclusion and * +- limitation may not apply to You. * +- + - + +--- 8. Litigation -------------- -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. +--- + +Any litigation relating to this License may be brought only in the courts of a +jurisdiction where the defendant maintains its principal place of business and +such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ----------------- -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. +--- + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it enforceable. +Any law or regulation which provides that the language of a contract shall be +construed against the drafter shall not be used to construe this License against +a Contributor. 10. Versions of the License ---------------------------- + +--- 10.1. New Versions -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. +Mozilla Foundation is the license steward. Except as provided in Section 10.3, +no one other than the license steward has the right to modify or publish new +versions of this License. Each version will be given a distinguishing version +number. 10.2. Effect of New Versions -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. +You may distribute the Covered Software under the terms of the version of the +License under which You originally received the Covered Software, or under the +terms of any subsequent version published by the license steward. 10.3. Modified Versions -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). +If you create software not governed by this License, and you want to create a +new license for such software, you may create and use a modified version of this +License if you rename the license and remove any references to the name of the +license steward (except to note that such modified license differs from this +License). -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. +If You choose to distribute Source Code Form that is Incompatible With Secondary +Licenses under the terms of this version of the License, the notice described in +Exhibit B of this License must be attached. -Exhibit A - Source Code Form License Notice -------------------------------------------- +## 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/. +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/. -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 -file in a relevant directory) where a recipient would be likely to look -for such a notice. +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 file in a relevant +directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- +## Exhibit B - "Incompatible With Secondary Licenses" Notice - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. +This Source Code Form is "Incompatible With Secondary Licenses", as defined by +the Mozilla Public License, v. 2.0. diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index f3cbfd04..2bcbcb01 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -1,13 +1,28 @@ -FROM docker.io/denoland/deno:debian-1.38.0 +ARG DENO_V=1.38.3 +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 echo $FISH_V $GIT_V RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ apt install --yes --no-install-recommends \ # test deps - fish zsh \ + fish=$FISH_V \ + zsh=$ZSH_V \ # asdf deps - git curl xz-utils unzip ca-certificates \ + 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}/; From 1c818e00f83586ceef73c4c1ab670b4ffa2af6cb Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:02:43 +0300 Subject: [PATCH 38/43] fix: back to aliases and zsh support --- LICENSE | 373 ++++++++++++++++++++++++++++++++++++++++ LICENSE.md | 359 -------------------------------------- install/hooks/bash.sh | 31 ++-- install/hooks/fish.fish | 11 +- tests/test.Dockerfile | 8 +- 5 files changed, 392 insertions(+), 390 deletions(-) create mode 100644 LICENSE delete mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d0a1fa14 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +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 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 +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 82ffc403..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,359 +0,0 @@ -# Mozilla Public License Version 2.0 - -1. Definitions - ---- - -1.1. "Contributor" means each individual or legal entity that creates, -contributes to the creation of, or owns Covered Software. - -1.2. "Contributor Version" means the combination of the Contributions of others -(if any) used by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" means Covered Software of a particular Contributor. - -1.4. "Covered Software" means Source Code Form to which the initial Contributor -has attached the notice in Exhibit A, the Executable Form of such Source Code -Form, and Modifications of such Source Code Form, in each case including -portions thereof. - -1.5. "Incompatible With Secondary Licenses" means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" means any form of the work other than Source Code Form. - -1.7. "Larger Work" means a work that combines Covered Software with other -material, in a separate file or files, that is not Covered Software. - -1.8. "License" means this document. - -1.9. "Licensable" means having the right to grant, to the maximum extent -possible, whether at the time of the initial grant or subsequently, any and all -of the rights conveyed by this License. - -1.10. "Modifications" means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor means any patent claim(s), including -without limitation, method, process, and apparatus claims, in any patent -Licensable by such Contributor that would be infringed, but for the grant of the -License, by the making, using, selling, offering for sale, having made, import, -or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" means either the GNU General Public License, Version -2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General -Public License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" means the form of the work preferred for making -modifications. - -1.14. "You" (or "Your") means an individual or a legal entity exercising rights -under this License. For legal entities, "You" includes any entity that controls, -is controlled by, or is under common control with You. For purposes of this -definition, "control" means (a) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or otherwise, or (b) -ownership of more than fifty percent (50%) of the outstanding shares or -beneficial ownership of such entity. - -2. License Grants and Conditions - ---- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive -license: - -(a) under intellectual property rights (other than patent or trademark) -Licensable by such Contributor to use, reproduce, make available, modify, -display, perform, distribute, and otherwise exploit its Contributions, either on -an unmodified basis, with Modifications, or as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, -have made, import, and otherwise transfer either its Contributions or its -Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution become -effective for each Contribution on the date the Contributor first distributes -such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under this -License. No additional rights or licenses will be implied from the distribution -or licensing of Covered Software under this License. Notwithstanding Section -2.1(b) above, no patent license is granted by a Contributor: - -(a) for any code that a Contributor has removed from Covered Software; or - -(b) for infringements caused by: (i) Your and any other third party's -modifications of Covered Software, or (ii) the combination of its Contributions -with other software (except as part of its Contributor Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of its -Contributions. - -This License does not grant any rights in the trademarks, service marks, or -logos of any Contributor (except as may be necessary to comply with the notice -requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to distribute -the Covered Software under a subsequent version of this License (see Section -10.2) or under the terms of a Secondary License (if permitted under the terms of -Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its Contributions are -its original creation(s) or it has sufficient rights to grant the rights to its -Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under applicable -copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in -Section 2.1. - -3. Responsibilities - ---- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under the -terms of this License. You must inform recipients that the Source Code Form of -the Covered Software is governed by the terms of this License, and how they can -obtain a copy of this License. You may not attempt to alter or restrict the -recipients' rights in the Source Code Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code Form, as -described in Section 3.1, and You must inform recipients of the Executable Form -how they can obtain a copy of such Source Code Form by reasonable means in a -timely manner, at a charge no more than the cost of distribution to the -recipient; and - -(b) You may distribute such Executable Form under the terms of this License, or -sublicense it under different terms, provided that the license for the -Executable Form does not attempt to limit or alter the recipients' rights in the -Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, provided -that You also comply with the requirements of this License for the Covered -Software. If the Larger Work is a combination of Covered Software with a work -governed by one or more Secondary Licenses, and the Covered Software is not -Incompatible With Secondary Licenses, this License permits You to additionally -distribute such Covered Software under the terms of such Secondary License(s), -so that the recipient of the Larger Work may, at their option, further -distribute the Covered Software under the terms of either this License or such -Secondary License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices (including -copyright notices, patent notices, disclaimers of warranty, or limitations of -liability) contained within the Source Code Form of the Covered Software, except -that You may alter any license notices to the extent required to remedy known -factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, indemnity -or liability obligations to one or more recipients of Covered Software. However, -You may do so only on Your own behalf, and not on behalf of any Contributor. You -must make it absolutely clear that any such warranty, support, indemnity, or -liability obligation is offered by You alone, and You hereby agree to indemnify -every Contributor for any liability incurred by such Contributor as a result of -warranty, support, indemnity or liability terms You offer. You may include -additional disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - ---- - -If it is impossible for You to comply with any of the terms of this License with -respect to some or all of the Covered Software due to statute, judicial order, -or regulation then You must: (a) comply with the terms of this License to the -maximum extent possible; and (b) describe the limitations and the code they -affect. Such description must be placed in a text file included with all -distributions of the Covered Software under this License. Except to the extent -prohibited by statute or regulation, such description must be sufficiently -detailed for a recipient of ordinary skill to be able to understand it. - -5. Termination - ---- - -5.1. The rights granted under this License will terminate automatically if You -fail to comply with any of its terms. However, if You become compliant, then the -rights granted under this License from a particular Contributor are reinstated -(a) provisionally, unless and until such Contributor explicitly and finally -terminates Your grants, and (b) on an ongoing basis, if such Contributor fails -to notify You of the non-compliance by some reasonable means prior to 60 days -after You have come back into compliance. Moreover, Your grants from a -particular Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the first -time You have received notice of non-compliance with this License from such -Contributor, and You become compliant prior to 30 days after Your receipt of the -notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, counter-claims, and -cross-claims) alleging that a Contributor Version directly or indirectly -infringes any patent, then the rights granted to You by any and all Contributors -for the Covered Software under Section 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user -license agreements (excluding distributors and resellers) which have been -validly granted by You or Your distributors under this License prior to -termination shall survive termination. - ---- - -- - - -- - 6. Disclaimer of Warranty * -- ------------------------- * -- - - -- Covered Software is provided under this License on an "as is" * -- basis, without warranty of any kind, either expressed, implied, or * -- statutory, including, without limitation, warranties that the * -- Covered Software is free of defects, merchantable, fit for a * -- particular purpose or non-infringing. The entire risk as to the * -- quality and performance of the Covered Software is with You. * -- Should any Covered Software prove defective in any respect, You * -- (not any Contributor) assume the cost of any necessary servicing, * -- repair, or correction. This disclaimer of warranty constitutes an * -- essential part of this License. No use of any Covered Software is * -- authorized under this License except under this disclaimer. * -- - - - ---- - ---- - -- - - -- - 7. Limitation of Liability * -- -------------------------- * -- - - -- Under no circumstances and under no legal theory, whether tort * -- (including negligence), contract, or otherwise, shall any * -- Contributor, or anyone who distributes Covered Software as * -- permitted above, be liable to You for any direct, indirect, * -- special, incidental, or consequential damages of any character * -- including, without limitation, damages for lost profits, loss of * -- goodwill, work stoppage, computer failure or malfunction, or any * -- and all other commercial damages or losses, even if such party * -- shall have been informed of the possibility of such damages. This * -- limitation of liability shall not apply to liability for death or * -- personal injury resulting from such party's negligence to the * -- extent applicable law prohibits such limitation. Some * -- jurisdictions do not allow the exclusion or limitation of * -- incidental or consequential damages, so this exclusion and * -- limitation may not apply to You. * -- - - - ---- - -8. Litigation - ---- - -Any litigation relating to this License may be brought only in the courts of a -jurisdiction where the defendant maintains its principal place of business and -such litigation shall be governed by laws of that jurisdiction, without -reference to its conflict-of-law provisions. Nothing in this Section shall -prevent a party's ability to bring cross-claims or counter-claims. - -9. Miscellaneous - ---- - -This License represents the complete agreement concerning the subject matter -hereof. If any provision of this License is held to be unenforceable, such -provision shall be reformed only to the extent necessary to make it enforceable. -Any law or regulation which provides that the language of a contract shall be -construed against the drafter shall not be used to construe this License against -a Contributor. - -10. Versions of the License - ---- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section 10.3, -no one other than the license steward has the right to modify or publish new -versions of this License. Each version will be given a distinguishing version -number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version of the -License under which You originally received the Covered Software, or under the -terms of any subsequent version published by the license steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to create a -new license for such software, you may create and use a modified version of this -License if you rename the license and remove any references to the name of the -license steward (except to note that such modified license differs from this -License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - -If You choose to distribute Source Code Form that is Incompatible With Secondary -Licenses under the terms of this version of the License, the notice described in -Exhibit B of this License must be attached. - -## 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/. - -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 file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -## Exhibit B - "Incompatible With Secondary Licenses" Notice - -This Source Code Form is "Incompatible With Secondary Licenses", as defined by -the Mozilla Public License, v. 2.0. diff --git a/install/hooks/bash.sh b/install/hooks/bash.sh index 0bbb0a5c..ba8e7a13 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -1,3 +1,6 @@ +# 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' @@ -14,38 +17,30 @@ init_ghjk() { cur_dir=$PWD while [ "$cur_dir" != "/" ]; do if [ -e "$cur_dir/ghjk.ts" ]; then - envDir="$HOME/.local/share/ghjk/envs/$(echo "$cur_dir" | tr '/' '.')" + envDir="$HOME/.local/share/ghjk/envs/$(printf "$cur_dir" | tr '/' '.')" if [ -d "$envDir" ]; then . "$envDir/loader.sh" # FIXME: -ot not valid in POSIX # shellcheck disable=SC3000-SC4000 if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then - echo "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}" + printf "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}" fi else - echo "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}" - echo "$envDir" + printf "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}\n" + printf "$envDir\n" fi - export ghjk_alias="deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + alias ghjk="deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return fi cur_dir="$(dirname "$cur_dir")" done - export ghjk_alias="echo '${ansi_red}No ghjk.ts config found.${ansi_nc}'" + alias ghjk="printf '${ansi_red}No ghjk.ts config found.${ansi_nc}\n'" } -# the alias value could be changed by init_ghjk -# to execute the appropriate cmd based on ghjk.ts -ghjk_alias="echo 'No ghjk.ts config found.'" -ghjk () { - eval "$ghjk_alias" "$*"; -} - -# export function for non-interactive use -export -f ghjk -export -f init_ghjk - -# bash-preexec only executes if it detects bash +# 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 hooksDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") . "$hooksDir/bash-preexec.sh" diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index fa0a25ae..7466d239 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -20,19 +20,12 @@ function init_ghjk echo $envDir set_color normal end - set ghjk_alias "deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" + alias ghjk "deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return end set cur_dir (dirname $cur_dir) end - set ghjk_alias "echo 'No ghjk.ts config found.'" -end - -# the alias value could be changed by init_ghjk -# to execute the appropriate cmd based on ghjk.ts -set ghjk_alias "echo 'No ghjk.ts config found.'" -function ghjk - eval $ghjk_alias $argv + alias ghjk "echo 'No ghjk.ts config found.'" end # try to detect ghjk.ts on each change of PWD diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 2bcbcb01..8d0f6d85 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -1,4 +1,4 @@ -ARG DENO_V=1.38.3 +ARG DENO_V=1.38.4 FROM docker.io/denoland/deno:debian-${DENO_V} @@ -45,13 +45,13 @@ RUN SHELL=/bin/zsh deno run -A /ghjk/install.ts ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh ENV ZDOTDIR=/root/.local/share/ghjk/hooks/ -# BASH_ENV behavior is only avail in bash, not sh -SHELL [ "/bin/bash", "-c"] - RUN cat > ghjk.ts < Date: Mon, 4 Dec 2023 16:52:32 +0000 Subject: [PATCH 39/43] refactor(hooks): explicit bash command --- host/mod.ts | 20 +++++++---- install.ts | 8 ++++- install/hooks/bash.sh | 4 +-- install/hooks/fish.fish | 2 -- install/mod.ts | 70 +++++++++++++++++++++----------------- main.ts | 11 ++++++ modules/ports/mod.ts | 8 +++-- modules/ports/sync.ts | 36 ++------------------ modules/std.ts | 8 +++-- modules/types.ts | 4 +++ tests/test.Dockerfile | 19 ++++++----- utils/cli.ts | 46 ------------------------- utils/mod.ts | 74 +++++++++++++++++++++++++++++++++++++++-- 13 files changed, 173 insertions(+), 137 deletions(-) create mode 100755 main.ts delete mode 100644 utils/cli.ts diff --git a/host/mod.ts b/host/mod.ts index f4a9f731..20d0f4ee 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -5,15 +5,22 @@ import { cliffy_cmd } from "../deps/cli.ts"; import logger from "../utils/logger.ts"; // import { $ } from "../utils/mod.ts"; -import { isColorfulTty } 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 configPath = Deno.args[0]; // FIXME: might be better to get this from env var + 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); + } + const envDir = envDirFromConfig(configPath); - logger().debug("config", configPath); + logger().debug({ configPath }); + logger().debug({ envDir }); let serializedJson; switch (std_path.extname(configPath)) { @@ -36,9 +43,10 @@ export async function main() { } 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: get better version + .version("0.1.0") // FIXME: better way to resolve version .description("Programmable runtime manager.") .action(function () { this.showHelp(); @@ -59,12 +67,12 @@ export async function main() { if (!mod) { throw new Error(`unrecognized module specified by ghjk.ts: ${man.id}`); } - const instance = mod.ctor(man); + const instance = mod.ctor(ctx, man); cmd = cmd.command(man.id, instance.command()); } cmd .command("completions", new cliffy_cmd.CompletionsCommand()) - .parse(Deno.args.slice(1)); + .parse(Deno.args); // const serializedConfig = validators.serializedConfig.parse( // serializedJson, // ); diff --git a/install.ts b/install.ts index 2eed1000..e23db5f9 100644 --- a/install.ts +++ b/install.ts @@ -2,4 +2,10 @@ 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 index ba8e7a13..f7530425 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -23,18 +23,16 @@ init_ghjk() { # 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}" + 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 - alias ghjk="deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return fi cur_dir="$(dirname "$cur_dir")" done - alias ghjk="printf '${ansi_red}No ghjk.ts config found.${ansi_nc}\n'" } # onlt load bash-prexec if we detect bash diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index 7466d239..0e9a18d4 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -20,12 +20,10 @@ function init_ghjk echo $envDir set_color normal end - alias ghjk "deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts" return end set cur_dir (dirname $cur_dir) end - alias ghjk "echo 'No ghjk.ts config found.'" end # try to detect ghjk.ts on each change of PWD diff --git a/install/mod.ts b/install/mod.ts index 412c6aa4..d9bb8ba6 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -1,46 +1,30 @@ -import { std_path } from "../deps/cli.ts"; -import { dirs, importRaw } from "../utils/cli.ts"; -import { spawnOutput } from "../utils/mod.ts"; - -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}`); -} +//! this installs the different shell ghjk hooks in ~/.local/share/ghjk +//! and a `ghjk` bin at ~/.local/share/bin -// null means it should be removed (for cleaning up old versions) -const vfs = { - // the script executed when users use the ghjk command - "hooks/entrypoint.ts": ` -import { main } from "${import.meta.resolve("../host/mod.ts")}"; +// TODO: support for different environments to use different versions of ghjk -await main(); -`, +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 hookVfs = { "hooks/bash-preexec.sh": await importRaw( "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", ), "hooks/.zshenv": ( await importRaw(import.meta.resolve("./hooks/zsh.zsh")) - ) - .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), + ), + // the hook run before every prompt draw in bash "hooks/hook.sh": ( await importRaw(import.meta.resolve("./hooks/bash.sh")) - ) - .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), + ), "hooks/hook.fish": ( await importRaw(import.meta.resolve("./hooks/fish.fish")) - ) - .replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV), + ), }; async function detectShell(): Promise { @@ -60,11 +44,10 @@ async function detectShell(): Promise { } return std_path.basename(path, ".exe").toLowerCase().trim(); } - async function unpackVFS(baseDir: string): Promise { await Deno.mkdir(baseDir, { recursive: true }); - for (const [subpath, content] of Object.entries(vfs)) { + for (const [subpath, content] of Object.entries(hookVfs)) { const path = std_path.resolve(baseDir, subpath); if (content === null) { await Deno.remove(path); @@ -106,6 +89,9 @@ async function filterAddFile( } export async function install() { + if (Deno.build.os == "windows") { + throw new Error("windows is not yet supported :/"); + } const { homeDir, shareDir } = dirs(); await unpackVFS(shareDir); const shell = await detectShell(); @@ -130,4 +116,28 @@ export async function install() { } else { throw new Error(`unsupported shell: ${shell}`); } + const skipBinInstall = Deno.env.get("GHJK_SKIP_BIN_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 binDir = Deno.env.get("GHJK_BIN_INSTALL_PATH") ?? + std_path.resolve(homeDir, ".local", "bin"); + await std_fs.ensureDir(binDir); + await Deno.writeTextFile( + std_path.resolve(binDir, `ghjk`), + `#!/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`); + } + } } 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/modules/ports/mod.ts b/modules/ports/mod.ts index d3929263..6b2186c8 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -11,12 +11,16 @@ import validators, { 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 config: PortsModuleConfig) { + constructor( + public ctx: GhjkCtx, + public config: PortsModuleConfig, + ) { super(); } command() { @@ -29,7 +33,7 @@ export class PortsModule extends ModuleBase { .command( "sync", new cliffy_cmd.Command().description("Syncs the environment.") - .action(() => sync(this.config)), + .action(() => sync(this.ctx.envDir, this.config)), ) .command( "list", diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 3818a67e..2628e055 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -11,31 +11,9 @@ import validators, { type PortsModuleConfig, } from "./types.ts"; import { DenoWorkerPort } from "./worker.ts"; -import { AVAIL_CONCURRENCY, dirs } from "../../utils/cli.ts"; import { AmbientAccessPort } from "./ambient.ts"; import { AsdfPort } from "./asdf.ts"; -import { getInstallId } from "../../utils/mod.ts"; - -async function findConfig(path: string): Promise { - 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; -} - -function envDirFromConfig(config: string): string { - const { shareDir } = dirs(); - return std_path.resolve( - shareDir, - "envs", - std_path.dirname(config).replaceAll("/", "."), - ); -} +import { AVAIL_CONCURRENCY, getInstallId } from "../../utils/mod.ts"; async function writeLoader( envDir: string, @@ -82,17 +60,7 @@ set --global --prepend ${k} ${v}; ); } -export async function sync(cx: PortsModuleConfig) { - const config = await findConfig(Deno.cwd()); - if (!config) { - logger().error("ghjk did not find any `ghjk.ts` config."); - return; - } - logger().debug("syncing"); - - const envDir = envDirFromConfig(config); - logger().debug({ envDir }); - +export async function sync(envDir: string, cx: PortsModuleConfig) { const installs = buildInstallGraph(cx); const artifacts = new Map(); const pendingInstalls = [...installs.indie]; diff --git a/modules/std.ts b/modules/std.ts index e7f0a3ab..e0d1b8ca 100644 --- a/modules/std.ts +++ b/modules/std.ts @@ -1,6 +1,6 @@ import { PortsModule } from "./ports/mod.ts"; import portsValidators from "./ports/types.ts"; -import { type ModuleManifest } from "./types.ts"; +import { type GhjkCtx, type ModuleManifest } from "./types.ts"; export const ports = "ports"; @@ -8,15 +8,17 @@ export const tasks = "tasks"; export const map = { [ports as string]: { - ctor: (manifest: ModuleManifest) => + ctor: (ctx: GhjkCtx, manifest: ModuleManifest) => new PortsModule( + ctx, portsValidators.portsModuleConfig.parse(manifest.config), ), }, [tasks as string]: { // TODO: impl tasks module - ctor: (manifest: ModuleManifest) => + ctor: (ctx: GhjkCtx, manifest: ModuleManifest) => new PortsModule( + ctx, portsValidators.portsModuleConfig.parse(manifest.config), ), }, diff --git a/modules/types.ts b/modules/types.ts index 651b2ce2..f66eb299 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -10,6 +10,10 @@ const moduleManifest = zod.object({ export type ModuleId = zod.infer; export type ModuleManifest = zod.infer; +export type GhjkCtx = { + configPath: string; + envDir: string; +}; export default { moduleManifest, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 8d0f6d85..dfb055f7 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -9,7 +9,6 @@ ARG CURL_V=7.88.1-10+deb12u4 ARG XZ_V=5.4.1-0.2 ARG UNZIP_V=6.0-28 -RUN echo $FISH_V $GIT_V RUN set -eux; \ export DEBIAN_FRONTEND=noninteractive; \ apt update; \ @@ -32,27 +31,31 @@ COPY deno.lock ./ COPY deps/* ./deps/ RUN deno cache deps/* COPY . ./ +RUN ln -s ./main.ts /bin/ghjk WORKDIR /app +ENV GHJK_BIN_INSTALL_PATH=/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 -# activate ghjk non-interactive shells execs -ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh -ENV ZDOTDIR=/root/.local/share/ghjk/hooks/ - RUN cat > ghjk.ts <(val: T) { logger().debug("inline", val); return val; @@ -27,7 +28,8 @@ export type SpawnOptions = { // pipeErr?: WritableStream; }; -// FIXME: replace with deidcated ergonomic library +/// This is deprecated, please use the dax lib +// as exposed from this module by the `$` object export async function spawn( cmd: string[], options: SpawnOptions = {}, @@ -154,3 +156,71 @@ export function isColorfulTty(outFile = Deno.stdout) { } 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}`, + ); +} From fe0e00b3edaf32f3b2d2f9c2f51c8713676a9f08 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:34:37 +0000 Subject: [PATCH 40/43] feat(ci): ghjk action --- .github/workflows/tests.yml | 17 +++++++++++++++++ action.yml | 34 ++++++++++++++++++++++++++++++++++ examples/protoc/ghjk.ts | 4 ++++ ghjk.ts | 10 +++++----- host/mod.ts | 5 +++-- install/mod.ts | 14 ++++++++++---- tests/test.Dockerfile | 2 +- 7 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 action.yml create mode 100644 examples/protoc/ghjk.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66de08bf..bb0c5019 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,3 +40,20 @@ jobs: 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 + . ${{ steps.ghjk-action.outputs.hooks-dir }}/hook.sh + protoc --version diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..b4c0d417 --- /dev/null +++ b/action.yml @@ -0,0 +1,34 @@ +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 +outputs: + # FIXME: convert to js based action to access env var exports + # for BASH_ENV + hooks-dir: + description: "Directory where the hooks are placed" + value: ${{ steps.ghjk-dirs.outputs.share-dir }}/hooks +runs: + using: "composite" + steps: + - id: install-ghjk + shell: bash + env: + GHJK_EXE_INSTALL_DIR: /usr/bin + run: deno run -A ${{ inputs.installer-uri }} + - id: sync-ghjk + if: ${{ inputs.sync }} + run: | + ghjk ports sync + - id: ghjk-dirs + shell: bash + run: echo "share-dir=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT 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 d16386cd..2fc80651 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -24,11 +24,11 @@ import whiz from "./ports/whiz.ts"; // cargo_insta({}); // jco({}); // mold({}); -// act({}); -asdf({ - pluginRepo: "https://github.com/asdf-community/asdf-cmake", - installType: "version", -}); +act({}); +// asdf({ +// pluginRepo: "https://github.com/asdf-community/asdf-cmake", +// installType: "version", +// }); // protoc({ }); // earthly({}); // ruff({}); diff --git a/host/mod.ts b/host/mod.ts index 20d0f4ee..0b368068 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -11,12 +11,13 @@ import * as std_modules from "../modules/std.ts"; import * as deno from "./deno.ts"; export async function main() { - const configPath = Deno.env.get("GHJK_CONFIG") ?? + const configPathIn = Deno.env.get("GHJK_CONFIG") ?? await findConfig(Deno.cwd()); - if (!configPath) { + 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 }); diff --git a/install/mod.ts b/install/mod.ts index d9bb8ba6..e578f8f9 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -3,6 +3,8 @@ // 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"; @@ -93,6 +95,7 @@ export async function install() { 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") { @@ -116,7 +119,7 @@ export async function install() { } else { throw new Error(`unsupported shell: ${shell}`); } - const skipBinInstall = Deno.env.get("GHJK_SKIP_BIN_INSTALL"); + const skipBinInstall = Deno.env.get("GHJK_SKIP_EXE_INSTALL"); if (!skipBinInstall && skipBinInstall != "0" && skipBinInstall != "false") { switch (Deno.build.os) { case "linux": @@ -125,11 +128,13 @@ export async function install() { case "illumos": case "darwin": { // TODO: respect xdg dirs - const binDir = Deno.env.get("GHJK_BIN_INSTALL_PATH") ?? + const exeDir = Deno.env.get("GHJK_EXE_INSTALL_DIR") ?? std_path.resolve(homeDir, ".local", "bin"); - await std_fs.ensureDir(binDir); + await std_fs.ensureDir(exeDir); + const exePath = std_path.resolve(exeDir, `ghjk`); + logger().debug("installing executable", { exePath }); await Deno.writeTextFile( - std_path.resolve(binDir, `ghjk`), + exePath, `#!/bin/sh deno run --unstable-worker-options -A ${import.meta.resolve("../main.ts")} $*`, { mode: 0o700 }, @@ -140,4 +145,5 @@ deno run --unstable-worker-options -A ${import.meta.resolve("../main.ts")} $*`, throw new Error(`${Deno.build.os} is not yet supported`); } } + logger().info("install success"); } diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index dfb055f7..b40af831 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -35,7 +35,7 @@ RUN ln -s ./main.ts /bin/ghjk WORKDIR /app -ENV GHJK_BIN_INSTALL_PATH=/usr/bin +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 From 56de32fc9c021d568a94fd89c1787a7fc1a7a9dc Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:38:48 +0000 Subject: [PATCH 41/43] fix(ci): missing shell prop --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index b4c0d417..7580bd98 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,7 @@ runs: GHJK_EXE_INSTALL_DIR: /usr/bin run: deno run -A ${{ inputs.installer-uri }} - id: sync-ghjk + shell: bash if: ${{ inputs.sync }} run: | ghjk ports sync From fa8e79a841b336b4659b152bb2ea0e60a4c4cdf3 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:56:23 +0000 Subject: [PATCH 42/43] fix(action): improve outputs --- .github/workflows/tests.yml | 2 +- action.yml | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb0c5019..5c09652d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,5 +55,5 @@ jobs: - shell: bash run: | cd examples/protoc - . ${{ steps.ghjk-action.outputs.hooks-dir }}/hook.sh + . $BASH_ENV protoc --version diff --git a/action.yml b/action.yml index 7580bd98..e7d12513 100644 --- a/action.yml +++ b/action.yml @@ -11,25 +11,22 @@ inputs: description: 'Disable to skip syncing ports' required: true default: true -outputs: - # FIXME: convert to js based action to access env var exports - # for BASH_ENV - hooks-dir: - description: "Directory where the hooks are placed" - value: ${{ steps.ghjk-dirs.outputs.share-dir }}/hooks runs: using: "composite" steps: - id: install-ghjk shell: bash - env: - GHJK_EXE_INSTALL_DIR: /usr/bin - run: deno run -A ${{ inputs.installer-uri }} + 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-dirs + - id: ghjk-outputs shell: bash - run: echo "share-dir=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT + run: | + echo "GHJK_SHARE_DIR=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT + echo "GHJK_HOOK_DIR=$HOME/.local/share/ghjk/hooks" >> $GITHUB_OUTPUT + echo "BASH_ENV=$HOME/.local/share/ghjk/hooks/hook.sh" >> $GITHUB_ENV From 278beb160ca67f737b1aab6f45b8742af0f8dc89 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 5 Dec 2023 20:58:36 +0300 Subject: [PATCH 43/43] refactor: make hooks more efficient --- README.md | 35 ++++++++++++++++++----------------- action.yml | 5 ++--- ghjk.ts | 2 +- install/hooks/bash.sh | 21 +++++++++++++++++---- install/hooks/fish.fish | 15 +++++++++++++-- install/hooks/zsh.zsh | 5 ++--- install/mod.ts | 26 ++++++++++++++------------ tests/test.Dockerfile | 4 ++-- 8 files changed, 69 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index d7102d0d..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 ports sync` which - - installs the missing tools at `$HOME/.local/share/ghjk/installs` + - 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` @@ -83,4 +85,3 @@ and looks as follows (abstracting away some implementation details): - [ ] untar - [ ] xz - [ ] git -- [ ] Find a way to make copy ops atomic diff --git a/action.yml b/action.yml index e7d12513..8a047137 100644 --- a/action.yml +++ b/action.yml @@ -27,6 +27,5 @@ runs: - id: ghjk-outputs shell: bash run: | - echo "GHJK_SHARE_DIR=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT - echo "GHJK_HOOK_DIR=$HOME/.local/share/ghjk/hooks" >> $GITHUB_OUTPUT - echo "BASH_ENV=$HOME/.local/share/ghjk/hooks/hook.sh" >> $GITHUB_ENV + echo "GHJK_DIR=$HOME/.local/share/ghjk" >> $GITHUB_OUTPUT + echo "BASH_ENV=$HOME/.local/share/ghjk/env.sh" >> $GITHUB_ENV diff --git a/ghjk.ts b/ghjk.ts index 2fc80651..21d41439 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/install/hooks/bash.sh b/install/hooks/bash.sh index f7530425..ff44a681 100644 --- a/install/hooks/bash.sh +++ b/install/hooks/bash.sh @@ -12,13 +12,17 @@ ansi_nc='\033[0m' # No Color init_ghjk() { if [ -n "${GHJK_CLEANUP+x}" ]; then eval "$GHJK_CLEANUP" - unset 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 @@ -40,14 +44,23 @@ init_ghjk() { # but even reliably resolving it's address # requires bash extensions. if [ -n "${BASH_SOURCE+x}" ]; then - hooksDir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE}")") - . "$hooksDir/bash-preexec.sh" + 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() { - init_ghjk + 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 diff --git a/install/hooks/fish.fish b/install/hooks/fish.fish index 0e9a18d4..a12e3b9a 100644 --- a/install/hooks/fish.fish +++ b/install/hooks/fish.fish @@ -1,13 +1,17 @@ function init_ghjk if set --query GHJK_CLEANUP eval $GHJK_CLEANUP - set --erase 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 @@ -26,8 +30,15 @@ function init_ghjk 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_hook --on-variable PWD +function ghjk_pwd_hook --on-variable PWD init_ghjk end diff --git a/install/hooks/zsh.zsh b/install/hooks/zsh.zsh index 94e06225..a235648a 100644 --- a/install/hooks/zsh.zsh +++ b/install/hooks/zsh.zsh @@ -1,4 +1,3 @@ if [ -e ~/.zshenv ]; then . ~/.zshenv; fi -hooksDir=$(dirname -- "$(readlink -f -- "${(%):-%x}")") -echo $hooksDir -. $hooksDir/hook.sh +myDir=$(dirname -- "$(readlink -f -- "${(%):-%x}")") +. $myDir/env.sh diff --git a/install/mod.ts b/install/mod.ts index e578f8f9..5833acdb 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -10,24 +10,24 @@ 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 hookVfs = { - "hooks/bash-preexec.sh": await importRaw( +const getHooksVfs = async () => ({ + "bash-preexec.sh": await importRaw( "https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh", ), - "hooks/.zshenv": ( + ".zshenv": ( await importRaw(import.meta.resolve("./hooks/zsh.zsh")) ), // the hook run before every prompt draw in bash - "hooks/hook.sh": ( + "env.sh": ( await importRaw(import.meta.resolve("./hooks/bash.sh")) ), - "hooks/hook.fish": ( + "env.fish": ( await importRaw(import.meta.resolve("./hooks/fish.fish")) ), -}; +}); async function detectShell(): Promise { let path = Deno.env.get("SHELL"); @@ -49,6 +49,7 @@ async function detectShell(): Promise { 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) { @@ -101,20 +102,21 @@ export async function install() { if (shell === "fish") { await filterAddFile( std_path.resolve(homeDir, ".config/fish/config.fish"), - /\.local\/share\/ghjk\/hooks\/hook.fish/, - ". $HOME/.local/share/ghjk/hooks/hook.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\/hooks\/hook.sh/, - ". $HOME/.local/share/ghjk/hooks/hook.sh", + /\.local\/share\/ghjk\/env/, + ". $HOME/.local/share/ghjk/env.sh", ); } else if (shell === "zsh") { await filterAddFile( std_path.resolve(homeDir, ".zshrc"), - /\.local\/share\/ghjk\/hooks\/hook.sh/, - ". $HOME/.local/share/ghjk/hooks/.zshenv", + /\.local\/share\/ghjk\/env/, + // NOTE: we use the posix for zsh + ". $HOME/.local/share/ghjk/env.sh", ); } else { throw new Error(`unsupported shell: ${shell}`); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index b40af831..af549eb7 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -52,8 +52,8 @@ RUN <