From 256f2fa240dccb8ec5b039201a5be3006c2bcd63 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 3 Jan 2025 22:37:28 +0100 Subject: [PATCH 1/6] wip: wip --- .ghjk/deno.lock | 41 +++++++++++++++++++++++++++++++++++++++++ .ghjk/lock.json | 6 +++--- deno.jsonc | 16 ++++++++++++++++ deps/cli.ts | 1 - install.sh | 17 +++++++++-------- src/ghjk/config.rs | 3 +-- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 8ec1e4b0..4e2c93ac 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -398,6 +398,47 @@ "https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1", "https://esm.sh/jszip@3.7.1": "f3872a819b015715edb05f81d973b5cd05d3d213d8eb28293ca5471fe7a71773", "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/deps/common.ts": "6fc8efc7f33b96c206e434e7c3e6ad1039070c97586371e57a521dce4b00561f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/files/deno/bindings.ts": "dd8758428cb651194ca009ba108299399f91c7a90dc3151653c653ccafabee7f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/install/utils.ts": "d2e0514871a1393e29095ed78895490f5455ce0d01cf476e86bd795bd9bfef2d", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/envs/mod.ts": "6fdb2931feda733800fd46f6564b7a111c457d75fa44511b8376d0bd435142f2", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/envs/posix.ts": "c3221bdeb76962a4262924ec0b7887fa3881c351349fbcd23f7319028dd51185", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/envs/reducer.ts": "76ee6974c9d4885da0898e01c498dcfdd99a3652a5a564d679577931a680e781", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/envs/types.ts": "9ff28d47aa60042df42fbb98a46f7689d8111be462237f5fb81771011e429088", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/mod.ts": "dd79843fc580c2051f7d2a2f2c73002561d7469b9a6f0a97cd719426dc758943", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/ambient.ts": "dfa1082c67170b24840f2c22ca320b095cdefbca26c2b8b8f9c5703bd9ba2a36", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/ghrel.ts": "f9339b4a6d6d58c902152c5016158bceb9e39a5252d612ab749b8395dc04aed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/mod.ts": "108d949da3ef0d20e00e1c10966ce6899f112fb4b7388bc1f728923d74d77e08", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/sync.ts": "8324650e23cf22ef8cf44b6764988e984117fdecc4a7a55acff77137c14b6fd2", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/ports/worker.ts": "5ece2be9ba50b28a9c1ef28779ecc7eb2cfad7d115c7d740ae1a0d4616e885f8", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/tasks/deno.ts": "75b85d8cdc129e56d7bd1bfbfdc4a6f4685e86933c41908e48fbc51be7a57fee", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/tasks/exec.ts": "ddc6bc7cbed464fdd94038a0df8668138411e94e49ae639615b93e734e37d311", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/tasks/mod.ts": "75bc52b248b43e32329462e33ae40b7a147e049e3c58dd4975ba78648c61731b", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/modules/types.ts": "76209cb68996a3bc5da4ae88666b3a7eca9109049ed8771b56e86580d4d267bd", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/src/deno_systems/bindings.ts": "513adea5cbd7de22e9a4be411400ddd0ba4f4e474363935bf6c0231c5449edcf", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/src/deno_systems/mod.ts": "87390d9dd5935606d2588586302d76676c66fac3ff3ca75fbe8193d23ec3bfd6", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/src/deno_systems/types.ts": "8591343fc77c46cdd5998f12464675ae19239bc81cf133bbb467842b5710b382", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/src/ghjk/js/runtime.js": "65adbfcbc1d46563eda4c90e4886f5da2220182072bfcdef06ae00c41ad49d89", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/utils/logger.ts": "1fc012c6bc52f8112bdd26ee57b4d5836d556344892ee3c6c585b75d172684dd", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/utils/mod.ts": "f94e6bbef44e70050eda3954c703f90b6dba6e35b21ece8d99f89dbda9f9700c", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/utils/unarchive.ts": "903de8e8beecde247f4a116c2426d9e992e97d174a6e3c26aeed852c2610a51a", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", + "https://raw.githubusercontent.com/metatypedev/ghjk/0eaf4d8bd190896970501673b8969a2af03f0919/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62", "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 32181f40..b1faf95b 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -430,7 +430,7 @@ } ] }, - "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy": { + "bciqnerbswitlqt27ado74puxt3faxea6jaxe4wvytnzwjpupig25kky": { "provides": [ { "ty": "posix.envVar", @@ -440,7 +440,7 @@ { "ty": "posix.envVar", "key": "RUSTY_V8_MIRROR", - "val": "/var/home/asdf/repos/ecma/ghjk/.dev/rusty_v8" + "val": "/home/yohe/ghjk/.dev/rusty_v8" }, { "ty": "ghjk.ports.InstallSetRef", @@ -453,7 +453,7 @@ "envsNamed": { "main": "bciqfnku2tswsz4gapwhys5ox5uiyzcb5r7bmuwzljjeziljcu7efroi", "_rust": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", - "dev": "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy" + "dev": "bciqnerbswitlqt27ado74puxt3faxea6jaxe4wvytnzwjpupig25kky" } } } diff --git a/deno.jsonc b/deno.jsonc index b2f858e2..2dfa2c4e 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,11 +1,27 @@ { "tasks": { "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", + "test-doc": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", "self": "cargo run -p ghjk", "cache": "deno cache deps/*", "check": "deno run -A ./tools/check.ts", "dev": "deno run -A ./tools/dev.ts" }, + "test": { + "exclude": [ + ".git", + ".dev", + "install.ts", + "./target", + ".ghjk/**", + ".deno-dir/**", + "vendor/**", + "./src/deno_systems/bindings.ts", + "./src/ghjk/js", + "./files/deno/bindings.ts", + "./tools" + ] + }, "fmt": { "exclude": [ ".git", diff --git a/deps/cli.ts b/deps/cli.ts index 9829c228..9294775f 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -2,5 +2,4 @@ export * from "./common.ts"; -export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; export { Table } from "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts"; diff --git a/install.sh b/install.sh index 1eaa503b..ae17dc85 100755 --- a/install.sh +++ b/install.sh @@ -22,7 +22,6 @@ INSTALLER_URL="https://raw.githubusercontent.com/$ORG/$REPO/main/install.sh" RELEASE_URL="https://github.com/$ORG/$REPO/releases" LATEST_VERSION=$(curl "$RELEASE_URL/latest" -s -L -I -o /dev/null -w '%{url_effective}') -LATEST_VERSION="${LATEST_VERSION##*v}" PLATFORM="${PLATFORM:-}" TMP_DIR=$(mktemp -d) @@ -31,7 +30,9 @@ VERSION="${VERSION:-$LATEST_VERSION}" # make sure the version is prepended with v if [ "${VERSION#"v"}" = "$VERSION" ]; then - VERSION="v$VERSION" + cat >&2 <&2 read -r _throwaway mv "$TMP_DIR/$EXE" "$GHJK_INSTALL_EXEC_DIR" + rm -r "$TMP_DIR" else echo "$GHJK_INSTALL_EXEC_DIR is not writable." exit 1 @@ -126,9 +128,8 @@ EOF fi GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/$ORG/$REPO/$VERSION/install.ts}" -"$TMP_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" +"$GHJK_INSTALL_EXEC_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" -rm -r "$TMP_DIR" SHELL_TYPE=$(basename "$SHELL") diff --git a/src/ghjk/config.rs b/src/ghjk/config.rs index 5157c901..b1f6865d 100644 --- a/src/ghjk/config.rs +++ b/src/ghjk/config.rs @@ -92,8 +92,7 @@ impl Config { .join(&format!("{}/", cwd.file_name().unwrap().to_string_lossy())) .wrap_err("repo url error")? } else { - const BASE_URL: &str = - "https://raw.githubusercontent.com/metatypedev/metatype/"; + const BASE_URL: &str = "https://raw.githubusercontent.com/metatypedev/ghjk/"; // repo root url must end in slash due to // how Url::join works let url = BASE_URL.to_owned() + crate::shadow::COMMIT_HASH + "/"; From 550468861bf26cc74752850a6cb3d38eea1e4d6a Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 4 Jan 2025 05:02:21 +0300 Subject: [PATCH 2/6] wip!: breaking warts --- .ghjk/lock.json | 212 +++++++++++++++++++--------- .github/actionlint.yaml | 2 + .github/workflows/tests.yml | 2 +- .pre-commit-config.yaml | 4 +- README.md | 10 +- deno.jsonc | 2 +- docs/manual.md | 13 +- examples/env_vars/ghjk.ts | 3 +- examples/envs/ghjk.ts | 2 +- examples/kitchen/ghjk.ts | 10 +- examples/many_installs/ghjk.ts | 2 +- examples/tasks/ghjk.ts | 2 +- {host/init => examples}/template.ts | 12 +- files/deno/bindings.ts | 10 +- files/deno/worker.ts | 49 ------- files/mod.ts | 2 +- files/types.ts | 15 ++ ghjk.ts | 115 +++++++-------- hack.ts | 4 +- host/init/mod.ts | 10 +- host/types.ts | 22 --- mod.ts | 10 +- modules/envs/mod.ts | 17 ++- modules/mod.ts | 5 +- modules/ports/mod.ts | 2 +- modules/ports/types.ts | 2 +- modules/tasks/deno.ts | 2 +- modules/tasks/mod.ts | 3 +- modules/tasks/types.ts | 2 +- modules/types.ts | 8 ++ ports/cargobi.ts | 7 + ports/npmi.ts | 4 +- src/deno_systems/bindings.ts | 11 +- src/deno_systems/types.ts | 70 ++++----- src/ghjk/cli.rs | 101 +++++++++++-- src/ghjk/config.rs | 4 +- src/ghjk/host.rs | 2 +- src/ghjk/host/hashfile.rs | 11 +- src/ghjk/log.rs | 4 +- src/ghjk/main.rs | 18 ++- src/ghjk/systems/deno.rs | 3 + src/ghjk/utils.rs | 12 ++ tests/envHooks.ts | 2 +- tests/examples.ts | 28 ++++ tests/hashfile.ts | 11 +- tests/tasks.ts | 5 +- tests/utils.ts | 6 +- tools/dev.ts | 2 +- utils/mod.ts | 2 +- 49 files changed, 527 insertions(+), 330 deletions(-) create mode 100644 .github/actionlint.yaml rename {host/init => examples}/template.ts (51%) delete mode 100644 files/deno/worker.ts create mode 100644 files/types.ts delete mode 100644 host/types.ts create mode 100644 tests/examples.ts diff --git a/.ghjk/lock.json b/.ghjk/lock.json index b1faf95b..0d510038 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -295,6 +295,44 @@ "portRef": "asdf_plugin_git@0.1.0", "pluginRepo": "https://github.com/lsanwick/asdf-jq", "specifiedVersion": false + }, + "bciqjtrxihpi27npax5rsw7dgpojy6gkpo7vwhb2opxobk24mbgmcp7q": { + "version": "0.12.1", + "buildDepConfigs": { + "cargo_binstall_ghrel": { + "version": "v1.10.18", + "buildDepConfigs": {}, + "portRef": "cargo_binstall_ghrel@0.1.0", + "specifiedVersion": false + }, + "rust_rustup": { + "version": "1.82.0", + "buildDepConfigs": { + "rustup_rustlang": { + "version": "1.27.1", + "buildDepConfigs": { + "git_aa": { + "version": "2.47.1", + "buildDepConfigs": {}, + "portRef": "git_aa@0.1.0", + "specifiedVersion": false + } + }, + "portRef": "rustup_rustlang@0.1.0", + "specifiedVersion": false + } + }, + "portRef": "rust_rustup@0.1.0", + "profile": "default", + "components": [ + "rust-src" + ], + "specifiedVersion": true + } + }, + "portRef": "cargobi_cratesio@0.1.0", + "crateName": "cargo-bloat", + "specifiedVersion": false } } }, @@ -313,11 +351,11 @@ "sets": { "ghjkEnvProvInstSet___main": { "installs": [ - "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za", "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", "bciqpu4klxr3hl6ujhmflrlfd3dxp47ijq26mnathb26ojzwkeggy5ii", - "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji" + "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji", + "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za" ], "allowedBuildDeps": "bciqdg64uhkvlkqyc7nli33cja3aolbcdr75qepnrhj5ojlifsvxqzgq" }, @@ -326,26 +364,28 @@ "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa", "bciqfrfun7z7soj7yxzziyvmt2jnebqvneeoozk5vynmg5pa6wqynhvi", "bciqgkc6fegmxehj4whmusfuurxyp4ayeysn6qa2t6q64baac5is7uui", - "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za", + "bciqlmoqot4jk2lb2b34pldr5iiwsfm3biuipzesjkkwmc2n2o6nlw4q", "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", "bciqpu4klxr3hl6ujhmflrlfd3dxp47ijq26mnathb26ojzwkeggy5ii", - "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji" + "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji", + "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za" ], "allowedBuildDeps": "bciqdg64uhkvlkqyc7nli33cja3aolbcdr75qepnrhj5ojlifsvxqzgq" }, "ghjkEnvProvInstSet___dev": { "installs": [ "bciqlfx3mm5hi37g75snjknph6fkniixjhnvyyfxeua7f5z4h7nnqtna", - "bciqlmoqot4jk2lb2b34pldr5iiwsfm3biuipzesjkkwmc2n2o6nlw4q", + "bciqhsrsmayibhhhcp3jmay4tnrsyhz5od6ngtaazymx3o64xxzbqiha", "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa", "bciqfrfun7z7soj7yxzziyvmt2jnebqvneeoozk5vynmg5pa6wqynhvi", "bciqgkc6fegmxehj4whmusfuurxyp4ayeysn6qa2t6q64baac5is7uui", - "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za", + "bciqlmoqot4jk2lb2b34pldr5iiwsfm3biuipzesjkkwmc2n2o6nlw4q", "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", "bciqpu4klxr3hl6ujhmflrlfd3dxp47ijq26mnathb26ojzwkeggy5ii", - "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji" + "bciqelae2kzmf7umbo62flzq2mnlhnc4ilbfmn4va2fzrqwx7w7zusji", + "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za" ], "allowedBuildDeps": "bciqdg64uhkvlkqyc7nli33cja3aolbcdr75qepnrhj5ojlifsvxqzgq" }, @@ -430,7 +470,7 @@ } ] }, - "bciqnerbswitlqt27ado74puxt3faxea6jaxe4wvytnzwjpupig25kky": { + "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy": { "provides": [ { "ty": "posix.envVar", @@ -440,7 +480,7 @@ { "ty": "posix.envVar", "key": "RUSTY_V8_MIRROR", - "val": "/home/yohe/ghjk/.dev/rusty_v8" + "val": "/var/home/asdf/repos/ecma/ghjk/.dev/rusty_v8" }, { "ty": "ghjk.ports.InstallSetRef", @@ -453,62 +493,13 @@ "envsNamed": { "main": "bciqfnku2tswsz4gapwhys5ox5uiyzcb5r7bmuwzljjeziljcu7efroi", "_rust": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", - "dev": "bciqnerbswitlqt27ado74puxt3faxea6jaxe4wvytnzwjpupig25kky" + "ci": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", + "dev": "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy" } } } ], "blackboard": { - "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za": { - "buildDepConfigs": { - "asdf_plugin_git": { - "pluginRepo": "https://github.com/lsanwick/asdf-jq", - "portRef": "asdf_plugin_git@0.1.0" - } - }, - "resolutionDepConfigs": { - "asdf_plugin_git": { - "pluginRepo": "https://github.com/lsanwick/asdf-jq", - "portRef": "asdf_plugin_git@0.1.0" - } - }, - "port": { - "ty": "denoWorker@v1", - "name": "asdf", - "platforms": [ - "x86_64-linux", - "aarch64-linux", - "x86_64-darwin", - "aarch64-darwin" - ], - "version": "0.1.0", - "buildDeps": [ - { - "name": "curl_aa" - }, - { - "name": "git_aa" - }, - { - "name": "asdf_plugin_git" - } - ], - "resolutionDeps": [ - { - "name": "curl_aa" - }, - { - "name": "git_aa" - }, - { - "name": "asdf_plugin_git" - } - ], - "moduleSpecifier": "file:///ports/asdf.ts" - }, - "pluginRepo": "https://github.com/lsanwick/asdf-jq", - "installType": "version" - }, "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi": { "port": { "ty": "denoWorker@v1", @@ -610,6 +601,56 @@ "moduleSpecifier": "file:///ports/deno_ghrel.ts" } }, + "bciqjucge6yrzcawqzcljvrpmtwrocecsww6pcjwipzn5j2hfwjof7za": { + "buildDepConfigs": { + "asdf_plugin_git": { + "pluginRepo": "https://github.com/lsanwick/asdf-jq", + "portRef": "asdf_plugin_git@0.1.0" + } + }, + "resolutionDepConfigs": { + "asdf_plugin_git": { + "pluginRepo": "https://github.com/lsanwick/asdf-jq", + "portRef": "asdf_plugin_git@0.1.0" + } + }, + "port": { + "ty": "denoWorker@v1", + "name": "asdf", + "platforms": [ + "x86_64-linux", + "aarch64-linux", + "x86_64-darwin", + "aarch64-darwin" + ], + "version": "0.1.0", + "buildDeps": [ + { + "name": "curl_aa" + }, + { + "name": "git_aa" + }, + { + "name": "asdf_plugin_git" + } + ], + "resolutionDeps": [ + { + "name": "curl_aa" + }, + { + "name": "git_aa" + }, + { + "name": "asdf_plugin_git" + } + ], + "moduleSpecifier": "file:///ports/asdf.ts" + }, + "pluginRepo": "https://github.com/lsanwick/asdf-jq", + "installType": "version" + }, "bciqdfarczmlu3r5dkvcdoultfbnuvn6saao55h4fbb3jg72kv6mkr3y": { "manifest": { "ty": "denoWorker@v1", @@ -1009,6 +1050,25 @@ "rust-src" ] }, + "bciqlmoqot4jk2lb2b34pldr5iiwsfm3biuipzesjkkwmc2n2o6nlw4q": { + "version": "v2.4.0", + "port": { + "ty": "denoWorker@v1", + "name": "mold_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux" + ], + "version": "0.1.0", + "buildDeps": [ + { + "name": "tar_aa" + } + ], + "moduleSpecifier": "file:///ports/mold.ts" + }, + "replaceLd": true + }, "bciqlfx3mm5hi37g75snjknph6fkniixjhnvyyfxeua7f5z4h7nnqtna": { "port": { "ty": "denoWorker@v1", @@ -1046,24 +1106,42 @@ }, "crateName": "tokio-console" }, - "bciqlmoqot4jk2lb2b34pldr5iiwsfm3biuipzesjkkwmc2n2o6nlw4q": { - "version": "v2.4.0", + "bciqhsrsmayibhhhcp3jmay4tnrsyhz5od6ngtaazymx3o64xxzbqiha": { "port": { "ty": "denoWorker@v1", - "name": "mold_ghrel", + "name": "cargobi_cratesio", "platforms": [ + "x86_64-linux", "aarch64-linux", - "x86_64-linux" + "x86_64-darwin", + "aarch64-darwin", + "x86_64-windows", + "aarch64-windows", + "x86_64-freebsd", + "aarch64-freebsd", + "x86_64-netbsd", + "aarch64-netbsd", + "x86_64-aix", + "aarch64-aix", + "x86_64-solaris", + "aarch64-solaris", + "x86_64-illumos", + "aarch64-illumos", + "x86_64-android", + "aarch64-android" ], "version": "0.1.0", "buildDeps": [ { - "name": "tar_aa" + "name": "cargo_binstall_ghrel" + }, + { + "name": "rust_rustup" } ], - "moduleSpecifier": "file:///ports/mold.ts" + "moduleSpecifier": "file:///ports/cargobi.ts" }, - "replaceLd": true + "crateName": "cargo-bloat" }, "bciqeie3punk3gz4kcfdk2fxx5bsj5fh3j7bb7z36qmimayhwdsvp7cq": {} } diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..2cc072fe --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,2 @@ +runner-labels: + - custom-arm diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1517cffc..9b2a08b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,7 +42,7 @@ jobs: # pre commit runs ghjk. We'll always see changes # to lock.json since GITHUB_TOKEN is different # in the CI - - run: deno task self envs cook -t lock-sed + - run: deno task ghjk envs cook -t lock-sed - uses: pre-commit/action@v3.0.1 env: SKIP: ghjk-resolve diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0766cc4..4f8a3bd3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,12 +38,12 @@ repos: - id: ghjk-resolve name: Ghjk resolve language: system - entry: bash -c 'deno task self p resolve' + entry: bash -c 'deno task ghjk p resolve' pass_filenames: false - id: lock-sed name: Sed lock language: system - entry: bash -c 'deno task self x lock-sed' + entry: bash -c 'deno task ghjk x lock-sed' pass_filenames: false - id: deno-fmt name: Deno format diff --git a/README.md b/README.md index c5f884fd..d82d8af7 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Before anything, make sure the following programs are available on the system. Install the ghjk cli using the installer scripts like so: ```bash -curl -fsSL https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh | bash +curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh" | bash ``` Use the following command to create a starter `ghjk.ts` in your project directory: @@ -65,12 +65,8 @@ import { file } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts // ports are small programs that install sowtware to your envs import * as ports from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/ports/mod.ts"; -const ghjk = file({}); - -// NOTE: `ghjk.ts` files are expected to export this sophon object -// all the functions on the ghjk object are ultimately modifying the 'sophon' proxy -// object exported here. -export const sophon = ghjk.sophon; +// NOTE: `ghjk.ts` files are expected to export this ghjk object +export const ghjk = file({}); // top level `install`s go to the `main` env ghjk.install(ports.protoc()); diff --git a/deno.jsonc b/deno.jsonc index 2dfa2c4e..dcd15170 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -2,7 +2,7 @@ "tasks": { "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", "test-doc": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", - "self": "cargo run -p ghjk", + "ghjk": "cargo run -p ghjk", "cache": "deno cache deps/*", "check": "deno run -A ./tools/check.ts", "dev": "deno run -A ./tools/dev.ts" diff --git a/docs/manual.md b/docs/manual.md index f4dedf34..2f09699c 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -17,7 +17,7 @@ There are installer scripts available in the repo. ```bash # stable -curl -fsSL https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh | bash +curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh" | bash ``` This will install the CLI and add configuration your shell rc files the necessary hooks ghjk needs to function. @@ -44,11 +44,8 @@ import { file } from ".../mod.ts"; // import the port for the node program import node from ".../ports/node.ts"; -const ghjk = file(); - -// all ghjk.ts files are expected to export this special `sophon` object -// all the functions from the ghjk object are modifying the sophon -export const sophon = ghjk.sophon; +// all ghjk.ts files are expected to export this special `ghjk` object +export const ghjk = file(); // install programs (ports) into your env ghjk.install( @@ -95,7 +92,7 @@ To look at what the ghjkfile looks like serialized, you can use the following co ```bash # look at the serialized form the ghjkfile -ghjk print config +ghjk print serialized ``` #### The Hashfile @@ -414,7 +411,7 @@ Namely, it's good practice to: # sample of how one would install ghjk for use in a Dockerfile ARG GHJK_VERSION=v0.3.0-rc.1 # /usr/bin is available in $PATH by default making ghjk immediately avail -RUN curl -fsSL https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh \ +RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh" \ | GHJK_INSTALL_EXE_DIR=/usr/bin sh ``` diff --git a/examples/env_vars/ghjk.ts b/examples/env_vars/ghjk.ts index 1d98f92c..bd9cad01 100644 --- a/examples/env_vars/ghjk.ts +++ b/examples/env_vars/ghjk.ts @@ -1,6 +1,6 @@ import { file } from "../../hack.ts"; -const ghjk = file({ +export const ghjk = file({ defaultEnv: "empty", envs: [{ name: "empty", inherit: false }], defaultBaseEnv: "empty", @@ -11,7 +11,6 @@ const ghjk = file({ tasks: {}, }); -export const sophon = ghjk.sophon; const { env, task } = ghjk; env("main") diff --git a/examples/envs/ghjk.ts b/examples/envs/ghjk.ts index 3a2e7c5d..ffb646ea 100644 --- a/examples/envs/ghjk.ts +++ b/examples/envs/ghjk.ts @@ -1,4 +1,4 @@ -export { sophon } from "../../hack.ts"; +export { ghjk } from "../../hack.ts"; import { config, env, install, task } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index ca2aa829..3147abeb 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -2,7 +2,10 @@ import { stdDeps } from "../../files/mod.ts"; import { file } from "../../mod.ts"; import * as ports from "../../ports/mod.ts"; -const ghjk = file({ +// we need this export for this file to be a valid ghjkfile +// it's the one thing used by the ghjk host implementation to +// interact with your ghjkfile +export const ghjk = file({ // configre an empty env so that no ports are avail by default in our workdir defaultEnv: "empty", envs: [{ name: "empty", inherit: false }], @@ -19,11 +22,6 @@ const ghjk = file({ tasks: {}, }); -// we need this export for this file to be a valid ghjkfile -// it's the one thing used by the ghjk host implementation to -// interact with your ghjkfile -export const sophon = ghjk.sophon; - const { install, env, task } = ghjk; // we can configure main like this as well diff --git a/examples/many_installs/ghjk.ts b/examples/many_installs/ghjk.ts index b5a3a154..60f79862 100644 --- a/examples/many_installs/ghjk.ts +++ b/examples/many_installs/ghjk.ts @@ -1,4 +1,4 @@ -export { sophon } from "../../hack.ts"; +export { ghjk } from "../../hack.ts"; import { config, install } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts index 61b227f4..20eb53b4 100644 --- a/examples/tasks/ghjk.ts +++ b/examples/tasks/ghjk.ts @@ -1,4 +1,4 @@ -export { sophon } from "../../hack.ts"; +export { ghjk } from "../../hack.ts"; import { logger, task } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/host/init/template.ts b/examples/template.ts similarity index 51% rename from host/init/template.ts rename to examples/template.ts index fdf1460e..5f53f889 100644 --- a/host/init/template.ts +++ b/examples/template.ts @@ -1,20 +1,18 @@ // @ts-nocheck: Deno based -import { file } from "../../mod.ts"; // template-import -// import * as ports from "../../ports/mod.ts"; // template-import +import { file } from "../mod.ts"; // template-import +// import * as ports from "../ports/mod.ts"; // template-import -const ghjk = file({ +// This export is necessary for typescript ghjkfiles +export const ghjk = file({ // allows usage of ports that depend on node/python // enableRuntimes: true, }); -// This export is necessary for ts ghjkfiles -export const sophon = ghjk.sophon; - ghjk.install( // install ports into the main env // ports.node(), - // ports.cpy_bs(), + // ports.pnpm(), ); ghjk.task("greet", async ($) => { diff --git a/files/deno/bindings.ts b/files/deno/bindings.ts index 8bf47f10..530b57c2 100644 --- a/files/deno/bindings.ts +++ b/files/deno/bindings.ts @@ -20,7 +20,15 @@ async function serialize(args: zod.infer) { const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(args.uri); - const rawConfig = await mod.sophon.getConfig(args.uri, mod.secureConfig); + if (!mod.ghjk) { + throw new Error(`ghjk.ts does not export a ghjk object: ${args.uri}`); + } + if (!mod.ghjk.sophon) { + throw new Error( + `no sophon found on exported ghjk object from ghjk.ts: ${args.uri}`, + ); + } + const rawConfig = await mod.ghjk.sophon.getConfig(args.uri, mod.secureConfig); const config = JSON.parse(JSON.stringify(rawConfig)); return { config, diff --git a/files/deno/worker.ts b/files/deno/worker.ts deleted file mode 100644 index ca55221d..00000000 --- a/files/deno/worker.ts +++ /dev/null @@ -1,49 +0,0 @@ -//! this loads the ghjk.ts module and provides a program for it - -//// -/// - -// all imports in here should be dynamic imports as we want to -// modify the Deno namespace before anyone touches it - -import { shimDenoNamespace } from "../../utils/worker.ts"; -// NOTE: only import types -import type { DriverRequests, DriverResponse } from "./mod.ts"; - -self.onmessage = onMsg; - -async function onMsg(msg: MessageEvent) { - const req = msg.data; - if (!req.ty) { - throw new Error(`unrecognized event data`, { - cause: req, - }); - } - let res: DriverResponse; - if (req.ty == "serialize") { - res = { - ty: req.ty, - payload: await serializeConfig(req.uri, req.envVars), - }; - } else { - throw new Error(`unrecognized request type: ${req.ty}`, { - cause: req, - }); - } - self.postMessage(res); -} - -async function serializeConfig(uri: string, envVars: Record) { - const shimHandle = shimDenoNamespace(envVars); - const { setup: setupLogger } = await import("../../utils/logger.ts"); - setupLogger(); - const mod = await import(uri); - const rawConfig = await mod.sophon.getConfig(uri, mod.secureConfig); - const config = JSON.parse(JSON.stringify(rawConfig)); - return { - config, - accessedEnvKeys: shimHandle.getAccessedEnvKeys(), - readFiles: shimHandle.getReadFiles(), - listedFiles: shimHandle.getListedFiles(), - }; -} diff --git a/files/mod.ts b/files/mod.ts index dc30b666..350f31bb 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -27,7 +27,7 @@ import { import * as std_ports from "../modules/ports/std.ts"; import runtime_ports from "../modules/ports/std_runtime.ts"; // host -import type { SerializedConfig } from "../host/types.ts"; +import type { SerializedConfig } from "./types.ts"; import * as std_modules from "../modules/std.ts"; // tasks // WARN: this module has side-effects and only ever import diff --git a/files/types.ts b/files/types.ts new file mode 100644 index 00000000..010baf80 --- /dev/null +++ b/files/types.ts @@ -0,0 +1,15 @@ +import { zod } from "../deps/common.ts"; +import moduleValidators from "../modules/types.ts"; + +const serializedConfig = zod.object( + { + modules: zod.array(moduleValidators.moduleManifest), + blackboard: moduleValidators.blackboard, + }, +); + +export type SerializedConfig = zod.infer; + +export default { + serializedConfig, +}; diff --git a/ghjk.ts b/ghjk.ts index 79e8be0c..2b2c5a3f 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,17 +1,29 @@ // @ts-nocheck: Ghjkfile based on Deno -export { sophon } from "./hack.ts"; -import { config, env, install, task } from "./hack.ts"; -import { switchMap } from "./port.ts"; +import { file } from "./mod.ts"; + import * as ports from "./ports/mod.ts"; +import { switchMap } from "./port.ts"; import { sedLock } from "./std.ts"; import { downloadFile, DownloadFileArgs } from "./utils/mod.ts"; import { unarchive } from "./utils/unarchive.ts"; import dummy from "./ports/dummy.ts"; +export const ghjk = file({}); + +const DENO_VERSION = "2.1.2"; // keep in sync with the deno repo's ./rust-toolchain.toml const RUST_VERSION = "1.82.0"; +ghjk.env("main") + // these are used for developing ghjk + .install( + ports.act(), + ports.pipi({ packageName: "pre-commit" })[0], + ports.pipi({ packageName: "vale" })[0], + ports.deno_ghrel({ version: DENO_VERSION }), + ); + const installs = { rust: ports.rust({ version: RUST_VERSION, @@ -20,84 +32,75 @@ const installs = { }), }; -config({ +ghjk.config({ defaultEnv: "dev", enableRuntimes: true, allowedBuildDeps: [ports.cpy_bs({ version: "3.13.1" }), installs.rust], }); -env("main").vars({ - RUST_LOG: [ - "info", - Object.entries({ - "TRACE": [ - // "denort", - // "deno", - ], - "DEBUG": [ - // "runtime", - // "tokio", - ], - "INFO": [ - "deno::npm", - "deno::file_fetcher", - "swc_ecma_transforms_base", - "swc_common", - "h2", - "rustls", - "mio", - "hyper_util", - ], - }).flatMap(([level, modules]) => - modules.map((module) => `${module}=${level.toLowerCase()}`) - ), - ].join(), -}); - -env("_rust") +ghjk.env("_rust") .install( ports.protoc(), ports.pipi({ packageName: "cmake" })[0], installs.rust, + ...(Deno.build.os == "linux" && !Deno.env.has("NO_MOLD") + ? [ports.mold({ + version: "v2.4.0", + replaceLd: true, + })] + : []), ); -const RUSTY_V8_MIRROR = `${import.meta.dirname}/.dev/rusty_v8`; - -env("dev") +ghjk.env("dev") .inherit("_rust") .install(ports.cargobi({ crateName: "tokio-console" })) + .install(ports.cargobi({ crateName: "cargo-bloat" })) .vars({ // V8_FORCE_DEBUG: "true", - RUSTY_V8_MIRROR, + RUSTY_V8_MIRROR: `${import.meta.dirname}/.dev/rusty_v8`, }); -if (Deno.build.os == "linux" && !Deno.env.has("NO_MOLD")) { - const mold = ports.mold({ - version: "v2.4.0", - replaceLd: true, - }); - env("dev").install(mold); -} +ghjk.env("ci") + .inherit("_rust"); // these are just for quick testing -install( +ghjk.install( ports.asdf({ pluginRepo: "https://github.com/lsanwick/asdf-jq", installType: "version", }), ); -const DENO_VERSION = "2.1.2"; - -// these are used for developing ghjk -install( - ports.act(), - ports.pipi({ packageName: "pre-commit" })[0], - ports.pipi({ packageName: "vale" })[0], - ports.deno_ghrel({ version: DENO_VERSION }), -); +ghjk.env("main") + .vars({ + RUST_LOG: [ + "info", + Object.entries({ + "TRACE": [ + // "denort", + // "deno", + ], + "DEBUG": [ + // "runtime", + // "tokio", + ], + "INFO": [ + "deno::npm", + "deno::file_fetcher", + "swc_ecma_transforms_base", + "swc_common", + "h2", + "rustls", + "mio", + "hyper_util", + ], + }).flatMap(([level, modules]) => + modules.map((module) => `${module}=${level.toLowerCase()}`) + ), + ].join(), + }); -task( +ghjk.task( "cache-v8", { desc: "Install the V8 builds to a local cache.", @@ -142,7 +145,7 @@ task( }, ); -task( +ghjk.task( "lock-sed", async ($) => { const GHJK_VERSION = "0.3.0-rc.1"; diff --git a/hack.ts b/hack.ts index 3d2ebeb8..4b6a948a 100644 --- a/hack.ts +++ b/hack.ts @@ -8,9 +8,7 @@ export * from "./mod.ts"; import { file } from "./mod.ts"; import logger from "./utils/logger.ts"; -const ghjk = file(); - -export const sophon = Object.freeze(ghjk.sophon); +export const ghjk = Object.freeze(file()); export const config = Object.freeze(firstCallerCheck(ghjk.config)); export const env = Object.freeze(firstCallerCheck(ghjk.env)); export const install = Object.freeze(firstCallerCheck(ghjk.install)); diff --git a/host/init/mod.ts b/host/init/mod.ts index 510c20a1..5f64ae64 100644 --- a/host/init/mod.ts +++ b/host/init/mod.ts @@ -106,12 +106,12 @@ async function handleVscodeSettings( } const schema = zod.object({ - "deno.enablePaths": zod.string().array().optional(), - "deno.disablePaths": zod.string().array().optional(), + "deno.enablePaths": zod.string().array().nullish(), + "deno.disablePaths": zod.string().array().nullish(), deno: zod.object({ - enablePaths: zod.string().array().optional(), - disablePaths: zod.string().array().optional(), - }).passthrough().optional(), + enablePaths: zod.string().array().nullish(), + disablePaths: zod.string().array().nullish(), + }).passthrough().nullish(), }).passthrough(); const originalConfig = await vscodeSettings.readJson() diff --git a/host/types.ts b/host/types.ts deleted file mode 100644 index 68283c8d..00000000 --- a/host/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { zod } from "../deps/common.ts"; -import moduleValidators from "../modules/types.ts"; - -/* const blackboard = zod.object({ - // installs: zod.record(zod.string(), portsValidator.installConfigFat), - // allowedPortDeps: zod.record(zod.string(), portsValidator.allowedPortDep), -}); */ -const blackboard = zod.record(zod.string(), zod.unknown()); - -const serializedConfig = zod.object( - { - modules: zod.array(moduleValidators.moduleManifest), - blackboard, - }, -); - -export type SerializedConfig = zod.infer; -export type Blackboard = zod.infer; - -export default { - serializedConfig, -}; diff --git a/mod.ts b/mod.ts index 681b2373..74a2b43c 100644 --- a/mod.ts +++ b/mod.ts @@ -110,17 +110,21 @@ type SecureConfigArgs = Omit< >; type DenoFileKnobs = { + /** + * The sophon is the actual proxy between the host world + * and the ghjkfile world. + */ sophon: Readonly; /** - * {@inheritdoc AddInstall} + * {@inheritDoc AddInstall} */ install: AddInstall; /** - * {@inheritdoc AddTask} + * {@inheritDoc AddTask} */ task: AddTask; /** - * {@inheritdoc AddTasks} + * {@inheritDoc AddTasks} */ tasks: AddTasks; /** diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 996970ad..c699ecf3 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -8,9 +8,8 @@ import type { EnvsModuleConfigX, WellKnownProvision, } from "./types.ts"; -import { type GhjkCtx, type ModuleManifest } from "../types.ts"; +import type { Blackboard, GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; -import type { Blackboard } from "../../host/types.ts"; import { cookPosixEnv } from "./posix.ts"; import { getPortsCtx, installGraphToSetMeta } from "../ports/inter.ts"; import type { @@ -212,7 +211,15 @@ export class EnvsModule extends ModuleBase { throw new Error(`no env found under "${envKey}"`); } // deno-lint-ignore no-console - console.log($.inspect(await showableEnv(gcx, env, envKey))); + console.log( + $.inspect( + await showableEnv( + gcx, + env, + ecx.keyToName[envKey] ?? [envKey], + ), + ), + ); }, }, ], @@ -343,7 +350,7 @@ async function reduceAndCookEnv( async function showableEnv( gcx: GhjkCtx, recipe: EnvRecipeX, - envName: string, + envName: string[], ) { const printBag = {} as Record; await using scx = await syncCtxFromGhjk(gcx); @@ -411,7 +418,7 @@ async function showableEnv( return { ...printBag, ...(recipe.desc ? { desc: recipe.desc } : {}), - envName, + envName: envName.length < 2 ? envName[0] : envName, }; } diff --git a/modules/mod.ts b/modules/mod.ts index 210079ab..65dc5a89 100644 --- a/modules/mod.ts +++ b/modules/mod.ts @@ -1,7 +1,6 @@ -import { Blackboard } from "../host/types.ts"; -import { CliCommand } from "../src/deno_systems/types.ts"; +import type { CliCommand } from "../src/deno_systems/types.ts"; import type { Json } from "../utils/mod.ts"; -import type { GhjkCtx, ModuleManifest } from "./types.ts"; +import type { Blackboard, GhjkCtx, ModuleManifest } from "./types.ts"; export abstract class ModuleBase { constructor(protected gcx: GhjkCtx) {} diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 98f9b7b2..d6835517 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -28,7 +28,7 @@ import { resolveAndInstall, syncCtxFromGhjk, } from "./sync.ts"; // TODO: rename to install.ts -import type { Blackboard } from "../../host/types.ts"; +import type { Blackboard } from "../types.ts"; import { getProvisionReducerStore } from "../envs/reducer.ts"; import { installSetReducer, installSetRefReducer } from "./reducers.ts"; import type { Provision, ProvisionReducer } from "../envs/types.ts"; diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 249c127b..97fa8471 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -118,7 +118,7 @@ const installConfigFat = stdInstallConfigFat; const installConfigResolved = installConfigLite.merge(zod.object({ // NOTE: version is no longer nullish version: zod.string(), - versionSpecified: zod.boolean().optional(), + versionSpecified: zod.boolean().nullish(), // buildDepConfigs: zod.record( // portName, // // FIXME: figure out cyclically putting `installConfigResolved` here diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index 680a43ee..0f3bc46b 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -74,7 +74,7 @@ async function importAndExec( ) { const _shimHandle = shimDenoNamespace(args.envVars); const mod = await import(uri); - const ret = await mod.sophon.execTask(args); + const ret = await mod.ghjk.sophon.execTask(args); return { data: ret, status: true }; } diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index c5349c24..84e4fe3a 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -5,11 +5,10 @@ import { Json, unwrapZodRes } from "../../utils/mod.ts"; import validators from "./types.ts"; import type { TasksModuleConfigX } from "./types.ts"; -import { type ModuleManifest } from "../types.ts"; +import { Blackboard, type ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import { buildTaskGraph, execTask, type TaskGraph } from "./exec.ts"; -import { Blackboard } from "../../host/types.ts"; import { getTasksCtx } from "./inter.ts"; import { CliCommand } from "../../src/deno_systems/types.ts"; diff --git a/modules/tasks/types.ts b/modules/tasks/types.ts index 4472aca1..35add19c 100644 --- a/modules/tasks/types.ts +++ b/modules/tasks/types.ts @@ -13,7 +13,7 @@ const taskDefBase = zod.object({ }); const taskDefFullBase = taskDefBase.merge(zod.object({ - env: envsValidators.envRecipe.optional(), + env: envsValidators.envRecipe.nullish(), })); const taskDefHashedBase = taskDefBase.merge(zod.object({ diff --git a/modules/types.ts b/modules/types.ts index 561fa2b8..516cef4b 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -11,6 +11,13 @@ const moduleManifest = zod.object({ config: zod.unknown(), }); +/* const blackboard = zod.object({ + // installs: zod.record(zod.string(), portsValidator.installConfigFat), + // allowedPortDeps: zod.record(zod.string(), portsValidator.allowedPortDep), +}); */ +const blackboard = zod.record(zod.string(), zod.unknown()); + +export type Blackboard = zod.infer; export type ModuleId = zod.infer; export type ModuleManifest = zod.infer; export type GhjkCtx = { @@ -24,4 +31,5 @@ export default { moduleManifest, moduleId, envVarName, + blackboard, }; diff --git a/ports/cargobi.ts b/ports/cargobi.ts index fb3d4683..9afa08a3 100644 --- a/ports/cargobi.ts +++ b/ports/cargobi.ts @@ -175,6 +175,13 @@ export class Port extends PortBase { } } + await $.co( + (await Array.fromAsync( + $.path(args.tmpDirPath).join("bin").walk({ maxDepth: 0 }), + )) + .map(({ path }) => path.chmod(0o700)), + ); + await std_fs.move( args.tmpDirPath, args.downloadPath, diff --git a/ports/npmi.ts b/ports/npmi.ts index 22b6ccef..2a1ed79b 100644 --- a/ports/npmi.ts +++ b/ports/npmi.ts @@ -83,8 +83,8 @@ export class Port extends PortBase { override async download(args: DownloadArgs) { const conf = confValidator.parse(args.config); - await $.raw`${ - depExecShimPath(std_ports.node_org, "npm", args.depArts) + await $.raw`${depExecShimPath(std_ports.node_org, "npm", args.depArts) + // provide prefix flat to avoid looking at package.json in parent dirs } install --prefix ${args.tmpDirPath} --no-fund ${conf.packageName}@${args.installVersion}` .cwd(args.tmpDirPath) .env(pathsWithDepArts(args.depArts, args.platform.os)); diff --git a/src/deno_systems/bindings.ts b/src/deno_systems/bindings.ts index d1f58b70..27976649 100644 --- a/src/deno_systems/bindings.ts +++ b/src/deno_systems/bindings.ts @@ -20,9 +20,12 @@ setInterval(() => {/* beat */}, 1000); // import "../../src/ghjk/js/mock.sfx.ts"; import { zod } from "../../deps/common.ts"; import { $, unwrapZodRes } from "../../utils/mod.ts"; -import type { GhjkCtx, ModuleManifest } from "../../modules/types.ts"; +import type { + Blackboard, + GhjkCtx, + ModuleManifest, +} from "../../modules/types.ts"; import type { ModuleBase } from "../../modules/mod.ts"; -import type { Blackboard } from "../../host/types.ts"; import { Ghjk, Json } from "../ghjk/js/runtime.js"; import type { @@ -41,10 +44,10 @@ import bindingTypes from "./types.ts"; const prepareArgs = zod.object({ uri: zod.string(), config: zod.object({ - ghjkfile: zod.string().optional(), + ghjkfile: zod.string().nullish(), ghjkdir: zod.string(), data_dir: zod.string(), - deno_lockfile: zod.string().optional(), + deno_lockfile: zod.string().nullish(), repo_root: zod.string(), deno_dir: zod.string(), }), diff --git a/src/deno_systems/types.ts b/src/deno_systems/types.ts index 85b0e178..796dc68a 100644 --- a/src/deno_systems/types.ts +++ b/src/deno_systems/types.ts @@ -9,7 +9,7 @@ const denoSystemsRoot = zod.object({ const charSchema = zod.string().length(1); const cliArg = zod.object({ - value_name: zod.string().optional(), + value_name: zod.string().nullish(), value_hint: zod.enum([ "Unknown", "Other", @@ -24,7 +24,7 @@ const cliArg = zod.object({ "Hostname", "Url", "EmailAddress", - ]).optional(), + ]).nullish(), action: zod.enum([ "Set", @@ -36,45 +36,45 @@ const cliArg = zod.object({ "HelpShort", "HelpLong", "Version", - ]).optional(), + ]).nullish(), - required: zod.boolean().optional(), - global: zod.boolean().optional(), - hide: zod.boolean().optional(), - exclusive: zod.boolean().optional(), - trailing_var_arg: zod.boolean().optional(), + required: zod.boolean().nullish(), + global: zod.boolean().nullish(), + hide: zod.boolean().nullish(), + exclusive: zod.boolean().nullish(), + trailing_var_arg: zod.boolean().nullish(), - env: zod.string().optional(), + env: zod.string().nullish(), - help: zod.string().optional(), - long_help: zod.string().optional(), + help: zod.string().nullish(), + long_help: zod.string().nullish(), }); const cliFlag = cliArg.extend({ - long: zod.string().optional(), - long_aliases: zod.string().array().optional(), - visible_long_aliases: zod.string().array().optional(), + long: zod.string().nullish(), + long_aliases: zod.string().array().nullish(), + visible_long_aliases: zod.string().array().nullish(), - short: charSchema.optional(), - short_aliases: charSchema.array().optional(), - visible_short_aliases: charSchema.array().optional(), + short: charSchema.nullish(), + short_aliases: charSchema.array().nullish(), + visible_short_aliases: charSchema.array().nullish(), }); const cliCommandBase = zod.object({ name: zod.string(), - aliases: zod.string().array().optional(), - visible_aliases: zod.string().array().optional(), + aliases: zod.string().array().nullish(), + visible_aliases: zod.string().array().nullish(), - hide: zod.boolean().optional(), - disable_help_subcommand: zod.boolean().optional(), + hide: zod.boolean().nullish(), + disable_help_subcommand: zod.boolean().nullish(), - about: zod.string().optional(), - before_help: zod.string().optional(), - before_long_help: zod.string().optional(), + about: zod.string().nullish(), + before_help: zod.string().nullish(), + before_long_help: zod.string().nullish(), - args: zod.record(cliArg).optional(), - flags: zod.record(cliFlag).optional(), + args: zod.record(cliArg).nullish(), + flags: zod.record(cliFlag).nullish(), }); const flagsAndArgs = zod.record( @@ -83,7 +83,7 @@ const flagsAndArgs = zod.record( zod.string().array(), zod.number(), zod.boolean(), - ]).optional(), + ]).nullish(), ); const cliActionArgs = zod.object({ @@ -94,20 +94,20 @@ const cliActionArgs = zod.object({ const cliCommandActionBase = cliCommandBase.extend({ action: zod.function() .args(cliActionArgs) - .returns(zod.union([zod.promise(zod.void()), zod.void()])).optional(), + .returns(zod.union([zod.promise(zod.void()), zod.void()])).nullish(), }); const cliCommandBindedBase = cliCommandBase.extend({ - action_cb_key: zod.string().optional(), + action_cb_key: zod.string().nullish(), }); const cliCommand: zod.ZodType = cliCommandActionBase.extend({ - sub_commands: zod.lazy(() => zod.array(cliCommand).optional()), + sub_commands: zod.lazy(() => zod.array(cliCommand).nullish()), }); const cliCommandBinded: zod.ZodType = cliCommandBindedBase .extend({ - sub_commands: zod.lazy(() => zod.array(cliCommandBinded).optional()), + sub_commands: zod.lazy(() => zod.array(cliCommandBinded).nullish()), }); type DenoSystemCtor = (gcx: GhjkCtx) => ModuleBase; @@ -117,17 +117,17 @@ export type DenoSystemsRoot = { }; export type CliCommand = zod.input & { - sub_commands?: CliCommand[]; + sub_commands?: CliCommand[] | null; }; export type CliCommandX = zod.infer & { - sub_commands?: CliCommandX[]; + sub_commands?: CliCommandX[] | null; }; export type CliCommandBinded = zod.input & { - sub_commands?: CliCommandBinded[]; + sub_commands?: CliCommandBinded[] | null; }; export type CliCommandBindedX = zod.infer & { - sub_commands?: CliCommandBindedX[]; + sub_commands?: CliCommandBindedX[] | null; }; export type CliFlag = zod.input; diff --git a/src/ghjk/cli.rs b/src/ghjk/cli.rs index 75692e17..a8d062f5 100644 --- a/src/ghjk/cli.rs +++ b/src/ghjk/cli.rs @@ -131,8 +131,11 @@ pub async fn cli() -> Res { _ = commands.action(&gcx.config, Some(&systems.config))?; return Ok(ExitCode::SUCCESS); } + Ok(QuickComands::Init { .. }) => { + unreachable!("quick_cli will prevent this") + } Ok(QuickComands::Deno { .. }) => { - unreachable!("deno quick cli will prevent this") + unreachable!("deno_quick_cli will prevent this") } Err(err) => { let kind = err.kind(); @@ -204,6 +207,7 @@ pub async fn try_quick_cli(config: &Config) -> Res> { ))); } } + QuickComands::Init { commands } => commands.action(config).await?, QuickComands::Deno { .. } => unreachable!("deno quick cli will have prevented this"), } @@ -229,11 +233,16 @@ struct Cli { #[derive(clap::Subcommand, Debug)] enum QuickComands { - /// Print different discovored or built values to stdout. + /// Print different discovored or built values to stdout Print { #[command(subcommand)] commands: PrintCommands, }, + /// Setup your working directory for ghjk usage + Init { + #[command(subcommand)] + commands: InitCommands, + }, /// Access the deno cli Deno { #[arg(raw(true))] @@ -243,17 +252,19 @@ enum QuickComands { #[derive(clap::Subcommand, Debug)] enum PrintCommands { - /// Print the path to the data dir used by ghjk. + /// Print the path to the data dir used by ghjk DataDirPath, - /// Print the path to the dir of the currently active ghjk context. + /// Print the path to the dir of the currently active ghjk context GhjkdirPath, - /// Print the path of the ghjkfile used. + /// Print the path of the ghjkfile used GhjkfilePath, - /// Print the extracted and serialized config from the ghjkfile. - Config { - /// Use json format when printing config. + /// Print the currently resolved configuration + Config, + /// Print the extracted and serialized config from the ghjkfile + Serialized { + /* /// Use json format when printing config #[arg(long)] - json: bool, + json: bool, */ }, } @@ -289,18 +300,84 @@ impl PrintCommands { eyre::bail!("no ghjkfile found."); } } - PrintCommands::Config { .. } => match serialized_config { + PrintCommands::Serialized { .. } => match serialized_config { Some(config) => { - let conf_json = serde_json::to_string_pretty(&config)?; - println!("{conf_json}"); + let serialized_json = serde_json::to_string_pretty(&config)?; + println!("{serialized_json}"); true } None => false, }, + PrintCommands::Config {} => { + let conf_json = serde_json::to_string_pretty(&cli_config)?; + println!("{conf_json}"); + true + } }) } } +#[derive(clap::Subcommand, Debug)] +enum InitCommands { + /// Create a starter typescript ghjkfile (ghjk.ts) in the current directory. + Ts { + /// Auto confirm every choice. + #[clap(long)] + yes: bool, + }, + /// Interactively configure working directory for best LSP + /// support of ghjk.ts. + TsLsp { + /// Auto confirm every choice. + #[clap(long)] + yes: bool, + }, +} + +impl InitCommands { + async fn action(self, cli_config: &Config) -> Res<()> { + match self { + InitCommands::Ts { yes } => self.init(cli_config, yes).await, + InitCommands::TsLsp { yes } => self.init_ts_lsp(cli_config, yes).await, + } + } + + async fn init(self, cli_config: &Config, yes: bool) -> Res<()> { + if let Some(path) = &cli_config.ghjkdir { + eyre::bail!( + "conflict, already in ghjkdir context located at {}", + path.display() + ); + } + /* if let Some(path) = cli_config.ghjkfile { + eyre::bail!("conflict, another ghjkfile located at {}", path.display()); + } */ + let cwd = std::env::current_dir().expect_or_log("cwd error"); + let path = cli_config + .ghjkfile + .clone() + .unwrap_or_else(|| cwd.join("ghjk.ts")); + if !crate::utils::file_exists(&path).await? { + const TEMPLATE_TS: &str = include_str!("../../examples/template.ts"); + let re = regex::Regex::new("from \"../(.*)\"; // template-import") + .expect_or_log("regex error"); + + let contents = + re.replace_all(TEMPLATE_TS, format!("from \"{}$1\";", cli_config.repo_root)); + + tokio::fs::write(&path, &contents[..]) + .await + .wrap_err_with(|| format!("error writing out ghjk.ts at {}", path.display()))?; + + info!(path = %path.display(),"written out ghjk.ts"); + } + self.init_ts_lsp(cli_config, yes).await + } + async fn init_ts_lsp(self, _cli_config: &Config, _yes: bool) -> Res<()> { + Ok(()) + } +} + type SysCmdActions = IndexMap; struct SysCmdAction { name: CHeapStr, diff --git a/src/ghjk/config.rs b/src/ghjk/config.rs index b1f6865d..b23b9d11 100644 --- a/src/ghjk/config.rs +++ b/src/ghjk/config.rs @@ -1,6 +1,6 @@ use crate::interlude::*; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Config { pub ghjkfile: Option, pub ghjkdir: Option, @@ -141,7 +141,7 @@ impl Config { if let Some(path) = &config.ghjkdir { let ignore_path = path.join(".gitignore"); - if !matches!(tokio::fs::try_exists(&ignore_path).await, Ok(true)) { + if !crate::utils::file_exists(&ignore_path).await? { tokio::fs::create_dir_all(path) .await .wrap_err_with(|| format!("error creating ghjkdir at {path:?}"))?; diff --git a/src/ghjk/host.rs b/src/ghjk/host.rs index 4cbcb10e..f7d812b4 100644 --- a/src/ghjk/host.rs +++ b/src/ghjk/host.rs @@ -112,7 +112,7 @@ pub async fn systems_from_ghjkfile( let (ghjkfile_exists, ghjkfile_hash) = if let Some(path) = &hcx.gcx.config.ghjkfile { ( - matches!(tokio::fs::try_exists(path).await, Ok(true)), + crate::utils::file_exists(path).await?, Some( hashfile::file_digest_hash(hcx.as_ref(), path) .await? diff --git a/src/ghjk/host/hashfile.rs b/src/ghjk/host/hashfile.rs index 5ec14e02..c14985ec 100644 --- a/src/ghjk/host/hashfile.rs +++ b/src/ghjk/host/hashfile.rs @@ -5,6 +5,8 @@ use super::HostCtx; #[derive(Debug, Serialize, Deserialize)] pub struct HashObj { pub version: String, + /// The cli config used during serializatin + pub cli_config: crate::config::Config, /// Hashes of all env vars that were read. pub env_var_hashes: indexmap::IndexMap>, /// Hashes of all files that were read. @@ -54,6 +56,7 @@ impl HashObj { .collect(), ) .await?, + cli_config: hcx.gcx.config.clone(), }) } @@ -72,6 +75,12 @@ impl HashObj { #[tracing::instrument(skip(hcx))] pub async fn is_stale(&self, hcx: &HostCtx) -> Res { + { + if self.cli_config != hcx.gcx.config { + trace!("stale cli config"); + return Ok(true); + } + } { let new_digest = env_var_digests( &hcx.config.env_vars, @@ -84,7 +93,7 @@ impl HashObj { } { for path in &self.listed_files { - if !matches!(tokio::fs::try_exists(path).await, Ok(true)) { + if crate::utils::file_exists(path).await? { trace!("stale listed files"); return Ok(true); } diff --git a/src/ghjk/log.rs b/src/ghjk/log.rs index 5aba03c9..0d575a6a 100644 --- a/src/ghjk/log.rs +++ b/src/ghjk/log.rs @@ -6,7 +6,7 @@ pub fn init() { let eyre_panic_hook = color_eyre::config::HookBuilder::default().display_location_section( std::env::var("RUST_ERR_LOCATION") .map(|var| var != "0") - .unwrap_or(true), + .unwrap_or(cfg!(debug_assertions)), ); #[cfg(not(debug_assertions))] @@ -16,7 +16,7 @@ at https://github.com/metatypedev/ghjk/issues/new. If you can reliably reproduce this panic, try to include the following items in your report: - Reproduction steps -- Output of meta-cli doctor and +- Output of `ghjk print config` and - A panic backtrace. Set the following environment variables as shown to enable full backtraces. - RUST_BACKTRACE=1 - RUST_LIB_BACKTRACE=full diff --git a/src/ghjk/main.rs b/src/ghjk/main.rs index f8324dd6..51592401 100644 --- a/src/ghjk/main.rs +++ b/src/ghjk/main.rs @@ -8,7 +8,7 @@ mod interlude { pub use crate::GhjkCtx; - pub use color_eyre::eyre; + pub use color_eyre::{eyre, Section, SectionExt}; pub use denort::deno::{ self, deno_runtime::{ @@ -56,12 +56,26 @@ fn main() -> Res { debug!(version = shadow::PKG_VERSION, "ghjk CLI"); - tokio::runtime::Builder::new_current_thread() + match tokio::runtime::Builder::new_current_thread() .enable_all() .build()? .block_on(cli::cli()) + { + Ok(code) => Ok(code), + Err(err) => { + let err_msg = format!("{err:?}"); + let err_msg = err_msg.split('\n').filter( + |&line| + line != "Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it." + && line != "Run with RUST_BACKTRACE=full to include source snippets." + ).join("\n"); + println!("{err_msg}"); + Ok(std::process::ExitCode::FAILURE) + } + } } +use itertools::Itertools; use shadow_rs::shadow; shadow!(shadow); diff --git a/src/ghjk/systems/deno.rs b/src/ghjk/systems/deno.rs index bee5aa85..64b60a70 100644 --- a/src/ghjk/systems/deno.rs +++ b/src/ghjk/systems/deno.rs @@ -126,6 +126,9 @@ pub async fn systems_from_deno( let mut active_worker = worker.drive_till_exit().await?; let manifests = tokio::select! { + Some(err) = exception_line.recv() => { + return Err(err).wrap_err("error setting up deno systems") + } res = &mut active_worker.exit_code_rx => { let exit_code = res .expect_or_log("channel error") diff --git a/src/ghjk/utils.rs b/src/ghjk/utils.rs index f3d7422c..6ecbd092 100644 --- a/src/ghjk/utils.rs +++ b/src/ghjk/utils.rs @@ -239,6 +239,18 @@ pub fn decode_hex_multibase(source: &str) -> eyre::Result> { } } +/// A simpler version of [`tokio::fs::try_exists`] that returns +/// false on a non-existent file and not just on a broken symlink. +#[inline(always)] +pub async fn file_exists(path: &Path) -> Result { + match tokio::fs::try_exists(path).await { + Ok(true) => Ok(true), + Ok(false) => Ok(false), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(err) => Err(err), + } +} + pub async fn find_entry_recursive(from: &Path, name: &str) -> Res> { let mut cur = from; loop { diff --git a/tests/envHooks.ts b/tests/envHooks.ts index f62ac73f..3b340fb6 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -80,7 +80,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { sophon } from "$ghjk/hack.ts"; +export { ghjk } from "$ghjk/hack.ts"; import { task, env } from "$ghjk/hack.ts"; env("main") diff --git a/tests/examples.ts b/tests/examples.ts new file mode 100644 index 00000000..3e1def7f --- /dev/null +++ b/tests/examples.ts @@ -0,0 +1,28 @@ +import "../setup_logger.ts"; +import { E2eTestCase, harness } from "./utils.ts"; + +type CustomE2eTestCase = Omit & { + stdin: string; +}; + +// TODO: check each eaxmple works + +const cases: CustomE2eTestCase[] = [{ + name: "template_ts", + stdin: ` +rm ghjk.ts .ghjk -r +ghjk init ts +ghjk sync +`, +}]; + +harness(cases.map((testCase) => ({ + ...testCase, + fs: { + "ghjk.ts": ` +export { ghjk } from "$ghjk/hack.ts"; +`, + }, + ePoints: [{ cmd: "fish", stdin: testCase.stdin }], + name: `examples/${testCase.name}`, +}))); diff --git a/tests/hashfile.ts b/tests/hashfile.ts index a7227379..bc79927d 100644 --- a/tests/hashfile.ts +++ b/tests/hashfile.ts @@ -49,6 +49,15 @@ __ghjk_get_mtime_ts .ghjk/hash.json > tstamp rm dir/one ghjk sync test (cat tstamp) -lt (__ghjk_get_mtime_ts .ghjk/hash.json); or exit 101 +`, + }, + { + name: "invalidated_cli_config_changed", + stdin: ` +__ghjk_get_mtime_ts .ghjk/hash.json > tstamp +rm dir/one +GHJK_DENO_LOCKFILLE=deno.lock ghjk sync +test (cat tstamp) -lt (__ghjk_get_mtime_ts .ghjk/hash.json); or exit 101 `, }, ]; @@ -57,7 +66,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { sophon } from "$ghjk/hack.ts"; +export { ghjk } from "$ghjk/hack.ts"; import { task, env } from "$ghjk/hack.ts"; import {stuff} from "./extra.ts" diff --git a/tests/tasks.ts b/tests/tasks.ts index 00b19eaf..39ecd4e3 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -122,7 +122,7 @@ test (cat eddy) = 'ed edd eddy' { name: "anon", ghjkTs: ` -export { sophon } from "$ghjk/hack.ts"; +export { ghjk } from "$ghjk/hack.ts"; import { task } from "$ghjk/hack.ts"; task({ @@ -149,9 +149,8 @@ test (cat eddy) = 'ed edd eddy' ghjkTs: ` import { file } from "$ghjk/hack.ts"; -const ghjk = file({}); +export const ghjk = file({}); -export const sophon = ghjk.sophon; const { env, task } = ghjk; env("main") diff --git a/tests/utils.ts b/tests/utils.ts index bc3e0237..6aaf7dbe 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -81,7 +81,7 @@ exec ${ghjkExePath.resolve().toString()} "$@"`, shellsToHook: [], }); - await $`ghjk print config` + await $`ghjk print serialized` .cwd(tmpDir.toString()) .clearEnv() .env(env); @@ -163,9 +163,7 @@ const confStr = \` ${serializedSecConf} \`; const confObj = JSON.parse(confStr); -const ghjk = file(confObj); - -export const sophon = ghjk.sophon; +export const ghjk = file(confObj); ${tasks} diff --git a/tools/dev.ts b/tools/dev.ts index d6e737c5..6284dab7 100755 --- a/tools/dev.ts +++ b/tools/dev.ts @@ -49,7 +49,7 @@ await install({ shellsToHook: [], }); -// await $`${ghjkDataDir.join("ghjk").toString()} print config` +// await $`${ghjkDataDir.join("ghjk").toString()} print serialized` // .cwd(devDir.toString()) // .clearEnv() // .env(env); diff --git a/utils/mod.ts b/utils/mod.ts index 464a43df..62d533f8 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -607,7 +607,7 @@ export async function detectShellPath(): Promise { } /** - * {@inheritdoc detectShellPath} + * {@inheritDoc detectShellPath} */ export async function detectShell(): Promise { const shellPath = await detectShellPath(); From ecfedaa734c8ed38a449bf1c75845731c65924f1 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:16:03 +0300 Subject: [PATCH 3/6] feat: `ghjk init ts-lsp` --- .ghjk/lock.json | 26 +-- .github/workflows/tests.yml | 5 +- README.md | 5 +- deno.jsonc | 1 + docs/installation-vars.md | 8 +- docs/manual.md | 25 +-- examples/env_vars/ghjk.ts | 3 +- examples/envs/ghjk.ts | 2 +- examples/kitchen/ghjk.ts | 3 +- examples/many_installs/ghjk.ts | 2 +- examples/tasks/ghjk.ts | 2 +- examples/template.ts | 3 +- files/deno/bindings.ts | 9 +- files/mod.ts | 10 +- ghjk.ts | 9 +- hack.ts | 3 +- host/init/mod.ts | 2 +- install.sh | 34 ++-- mod.ts | 125 +++++++++++---- modules/envs/mod.ts | 2 +- modules/envs/posix.ts | 8 +- modules/tasks/deno.ts | 7 +- src/ghjk/cli.rs | 250 +++-------------------------- src/ghjk/cli/init.rs | 281 +++++++++++++++++++++++++++++++++ src/ghjk/cli/print.rs | 70 ++++++++ src/ghjk/cli/sys.rs | 93 +++++++++++ src/ghjk/host.rs | 4 +- src/ghjk/host/hashfile.rs | 2 +- src/ghjk/main.rs | 5 +- src/ghjk/utils.rs | 32 ++++ tests/envHooks.ts | 2 +- tests/examples.ts | 5 +- tests/hashfile.ts | 5 +- tests/tasks.ts | 5 +- tests/utils.ts | 3 +- 35 files changed, 695 insertions(+), 356 deletions(-) create mode 100644 src/ghjk/cli/init.rs create mode 100644 src/ghjk/cli/print.rs create mode 100644 src/ghjk/cli/sys.rs diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 0d510038..fe545268 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -403,7 +403,7 @@ "lock-sed": { "ty": "denoFile@v1", "key": "lock-sed", - "envKey": "bciqocjamyeiuh6llwcdqg4q4ceantuzpbm5bmnlz7pkqz4r2ca7w2eq" + "envKey": "bciqei2rn3w6xakdaaapst7yyp36wjmz3txqtja24gl5v3fgwqcsa34i" }, "cache-v8": { "ty": "denoFile@v1", @@ -430,13 +430,13 @@ } ] }, - "bciqfnku2tswsz4gapwhys5ox5uiyzcb5r7bmuwzljjeziljcu7efroi": { + "bciqenvipb7pm4gge77liqnjhvdv7nhckd6kqjg3bhc3pc6e4g6o2zwa": { "desc": "the default default environment.", "provides": [ { "ty": "posix.envVar", "key": "RUST_LOG", - "val": "info,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" + "val": "info,runtime=debug,tokio=debug,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" }, { "ty": "ghjk.ports.InstallSetRef", @@ -444,12 +444,12 @@ } ] }, - "bciqocjamyeiuh6llwcdqg4q4ceantuzpbm5bmnlz7pkqz4r2ca7w2eq": { + "bciqei2rn3w6xakdaaapst7yyp36wjmz3txqtja24gl5v3fgwqcsa34i": { "provides": [ { "ty": "posix.envVar", "key": "RUST_LOG", - "val": "info,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" + "val": "info,runtime=debug,tokio=debug,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" }, { "ty": "ghjk.ports.InstallSetRef", @@ -457,12 +457,12 @@ } ] }, - "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca": { + "bciqm23m6kl7m2mdbjmcjoleysme4gwtkzeeqrbyrpydpm3fvx3bn25a": { "provides": [ { "ty": "posix.envVar", "key": "RUST_LOG", - "val": "info,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" + "val": "info,runtime=debug,tokio=debug,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" }, { "ty": "ghjk.ports.InstallSetRef", @@ -470,12 +470,12 @@ } ] }, - "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy": { + "bciqddyi4oxhgfvgejlmq5s4psdv3ajzc7unzyc2kq35q6nlix6c4yai": { "provides": [ { "ty": "posix.envVar", "key": "RUST_LOG", - "val": "info,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" + "val": "info,runtime=debug,tokio=debug,deno::npm=info,deno::file_fetcher=info,swc_ecma_transforms_base=info,swc_common=info,h2=info,rustls=info,mio=info,hyper_util=info" }, { "ty": "posix.envVar", @@ -491,10 +491,10 @@ }, "defaultEnv": "dev", "envsNamed": { - "main": "bciqfnku2tswsz4gapwhys5ox5uiyzcb5r7bmuwzljjeziljcu7efroi", - "_rust": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", - "ci": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", - "dev": "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy" + "main": "bciqenvipb7pm4gge77liqnjhvdv7nhckd6kqjg3bhc3pc6e4g6o2zwa", + "_rust": "bciqm23m6kl7m2mdbjmcjoleysme4gwtkzeeqrbyrpydpm3fvx3bn25a", + "ci": "bciqm23m6kl7m2mdbjmcjoleysme4gwtkzeeqrbyrpydpm3fvx3bn25a", + "dev": "bciqddyi4oxhgfvgejlmq5s4psdv3ajzc7unzyc2kq35q6nlix6c4yai" } } } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b2a08b1..91af14e3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -84,7 +84,10 @@ jobs: # need cmake to build the rust deps # need coreutils on max for the `timeout` command run: brew install fish zsh coreutils cmake - - run: deno task test + - run: | + deno task test-rust + deno task test-doc + deno task test # test-action: # runs-on: ubuntu-latest diff --git a/README.md b/README.md index d82d8af7..8dac25df 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,13 @@ Ghjk is primarily configured through constructs called "environments" or "envs" They serve as recipes for making (mostly) reproducible posix shells. ```ts +// NOTE: `ghjk.ts` files are expected to export this sophon object +export { sophon } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts"; import { file } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts"; // ports are small programs that install sowtware to your envs import * as ports from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/ports/mod.ts"; -// NOTE: `ghjk.ts` files are expected to export this ghjk object -export const ghjk = file({}); +const ghjk = file({}); // top level `install`s go to the `main` env ghjk.install(ports.protoc()); diff --git a/deno.jsonc b/deno.jsonc index dcd15170..0eb73c80 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -2,6 +2,7 @@ "tasks": { "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", "test-doc": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", + "test-rust": "cargo test", "ghjk": "cargo run -p ghjk", "cache": "deno cache deps/*", "check": "deno run -A ./tools/check.ts", diff --git a/docs/installation-vars.md b/docs/installation-vars.md index 967a944a..66a81b35 100644 --- a/docs/installation-vars.md +++ b/docs/installation-vars.md @@ -2,14 +2,10 @@ | Env vars | Desc | Default | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| `GHJK_VERSION` | Git tag/ref of the ghjk repo to install from. | Latest release tag. | -| `GHJK_SHARE_DIR` | Root directory for ghjk installation. | `$HOME/.local/share/ghjk` | +| `VERSION` | Git tag/ref of the ghjk repo to install from. | Latest release tag. | +| `GHJK_DATA_DIR` | Data directory for ghjk installation. | `$HOME/.local/share/ghjk` | | `GHJK_INSTALLER_URL` | Uri to the typescript section of installer script. | `install.ts` file from the ghjk repo under | | `GHJK_INSTALL_EXE_DIR` | Location to install the `ghjk` exec. | `$HOME/.local/bin` | -| `GHJK_INSTALL_SKIP_EXE` | Weather or not to skip install the `ghjk` CLI to `GHJK_INSTALL_EXE_DIR`. | `false` | -| `GHJK_INSTALL_DENO_EXEC` | Alternative deno exec to use. If provided, no separate Deno CLI is downloaded. It's generally preferable for ghjk to manage it's own Deno versions still. | A Deno CLI is installed to `$GHJK_SHARE_DIR/bin` | -| `DENO_VERSION` | Deno version to install if `GHJK_INSTALL_DENO_EXEC` is not test. | Deno version used for ghjk development. | | `GHJK_INSTALL_HOOK_SHELLS` | Comma separated list of shells to hook. | `bash,fish,zsh` | | `GHJK_INSTALL_HOOK_MARKER` | Marker to use when installing shell hooks. | `ghjk-hook-marker` | | | | | -| `GHJK_INSTALL_NO_LOCKFILE` | Disable use of a Deno lockfile for the ghjk program. | | diff --git a/docs/manual.md b/docs/manual.md index 2f09699c..f8a16337 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -41,11 +41,15 @@ Look through the following snippet to understand the basic structure of a `ghjk. // one's using. For example // https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/ import { file } from ".../mod.ts"; +// all ghjk.ts files are expected to export this special `sophon` object +export { sophon } from ".../mod.ts"; // import the port for the node program import node from ".../ports/node.ts"; -// all ghjk.ts files are expected to export this special `ghjk` object -export const ghjk = file(); +// Create the ghjk object using the file functiono. This modifies +// the sophon exported above and may only be called once during +// serialization. +const ghjk = file(); // install programs (ports) into your env ghjk.install( @@ -106,11 +110,10 @@ Thankfully, through the great sandbox provided through Deno's implementation, th - The contents of the ghjkfile - Files accessed during serialization - Environment variables read during serialization +- Configuration used by the ghjk cli This doesn't cover everything though and the `ghjk.ts` implementation generally assumes a declarative paradigm of programming. -You'll generally want to avoid any logic that's deterministic on inputs like time or RNGs. - -There are still a couple of glaring omissions from this list that will be addressed as ghjk matures. +You'll generally want to avoid any logic that's not deterministic and depends on inputs like time or RNGs. If you encounter any edge cases or want to force re-serialization, you can remove the hashfile at `.ghjk/hash.json` which contains hashes for change tracking. ```bash @@ -140,7 +143,7 @@ The best way to resolve ghjk merge conflicts is to: - In git, easier to remove any changes in the merge and revert to the base/HEAD branch - Re-serialize by invoking the ghjk CLI -This simple steps make sure that the _lockfile_ reflect what's in the latest _ghjkfile_ without needing to re-resolve the world. +These simple steps make sure that the _lockfile_ reflect what's in the latest _ghjkfile_ without needing to re-resolve the world. Of course, if the discarded version of the lockfile contained new versions, they'll be re-resolved possibly to a different version. But generally, if the versions specified in ghjkfile are tight enough, it'll resolve the same values as before. If versions are important, it's good to explicitly specify them in your ghjkfile. @@ -211,7 +214,7 @@ ghjk.env("my-env") By default, your ghjkfile has an env called `main`. Envs can inherit from each other and by default inherit from the `main` environment. -Inheritance is additive based for most env properties and allows easy composition. +Inheritance is additive on most env properties and allows easy composition. Please look at the [envs example](../examples/envs/ghjk.ts) or the [kitchen sink](../examples/kitchen/ghjk.ts) example which show all the knobs available on envs. You can then access the envs feature under the `envs` section of the CLI: @@ -403,15 +406,15 @@ The primarily difference between the two scenarios is how activation of envs is The standard installation script is the best way to install ghjk in CI environments. The environment [variables](./installation-vars.md) used for the installer customization come in extra handy here. Namely, it's good practice to: -- Make sure the `$GHJK_VERSION` is the one used by the ghjkfile. -- Specify `$GHJK_SHARE_DIR` to a location that can be cached by your CI tooling. This is where ports get installed. +- Make sure the `$VERSION` is the one used by the ghjkfile. +- Specify `$GHJK_DATA_DIR` to a location that can be cached by your CI tooling. This is where ports get installed. - Specify `$GHJK_INSTALL_EXE_DIR` to a location that you know will be in `$PATH`. This is where the ghjk CLI gets installed to. ```dockerfile # sample of how one would install ghjk for use in a Dockerfile ARG GHJK_VERSION=v0.3.0-rc.1 # /usr/bin is available in $PATH by default making ghjk immediately avail -RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh" \ +RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/${GHJK_VERSION}/install.sh" \ | GHJK_INSTALL_EXE_DIR=/usr/bin sh ``` @@ -419,7 +422,7 @@ RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh When working on non-interactive shells, the ghjk shell hooks are not available. This means that the default environment won't be activated for that CWD nor will any changes occur on changing directories. -It also prevents the `ghjk envs activate` command from functioning which requires that these hooks be run before each command. +It also prevents the `ghjk sync` and `ghjk envs activate` commands from functioning which requires that these hooks be run before each command. In such scenarios, one can directly `source` the activation script for the target env from the `.ghjk` directory. ```bash diff --git a/examples/env_vars/ghjk.ts b/examples/env_vars/ghjk.ts index bd9cad01..a534f00b 100644 --- a/examples/env_vars/ghjk.ts +++ b/examples/env_vars/ghjk.ts @@ -1,6 +1,7 @@ +export { sophon } from "../../hack.ts"; import { file } from "../../hack.ts"; -export const ghjk = file({ +const ghjk = file({ defaultEnv: "empty", envs: [{ name: "empty", inherit: false }], defaultBaseEnv: "empty", diff --git a/examples/envs/ghjk.ts b/examples/envs/ghjk.ts index ffb646ea..3a2e7c5d 100644 --- a/examples/envs/ghjk.ts +++ b/examples/envs/ghjk.ts @@ -1,4 +1,4 @@ -export { ghjk } from "../../hack.ts"; +export { sophon } from "../../hack.ts"; import { config, env, install, task } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index 3147abeb..0430240e 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -1,11 +1,12 @@ import { stdDeps } from "../../files/mod.ts"; +export { sophon } from "../../mod.ts"; import { file } from "../../mod.ts"; import * as ports from "../../ports/mod.ts"; // we need this export for this file to be a valid ghjkfile // it's the one thing used by the ghjk host implementation to // interact with your ghjkfile -export const ghjk = file({ +const ghjk = file({ // configre an empty env so that no ports are avail by default in our workdir defaultEnv: "empty", envs: [{ name: "empty", inherit: false }], diff --git a/examples/many_installs/ghjk.ts b/examples/many_installs/ghjk.ts index 60f79862..b5a3a154 100644 --- a/examples/many_installs/ghjk.ts +++ b/examples/many_installs/ghjk.ts @@ -1,4 +1,4 @@ -export { ghjk } from "../../hack.ts"; +export { sophon } from "../../hack.ts"; import { config, install } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts index 20eb53b4..61b227f4 100644 --- a/examples/tasks/ghjk.ts +++ b/examples/tasks/ghjk.ts @@ -1,4 +1,4 @@ -export { ghjk } from "../../hack.ts"; +export { sophon } from "../../hack.ts"; import { logger, task } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; diff --git a/examples/template.ts b/examples/template.ts index 5f53f889..9f7958b6 100644 --- a/examples/template.ts +++ b/examples/template.ts @@ -1,10 +1,11 @@ // @ts-nocheck: Deno based +export { sophon } from "../mod.ts"; // template-import import { file } from "../mod.ts"; // template-import // import * as ports from "../ports/mod.ts"; // template-import // This export is necessary for typescript ghjkfiles -export const ghjk = file({ +const ghjk = file({ // allows usage of ports that depend on node/python // enableRuntimes: true, }); diff --git a/files/deno/bindings.ts b/files/deno/bindings.ts index 530b57c2..e294f45a 100644 --- a/files/deno/bindings.ts +++ b/files/deno/bindings.ts @@ -11,6 +11,8 @@ import { shimDenoNamespace } from "../../utils/worker.ts"; import { zod } from "../../deps/common.ts"; import { Ghjk } from "../../src/ghjk/js/runtime.js"; +// TODO: shim Deno.exit to avoid killing whole program + const serializeArgs = zod.object({ uri: zod.string(), }); @@ -20,15 +22,12 @@ async function serialize(args: zod.infer) { const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(args.uri); - if (!mod.ghjk) { - throw new Error(`ghjk.ts does not export a ghjk object: ${args.uri}`); - } - if (!mod.ghjk.sophon) { + if (!mod.sophon) { throw new Error( `no sophon found on exported ghjk object from ghjk.ts: ${args.uri}`, ); } - const rawConfig = await mod.ghjk.sophon.getConfig(args.uri, mod.secureConfig); + const rawConfig = await mod.sophon.getConfig(args.uri, mod.secureConfig); const config = JSON.parse(JSON.stringify(rawConfig)); return { config, diff --git a/files/mod.ts b/files/mod.ts index 350f31bb..64f08a6d 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -555,16 +555,16 @@ export class Ghjkfile { envsNamed: {}, }; const workingSet = indie; - // console.log({ - // indie, - // deps, - // }); + /* $.dbg("graph", { + indie, + deps, + }); */ while (workingSet.length > 0) { const item = workingSet.pop()!; const final = all[item]; const base = this.#mergeEnvs(final.envBaseResolved ?? [], final.key); - // console.log({ parents: final.envBaseResolved, child: final.key, base }); + // $.dbg("processing", { parents: final.envBaseResolved, child: final.key, base }); const finalVars = { ...base.vars, diff --git a/ghjk.ts b/ghjk.ts index 2b2c5a3f..8fe395af 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,6 +1,7 @@ // @ts-nocheck: Ghjkfile based on Deno -import { file } from "./mod.ts"; +export { sophon } from "./mod.ts"; +import { $, file } from "./mod.ts"; import * as ports from "./ports/mod.ts"; import { switchMap } from "./port.ts"; @@ -9,7 +10,7 @@ import { downloadFile, DownloadFileArgs } from "./utils/mod.ts"; import { unarchive } from "./utils/unarchive.ts"; import dummy from "./ports/dummy.ts"; -export const ghjk = file({}); +const ghjk = file({}); const DENO_VERSION = "2.1.2"; // keep in sync with the deno repo's ./rust-toolchain.toml @@ -81,8 +82,8 @@ ghjk.env("main") // "deno", ], "DEBUG": [ - // "runtime", - // "tokio", + "runtime", + "tokio", ], "INFO": [ "deno::npm", diff --git a/hack.ts b/hack.ts index 4b6a948a..7212e0d5 100644 --- a/hack.ts +++ b/hack.ts @@ -5,10 +5,11 @@ //! import the functions defined herin and mess with your ghjkfile. export * from "./mod.ts"; +export { sophon } from "./mod.ts"; import { file } from "./mod.ts"; import logger from "./utils/logger.ts"; -export const ghjk = Object.freeze(file()); +const ghjk = file(); export const config = Object.freeze(firstCallerCheck(ghjk.config)); export const env = Object.freeze(firstCallerCheck(ghjk.env)); export const install = Object.freeze(firstCallerCheck(ghjk.install)); diff --git a/host/init/mod.ts b/host/init/mod.ts index 5f64ae64..fd27fc72 100644 --- a/host/init/mod.ts +++ b/host/init/mod.ts @@ -122,7 +122,7 @@ async function handleVscodeSettings( }); const parsedConfig = unwrapZodRes(schema.safeParse(originalConfig), { originalConfig, - }, "unexpected JSON discovored at .vscode/settings.json"); + }, "unexpected JSON discovered at .vscode/settings.json"); let writeOut = false; diff --git a/install.sh b/install.sh index ae17dc85..8d08373d 100755 --- a/install.sh +++ b/install.sh @@ -25,13 +25,13 @@ LATEST_VERSION=$(curl "$RELEASE_URL/latest" -s -L -I -o /dev/null -w '%{url_effe PLATFORM="${PLATFORM:-}" TMP_DIR=$(mktemp -d) -GHJK_INSTALL_EXEC_DIR="${GHJK_INSTALL_EXEC_DIR:-$HOME/.local/bin}" +GHJK_INSTALL_EXE_DIR="${GHJK_INSTALL_EXE_DIR:-$HOME/.local/bin}" VERSION="${VERSION:-$LATEST_VERSION}" # make sure the version is prepended with v if [ "${VERSION#"v"}" = "$VERSION" ]; then cat >&2 < bash EOF exit 1 fi @@ -101,34 +101,34 @@ fi tar -C "$TMP_DIR" -xvzf "$TMP_DIR/$ASSET.$EXT" "$EXE" chmod +x "$TMP_DIR/$EXE" -if [ "${GHJK_INSTALL_EXEC_DIR}" = "." ]; then +if [ "${GHJK_INSTALL_EXE_DIR}" = "." ]; then mv "$TMP_DIR/$EXE" . printf "\n\n%s has been extracted to your current directory\n" "$EXE" else cat <&2 read -r _throwaway - mv "$TMP_DIR/$EXE" "$GHJK_INSTALL_EXEC_DIR" + mv "$TMP_DIR/$EXE" "$GHJK_INSTALL_EXE_DIR" rm -r "$TMP_DIR" else - echo "$GHJK_INSTALL_EXEC_DIR is not writable." + echo "$GHJK_INSTALL_EXE_DIR is not writable." exit 1 fi fi GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/$ORG/$REPO/$VERSION/install.ts}" -"$GHJK_INSTALL_EXEC_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" +"$GHJK_INSTALL_EXE_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" SHELL_TYPE=$(basename "$SHELL") @@ -153,10 +153,10 @@ if [ -n "$SHELL_CONFIG" ]; then case $SHELL_TYPE in bash|zsh|ksh) - APPEND_CMD="export PATH=\"$GHJK_INSTALL_EXEC_DIR:\$PATH\"" + APPEND_CMD="export PATH=\"$GHJK_INSTALL_EXE_DIR:\$PATH\"" ;; fish) - APPEND_CMD="fish_add_path $GHJK_INSTALL_EXEC_DIR" + APPEND_CMD="fish_add_path $GHJK_INSTALL_EXE_DIR" ;; esac @@ -166,10 +166,10 @@ if [ -n "$SHELL_CONFIG" ]; then else cat <; type DenoFileKnobs = { - /** - * The sophon is the actual proxy between the host world - * and the ghjkfile world. - */ sophon: Readonly; /** * {@inheritDoc AddInstall} @@ -137,14 +133,51 @@ type DenoFileKnobs = { config(args: SecureConfigArgs): void; }; -export const file = Object.freeze(function file( - args: FileArgs = {}, +const builder = new Ghjkfile(); +// We need this in the module scope since +// both sophon.getConfig and setupGhjkts +// need to access it +let args: FileArgs | undefined; + +const DEFAULT_BASE_ENV_NAME = "main"; +/** + * The sophon is the actual proxy between the host world + * and the ghjkfile world. + */ +export const sophon = Object.freeze({ + // FIXME: ses.lockdown to freeze primoridials + // freeze the object to prevent malicious tampering of the secureConfig + getConfig: Object.freeze( + ( + ghjkfileUrl: string, + ) => { + if (!args) { + logger().warn( + "ghjk.ts has not called the `file` function even once.", + ); + } + return builder.toConfig({ + ghjkfileUrl, + defaultEnv: args?.defaultEnv ?? DEFAULT_BASE_ENV_NAME, + defaultBaseEnv: args?.defaultBaseEnv ?? + DEFAULT_BASE_ENV_NAME, + }); + }, + ), + execTask: Object.freeze( + // TODO: do we need to source the default base env from + // the secure config here? + (args: ExecTaskArgs) => builder.execTask(args), + ), +}); + +function setupGhjkts( + fileArgs: FileArgs = {}, ): DenoFileKnobs { const defaultBuildDepsSet: AllowedPortDep[] = []; - const DEFAULT_BASE_ENV_NAME = "main"; + args = fileArgs; - const builder = new Ghjkfile(); const mainEnv = builder.addEnv(DEFAULT_BASE_ENV_NAME, { name: DEFAULT_BASE_ENV_NAME, inherit: args.defaultBaseEnv && args.defaultBaseEnv != DEFAULT_BASE_ENV_NAME @@ -210,28 +243,6 @@ export const file = Object.freeze(function file( builder.addTask({ name, ...def, ty: "denoFile@v1" }); } - // FIXME: ses.lockdown to freeze primoridials - // freeze the object to prevent malicious tampering of the secureConfig - const sophon = Object.freeze({ - getConfig: Object.freeze( - ( - ghjkfileUrl: string, - ) => { - return builder.toConfig({ - ghjkfileUrl, - defaultEnv: args.defaultEnv ?? DEFAULT_BASE_ENV_NAME, - defaultBaseEnv: args.defaultBaseEnv ?? - DEFAULT_BASE_ENV_NAME, - }); - }, - ), - execTask: Object.freeze( - // TODO: do we need to source the default base env from - // the secure config here? - (args: ExecTaskArgs) => builder.execTask(args), - ), - }); - function task( nameOrArgsOrFn: string | DenoTaskDefArgs | TaskFn, argsOrFn?: Omit | TaskFn, @@ -311,10 +322,62 @@ export const file = Object.freeze(function file( ) { mainEnv.inherit(newArgs.defaultBaseEnv); } - // NOTE:we're deep mutating the first args from above + // NOTE:we're deep mutating the global args from above args = { ...newArgs, }; }, }; +} + +let fileCreated = false; +const exitFn = Deno.exit; +let firstCaller: string | undefined; + +export const file = Object.freeze(function file( + fileArgs: FileArgs = {}, +): DenoFileKnobs { + const caller = getCaller(); + if (fileCreated) { + logger().error( + `double \`file\` invocation detected detected at ${caller} after being first called at ${firstCaller}.` + + ` A ghjkfile can only invoke \`file\` once, exiting.`, + ); + exitFn(1); + } + fileCreated = true; + firstCaller = caller; + return setupGhjkts(fileArgs); }); + +// lifted from https://github.com/apiel/caller/blob/ead98/caller.ts +// MIT License 2020 Alexander Piel +interface Bind { + cb?: (file: string) => string; +} +function getCaller(this: Bind | any, levelUp = 3) { + const err = new Error(); + const stack = err.stack?.split("\n")[levelUp]; + if (stack) { + return getFile.bind(this)(stack); + } + function getFile(this: Bind | any, stack: string): string { + stack = stack.substring(stack.indexOf("at ") + 3); + if (!stack.startsWith("file://")) { + stack = stack.substring(stack.lastIndexOf("(") + 1); + } + const path = stack.split(":"); + let file; + if (Deno.build.os == "windows") { + file = `${path[0]}:${path[1]}:${path[2]}`; + } else { + file = `${path[0]}:${path[1]}`; + } + + if ((this as Bind)?.cb) { + const cb = (this as Bind).cb as any; + file = cb(file); + } + return file; + } +} diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index c699ecf3..748bd1d5 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -418,7 +418,7 @@ async function showableEnv( return { ...printBag, ...(recipe.desc ? { desc: recipe.desc } : {}), - envName: envName.length < 2 ? envName[0] : envName, + envName, }; } diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 323fa055..b1e9ccb2 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -235,8 +235,8 @@ async function writeActivators( `# shellcheck disable=SC2016`, `# SC2016: disabled because single quoted expressions are used for the cleanup scripts`, ``, - `# this file bust be sourced from an existing sh/bash/zsh session using the \`source\` command`, - `# it cannot be executed directly`, + `# this file must be sourced from an existing sh/bash/zsh session using the \`source\` command`, + `# it should be executed directly`, ``, `ghjk_deactivate () {`, ` if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then`, @@ -327,8 +327,8 @@ async function writeActivators( // // fish version fish: [ - `# this file bust be sourced from an existing fish session using the \`source\` command`, - `# it cannot be executed directly`, + `# this file must be sourced from an existing fish session using the \`source\` command`, + `# it should be executed directly`, ``, `function ghjk_deactivate`, ` if set --query GHJK_CLEANUP_FISH`, diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index 0f3bc46b..dedd01c6 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -74,7 +74,12 @@ async function importAndExec( ) { const _shimHandle = shimDenoNamespace(args.envVars); const mod = await import(uri); - const ret = await mod.ghjk.sophon.execTask(args); + if (!mod.sophon) { + throw new Error( + `no sophon found on exported ghjk object from ghjk.ts when executing task: ${uri}`, + ); + } + const ret = await mod.sophon.execTask(args); return { data: ret, status: true }; } diff --git a/src/ghjk/cli.rs b/src/ghjk/cli.rs index a8d062f5..4f26f557 100644 --- a/src/ghjk/cli.rs +++ b/src/ghjk/cli.rs @@ -1,13 +1,16 @@ +use crate::interlude::*; + use std::process::ExitCode; use clap::builder::styling::AnsiColor; use crate::config::Config; -use crate::interlude::*; - -use crate::systems::{CliCommandAction, SystemCliCommand}; use crate::{host, systems}; +mod init; +mod print; +mod sys; + const DENO_UNSTABLE_FLAGS: &[&str] = &["worker-options", "kv"]; pub async fn cli() -> Res { @@ -104,7 +107,7 @@ pub async fn cli() -> Res { debug!("collecting system commands"); - let (sys_cmds, sys_actions) = match commands_from_systems(&systems).await { + let (sys_cmds, sys_actions) = match sys::commands_from_systems(&systems).await { Ok(val) => val, Err(err) => { systems.write_lockfile_or_log().await; @@ -151,14 +154,14 @@ pub async fn cli() -> Res { } } - let (cmd_path, mut action, action_matches) = match action_for_match(sys_actions, &matches).await - { - Ok(val) => val, - Err(err) => { - systems.write_lockfile_or_log().await; - return Err(err); - } - }; + let (cmd_path, mut action, action_matches) = + match sys::action_for_match(sys_actions, &matches).await { + Ok(val) => val, + Err(err) => { + systems.write_lockfile_or_log().await; + return Err(err); + } + }; debug!(?cmd_path, "system command found"); let Some(action) = action.action else { @@ -233,15 +236,15 @@ struct Cli { #[derive(clap::Subcommand, Debug)] enum QuickComands { - /// Print different discovored or built values to stdout + /// Print different discovered or built values to stdout Print { #[command(subcommand)] - commands: PrintCommands, + commands: print::PrintCommands, }, /// Setup your working directory for ghjk usage Init { #[command(subcommand)] - commands: InitCommands, + commands: init::InitCommands, }, /// Access the deno cli Deno { @@ -250,223 +253,6 @@ enum QuickComands { }, } -#[derive(clap::Subcommand, Debug)] -enum PrintCommands { - /// Print the path to the data dir used by ghjk - DataDirPath, - /// Print the path to the dir of the currently active ghjk context - GhjkdirPath, - /// Print the path of the ghjkfile used - GhjkfilePath, - /// Print the currently resolved configuration - Config, - /// Print the extracted and serialized config from the ghjkfile - Serialized { - /* /// Use json format when printing config - #[arg(long)] - json: bool, */ - }, -} - -impl PrintCommands { - /// The return value specifies weather or not the CLI is done or - /// weather it should continue on with serialization if this - /// action was invoked as part of the quick cli - fn action( - self, - cli_config: &Config, - serialized_config: Option<&host::SerializedConfig>, - ) -> Res { - Ok(match self { - PrintCommands::DataDirPath => { - println!("{}", cli_config.data_dir.display()); - true - } - // TODO: rename GHJK_DIR to GHJKDIR - PrintCommands::GhjkdirPath => { - if let Some(path) = &cli_config.ghjkdir { - // TODO: graceful termination on SIGPIPE - println!("{}", path.display()); - true - } else { - eyre::bail!("no ghjkdir found."); - } - } - PrintCommands::GhjkfilePath => { - if let Some(path) = &cli_config.ghjkdir { - println!("{}", path.display()); - true - } else { - eyre::bail!("no ghjkfile found."); - } - } - PrintCommands::Serialized { .. } => match serialized_config { - Some(config) => { - let serialized_json = serde_json::to_string_pretty(&config)?; - println!("{serialized_json}"); - true - } - None => false, - }, - PrintCommands::Config {} => { - let conf_json = serde_json::to_string_pretty(&cli_config)?; - println!("{conf_json}"); - true - } - }) - } -} - -#[derive(clap::Subcommand, Debug)] -enum InitCommands { - /// Create a starter typescript ghjkfile (ghjk.ts) in the current directory. - Ts { - /// Auto confirm every choice. - #[clap(long)] - yes: bool, - }, - /// Interactively configure working directory for best LSP - /// support of ghjk.ts. - TsLsp { - /// Auto confirm every choice. - #[clap(long)] - yes: bool, - }, -} - -impl InitCommands { - async fn action(self, cli_config: &Config) -> Res<()> { - match self { - InitCommands::Ts { yes } => self.init(cli_config, yes).await, - InitCommands::TsLsp { yes } => self.init_ts_lsp(cli_config, yes).await, - } - } - - async fn init(self, cli_config: &Config, yes: bool) -> Res<()> { - if let Some(path) = &cli_config.ghjkdir { - eyre::bail!( - "conflict, already in ghjkdir context located at {}", - path.display() - ); - } - /* if let Some(path) = cli_config.ghjkfile { - eyre::bail!("conflict, another ghjkfile located at {}", path.display()); - } */ - let cwd = std::env::current_dir().expect_or_log("cwd error"); - let path = cli_config - .ghjkfile - .clone() - .unwrap_or_else(|| cwd.join("ghjk.ts")); - if !crate::utils::file_exists(&path).await? { - const TEMPLATE_TS: &str = include_str!("../../examples/template.ts"); - let re = regex::Regex::new("from \"../(.*)\"; // template-import") - .expect_or_log("regex error"); - - let contents = - re.replace_all(TEMPLATE_TS, format!("from \"{}$1\";", cli_config.repo_root)); - - tokio::fs::write(&path, &contents[..]) - .await - .wrap_err_with(|| format!("error writing out ghjk.ts at {}", path.display()))?; - - info!(path = %path.display(),"written out ghjk.ts"); - } - self.init_ts_lsp(cli_config, yes).await - } - async fn init_ts_lsp(self, _cli_config: &Config, _yes: bool) -> Res<()> { - Ok(()) - } -} - -type SysCmdActions = IndexMap; -struct SysCmdAction { - name: CHeapStr, - clap: clap::Command, - action: Option, - sub_commands: SysCmdActions, -} - -async fn commands_from_systems( - systems: &host::GhjkfileSystems, -) -> Res<(Vec, SysCmdActions)> { - fn inner(cmd: SystemCliCommand) -> (SysCmdAction, clap::Command) { - // apply styles here due to propagation - // breaking for these dynamic subcommands for some reason - let mut clap_cmd = cmd.clap.styles(CLAP_STYLE); - let mut sub_commands = IndexMap::new(); - for (id, cmd) in cmd.sub_commands { - let (sub_sys_cmd, sub_cmd) = inner(cmd); - clap_cmd = clap_cmd.subcommand(sub_cmd); - sub_commands.insert(id, sub_sys_cmd); - } - ( - SysCmdAction { - clap: clap_cmd.clone(), - name: cmd.name, - action: cmd.action, - sub_commands, - }, - clap_cmd, - ) - } - let mut commands = vec![]; - let mut conflict_tracker = HashMap::new(); - let mut actions = SysCmdActions::new(); - for (id, sys_inst) in &systems.sys_instances { - let cmds = sys_inst - .commands() - .await - .wrap_err_with(|| format!("error getting commands for system: {id}"))?; - for cmd in cmds { - let (sys_cmd, clap_cmd) = inner(cmd); - - if let Some(conflict) = conflict_tracker.insert(sys_cmd.name.clone(), id) { - eyre::bail!( - "system commannd conflict under name {:?} for modules {conflict:?} and {id:?}", - sys_cmd.name.clone(), - ); - } - actions.insert(sys_cmd.name.clone(), sys_cmd); - commands.push(clap_cmd); - } - } - Ok((commands, actions)) -} - -async fn action_for_match( - mut actions: SysCmdActions, - matches: &clap::ArgMatches, -) -> Res<(Vec, SysCmdAction, &clap::ArgMatches)> { - fn inner<'a>( - mut current: SysCmdAction, - matches: &'a clap::ArgMatches, - cmd_path: &mut Vec, - ) -> Res<(SysCmdAction, &'a clap::ArgMatches)> { - match matches.subcommand() { - Some((cmd_name, matches)) => { - cmd_path.push(cmd_name.into()); - match current.sub_commands.swap_remove(cmd_name) { - Some(action) => inner(action, matches, cmd_path), - None => { - eyre::bail!("no match found for cmd {cmd_path:?}") - } - } - } - None => Ok((current, matches)), - } - } - let mut cmd_path = vec![]; - let Some((cmd_name, matches)) = matches.subcommand() else { - unreachable!("clap prevents this branch") - }; - cmd_path.push(cmd_name.into()); - let Some(action) = actions.swap_remove(cmd_name) else { - eyre::bail!("no match found for cmd {cmd_path:?}"); - }; - let (action, matches) = inner(action, matches, &mut cmd_path)?; - Ok((cmd_path, action, matches)) -} - /// TODO: keep more of this in deno next time it's updated pub fn deno_quick_cli() -> Option<()> { let argv = std::env::args_os().skip(1).collect::>(); diff --git a/src/ghjk/cli/init.rs b/src/ghjk/cli/init.rs new file mode 100644 index 00000000..c067f76b --- /dev/null +++ b/src/ghjk/cli/init.rs @@ -0,0 +1,281 @@ +use crate::interlude::*; + +use crate::config::Config; + +use std::io::IsTerminal; + +#[derive(clap::Subcommand, Debug)] +pub enum InitCommands { + /// Create a starter typescript ghjkfile (ghjk.ts) in the current directory. + Ts { + /// Auto confirm every choice. + #[clap(long)] + yes: bool, + }, + /// Interactively configure working directory for best LSP + /// support of ghjk.ts. + TsLsp { + /// Auto confirm every choice. + #[clap(long)] + yes: bool, + }, +} + +impl InitCommands { + pub async fn action(self, cli_config: &Config) -> Res<()> { + match self { + InitCommands::Ts { yes } => self.init(cli_config, yes).await, + InitCommands::TsLsp { yes } => self.init_ts_lsp(cli_config, yes).await, + } + } + + async fn init(self, cli_config: &Config, yes: bool) -> Res<()> { + if let Some(path) = &cli_config.ghjkdir { + eyre::bail!( + "conflict, already in ghjkdir context located at {}", + path.display() + ); + } + /* if let Some(path) = cli_config.ghjkfile { + eyre::bail!("conflict, another ghjkfile located at {}", path.display()); + } */ + let cwd = std::env::current_dir().expect_or_log("cwd error"); + let path = cli_config + .ghjkfile + .clone() + .unwrap_or_else(|| cwd.join("ghjk.ts")); + if !crate::utils::file_exists(&path).await? { + const TEMPLATE_TS: &str = include_str!("../../../examples/template.ts"); + let re = regex::Regex::new("from \"../(.*)\"; // template-import") + .expect_or_log("regex error"); + + let contents = + re.replace_all(TEMPLATE_TS, format!("from \"{}$1\";", cli_config.repo_root)); + + tokio::fs::write(&path, &contents[..]) + .await + .wrap_err_with(|| format!("error writing out ghjk.ts at {}", path.display()))?; + + info!(path = %path.display(),"written out ghjk.ts"); + } + self.init_ts_lsp(cli_config, yes).await + } + + async fn init_ts_lsp(self, cli_config: &Config, yes: bool) -> Res<()> { + let cwd = cli_config + .ghjkdir + .as_ref() + .map(|path| path.parent().unwrap().to_owned()) + .unwrap_or_else(|| std::env::current_dir().expect_or_log("cwd error")); + let ghjkfile_path = cli_config + .ghjkfile + .clone() + .unwrap_or_else(|| cwd.join("ghjk.ts")); + + let change_vscode_settings = yes + || std::io::stderr().is_terminal() + && tokio::task::spawn_blocking({ + let path = ghjkfile_path.clone(); + move || { + dialoguer::Confirm::new() + .with_prompt(format!( + "Configure deno lsp to selectively enable on {} through .vscode/settings.json (no support for json5)?", + path.clone().display() + )) + .default(true) + .interact() + } + }) + .await + .expect_or_log("tokio error") + .wrap_err("prompt error")?; + + if change_vscode_settings { + let default = ".vscode/settings.json".to_owned(); + let vscode_path_raw = if std::io::stderr().is_terminal() { + tokio::task::spawn_blocking(move || { + dialoguer::Input::new() + .with_prompt("Path to .vscode/settings.json ghjk working dir") + .default(default) + .interact_text() + }) + .await + .expect_or_log("tokio error") + .wrap_err("prompt error")? + } else { + default + }; + handle_vscode_settings( + ghjkfile_path.clone(), + cwd.join(vscode_path_raw), + cwd.clone(), + ) + .await + .wrap_err("error modifying vscode settings")?; + } + + if crate::utils::file_exists(&ghjkfile_path).await? { + let content = tokio::fs::read_to_string(&ghjkfile_path) + .await + .wrap_err_with(|| { + format!("error reading ghjkfile at {}", ghjkfile_path.display()) + })?; + let re = regex::Regex::new("@ts-nocheck").expect_or_log("regex error"); + if !re.is_match(&content) { + let change_ghjkts = yes + || std::io::stderr().is_terminal() + && tokio::task::spawn_blocking({ + let path = ghjkfile_path.clone(); + move || { + dialoguer::Confirm::new() + .with_prompt(format!( + "Mark {} with @ts-nocheck?", + path.clone().display() + )) + .default(true) + .interact() + } + }) + .await + .expect_or_log("tokio error") + .wrap_err("prompt error")?; + + if change_ghjkts { + let content = format!( + r#" +// @ts-nocheck: Ghjkfile based on Deno + +{content}"# + ); + tokio::fs::write(&ghjkfile_path, content).await?; + info!("Added @ts-nocheck mark to {}", ghjkfile_path.display()); + } + } else { + info!( + "@ts-nocheck detected in {}, skipping", + ghjkfile_path.display() + ); + } + } + Ok(()) + } +} + +async fn handle_vscode_settings( + ghjkfile_path: PathBuf, + vscode_path: PathBuf, + cwd: PathBuf, +) -> Res<()> { + if !crate::utils::file_exists(&vscode_path).await? { + warn!( + "No file found at {}, creating a new one.", + vscode_path.display() + ); + + let config = json!({ + "deno.enablePaths": [ + ghjkfile_path, + ], + }); + + if let Some(parent) = vscode_path.parent() { + tokio::fs::create_dir_all(parent).await?; + } + + tokio::fs::write( + &vscode_path, + serde_json::to_vec(&config).expect_or_log("json error"), + ) + .await?; + + info!("Wrote config to {}", vscode_path.display()); + + return Ok(()); + } + + let found_conf_raw = tokio::fs::read(&vscode_path).await?; + let found_conf_raw: serde_json::Value = serde_json::from_slice(&found_conf_raw) + .wrap_err_with(|| format!("error parsing json at {}", vscode_path.display()))?; + + #[derive(Deserialize, Serialize)] + struct DenoSection { + #[serde(rename = "enablePaths")] + enable_paths: Option>, + #[serde(rename = "disablePaths")] + disable_paths: Option>, + } + #[derive(Deserialize, Serialize)] + struct RelevantSettings { + #[serde(rename = "deno.enablePaths")] + enable_paths_base: Option>, + #[serde(rename = "deno.disablePaths")] + disable_paths_base: Option>, + deno: Option, + } + + let mut relevant_conf: RelevantSettings = serde_json::from_value(found_conf_raw.clone()) + .wrap_err("expecting root to be a JSON object") + .wrap_err("error parsing vscode settings json")?; + + let mut write_out = false; + + // Do some basic sanity checks + if let Some(paths) = relevant_conf.disable_paths_base.as_mut() { + if paths.iter().any(|path| cwd.join(path) == ghjkfile_path) { + eyre::bail!( + "{} detected in \"deno.disablePaths\". Confused :/", + ghjkfile_path.display() + ); + } + } + if let Some(Some(paths)) = relevant_conf.deno.as_mut().map(|deno| &deno.disable_paths) { + if paths.iter().any(|path| cwd.join(path) == ghjkfile_path) { + eyre::bail!( + "{} detected in \"deno.disablePaths\". Confused :/", + ghjkfile_path.display() + ); + } + } + + if let Some(paths) = relevant_conf.enable_paths_base.as_mut() { + if !paths.iter().any(|path| cwd.join(path) == ghjkfile_path) { + info!("Adding {} to \"deno.enablePaths\"", ghjkfile_path.display()); + paths.push(ghjkfile_path.clone()); + write_out = true; + } else { + info!( + "Detected {} in deno.enablePaths, skipping", + ghjkfile_path.display() + ); + } + } else if let Some(Some(paths)) = relevant_conf + .deno + .as_mut() + .map(|deno| &mut deno.enable_paths) + { + if !paths.iter().any(|path| cwd.join(path) == ghjkfile_path) { + info!("Adding {} to deno.enablePaths", ghjkfile_path.display()); + paths.push(ghjkfile_path.clone()); + write_out = true; + } else { + info!( + "Detected {} in deno.enablePaths, skipping", + ghjkfile_path.display() + ); + } + } else { + relevant_conf.enable_paths_base = Some(vec![ghjkfile_path.clone()]); + info!("Adding {} to \"deno.enablePaths\"", ghjkfile_path.display()); + write_out = true; + } + if write_out { + let out_json = found_conf_raw.destructure_into_self(json!(relevant_conf)); + let out_json = serde_json::to_vec_pretty(&out_json).expect_or_log("json error"); + + tokio::fs::write(&vscode_path, out_json).await?; + + info!("Wrote .vscode settings to {}", vscode_path.display()); + } + + Ok(()) +} diff --git a/src/ghjk/cli/print.rs b/src/ghjk/cli/print.rs new file mode 100644 index 00000000..9367fb5e --- /dev/null +++ b/src/ghjk/cli/print.rs @@ -0,0 +1,70 @@ +use crate::interlude::*; + +use crate::config::Config; + +#[derive(clap::Subcommand, Debug)] +pub enum PrintCommands { + /// Print the path to the data dir used by ghjk + DataDirPath, + /// Print the path to the dir of the currently active ghjk context + GhjkdirPath, + /// Print the path of the ghjkfile used + GhjkfilePath, + /// Print the currently resolved configuration + Config, + /// Print the extracted and serialized config from the ghjkfile + Serialized { + /* /// Use json format when printing config + #[arg(long)] + json: bool, */ + }, +} + +impl PrintCommands { + /// The return value specifies weather or not the CLI is done or + /// weather it should continue on with serialization if this + /// action was invoked as part of the quick cli + pub fn action( + self, + cli_config: &Config, + serialized_config: Option<&crate::host::SerializedConfig>, + ) -> Res { + Ok(match self { + PrintCommands::DataDirPath => { + println!("{}", cli_config.data_dir.display()); + true + } + // TODO: rename GHJK_DIR to GHJKDIR + PrintCommands::GhjkdirPath => { + if let Some(path) = &cli_config.ghjkdir { + // TODO: graceful termination on SIGPIPE + println!("{}", path.display()); + true + } else { + eyre::bail!("no ghjkdir found."); + } + } + PrintCommands::GhjkfilePath => { + if let Some(path) = &cli_config.ghjkdir { + println!("{}", path.display()); + true + } else { + eyre::bail!("no ghjkfile found."); + } + } + PrintCommands::Serialized { .. } => match serialized_config { + Some(config) => { + let serialized_json = serde_json::to_string_pretty(&config)?; + println!("{serialized_json}"); + true + } + None => false, + }, + PrintCommands::Config {} => { + let conf_json = serde_json::to_string_pretty(&cli_config)?; + println!("{conf_json}"); + true + } + }) + } +} diff --git a/src/ghjk/cli/sys.rs b/src/ghjk/cli/sys.rs new file mode 100644 index 00000000..841f5f70 --- /dev/null +++ b/src/ghjk/cli/sys.rs @@ -0,0 +1,93 @@ +use crate::interlude::*; + +use crate::systems::{CliCommandAction, SystemCliCommand}; + +type SysCmdActions = IndexMap; + +pub struct SysCmdAction { + pub name: CHeapStr, + pub clap: clap::Command, + pub action: Option, + pub sub_commands: SysCmdActions, +} + +pub async fn commands_from_systems( + systems: &crate::host::GhjkfileSystems, +) -> Res<(Vec, SysCmdActions)> { + fn inner(cmd: SystemCliCommand) -> (SysCmdAction, clap::Command) { + // apply styles here due to propagation + // breaking for these dynamic subcommands for some reason + let mut clap_cmd = cmd.clap.styles(super::CLAP_STYLE); + let mut sub_commands = IndexMap::new(); + for (id, cmd) in cmd.sub_commands { + let (sub_sys_cmd, sub_cmd) = inner(cmd); + clap_cmd = clap_cmd.subcommand(sub_cmd); + sub_commands.insert(id, sub_sys_cmd); + } + ( + SysCmdAction { + clap: clap_cmd.clone(), + name: cmd.name, + action: cmd.action, + sub_commands, + }, + clap_cmd, + ) + } + let mut commands = vec![]; + let mut conflict_tracker = HashMap::new(); + let mut actions = SysCmdActions::new(); + for (id, sys_inst) in &systems.sys_instances { + let cmds = sys_inst + .commands() + .await + .wrap_err_with(|| format!("error getting commands for system: {id}"))?; + for cmd in cmds { + let (sys_cmd, clap_cmd) = inner(cmd); + + if let Some(conflict) = conflict_tracker.insert(sys_cmd.name.clone(), id) { + eyre::bail!( + "system commannd conflict under name {:?} for modules {conflict:?} and {id:?}", + sys_cmd.name.clone(), + ); + } + actions.insert(sys_cmd.name.clone(), sys_cmd); + commands.push(clap_cmd); + } + } + Ok((commands, actions)) +} + +pub async fn action_for_match( + mut actions: SysCmdActions, + matches: &clap::ArgMatches, +) -> Res<(Vec, SysCmdAction, &clap::ArgMatches)> { + fn inner<'a>( + mut current: SysCmdAction, + matches: &'a clap::ArgMatches, + cmd_path: &mut Vec, + ) -> Res<(SysCmdAction, &'a clap::ArgMatches)> { + match matches.subcommand() { + Some((cmd_name, matches)) => { + cmd_path.push(cmd_name.into()); + match current.sub_commands.swap_remove(cmd_name) { + Some(action) => inner(action, matches, cmd_path), + None => { + eyre::bail!("no match found for cmd {cmd_path:?}") + } + } + } + None => Ok((current, matches)), + } + } + let mut cmd_path = vec![]; + let Some((cmd_name, matches)) = matches.subcommand() else { + unreachable!("clap prevents this branch") + }; + cmd_path.push(cmd_name.into()); + let Some(action) = actions.swap_remove(cmd_name) else { + eyre::bail!("no match found for cmd {cmd_path:?}"); + }; + let (action, matches) = inner(action, matches, &mut cmd_path)?; + Ok((cmd_path, action, matches)) +} diff --git a/src/ghjk/host.rs b/src/ghjk/host.rs index f7d812b4..b2a5f076 100644 --- a/src/ghjk/host.rs +++ b/src/ghjk/host.rs @@ -1,9 +1,9 @@ -use std::io::IsTerminal; - use crate::interlude::*; use crate::systems::*; +use std::io::IsTerminal; + mod deno; mod hashfile; diff --git a/src/ghjk/host/hashfile.rs b/src/ghjk/host/hashfile.rs index c14985ec..f702c0b4 100644 --- a/src/ghjk/host/hashfile.rs +++ b/src/ghjk/host/hashfile.rs @@ -93,7 +93,7 @@ impl HashObj { } { for path in &self.listed_files { - if crate::utils::file_exists(path).await? { + if !crate::utils::file_exists(path).await? { trace!("stale listed files"); return Ok(true); } diff --git a/src/ghjk/main.rs b/src/ghjk/main.rs index 51592401..0a2ab483 100644 --- a/src/ghjk/main.rs +++ b/src/ghjk/main.rs @@ -1,13 +1,12 @@ #[allow(unused)] mod interlude { - pub use crate::utils::{default, CHeapStr, DHashMap}; + pub use crate::utils::{default, CHeapStr, DHashMap, JsonExt}; + pub use crate::GhjkCtx; pub use std::collections::HashMap; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; - pub use crate::GhjkCtx; - pub use color_eyre::{eyre, Section, SectionExt}; pub use denort::deno::{ self, diff --git a/src/ghjk/utils.rs b/src/ghjk/utils.rs index 6ecbd092..2c8b1ec7 100644 --- a/src/ghjk/utils.rs +++ b/src/ghjk/utils.rs @@ -271,3 +271,35 @@ pub async fn find_entry_recursive(from: &Path, name: &str) -> Res Self; + fn destructure_into_self(self, from: Self) -> Self; +} +impl JsonExt for serde_json::Value { + /* fn remove_keys_from_obj(self, keys: &[&str]) -> Self { + match self { + serde_json::Value::Object(mut map) => { + for key in keys { + map.remove(*key); + } + serde_json::Value::Object(map) + } + json => panic!("provided json was not an object: {:?}", json), + } + } */ + fn destructure_into_self(self, from: Self) -> Self { + match (self, from) { + (serde_json::Value::Object(mut first), serde_json::Value::Object(second)) => { + for (key, value) in second.into_iter() { + first.insert(key, value); + } + serde_json::Value::Object(first) + } + (first, second) => panic!( + "provided jsons weren't objects: first {:?}, second: {:?}", + first, second + ), + } + } +} diff --git a/tests/envHooks.ts b/tests/envHooks.ts index 3b340fb6..f62ac73f 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -80,7 +80,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { ghjk } from "$ghjk/hack.ts"; +export { sophon } from "$ghjk/hack.ts"; import { task, env } from "$ghjk/hack.ts"; env("main") diff --git a/tests/examples.ts b/tests/examples.ts index 3e1def7f..dcb1fb5e 100644 --- a/tests/examples.ts +++ b/tests/examples.ts @@ -10,7 +10,8 @@ type CustomE2eTestCase = Omit & { const cases: CustomE2eTestCase[] = [{ name: "template_ts", stdin: ` -rm ghjk.ts .ghjk -r +rm -r .ghjk +rm ghjk.ts ghjk init ts ghjk sync `, @@ -20,7 +21,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { ghjk } from "$ghjk/hack.ts"; +export { sophon } from "$ghjk/hack.ts"; `, }, ePoints: [{ cmd: "fish", stdin: testCase.stdin }], diff --git a/tests/hashfile.ts b/tests/hashfile.ts index bc79927d..25a4142b 100644 --- a/tests/hashfile.ts +++ b/tests/hashfile.ts @@ -55,8 +55,7 @@ test (cat tstamp) -lt (__ghjk_get_mtime_ts .ghjk/hash.json); or exit 101 name: "invalidated_cli_config_changed", stdin: ` __ghjk_get_mtime_ts .ghjk/hash.json > tstamp -rm dir/one -GHJK_DENO_LOCKFILLE=deno.lock ghjk sync +GHJK_DENO_LOCKFILE=deno.lock ghjk sync test (cat tstamp) -lt (__ghjk_get_mtime_ts .ghjk/hash.json); or exit 101 `, }, @@ -66,7 +65,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { ghjk } from "$ghjk/hack.ts"; +export { sophon } from "$ghjk/hack.ts"; import { task, env } from "$ghjk/hack.ts"; import {stuff} from "./extra.ts" diff --git a/tests/tasks.ts b/tests/tasks.ts index 39ecd4e3..1694e669 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -122,7 +122,7 @@ test (cat eddy) = 'ed edd eddy' { name: "anon", ghjkTs: ` -export { ghjk } from "$ghjk/hack.ts"; +export { sophon } from "$ghjk/hack.ts"; import { task } from "$ghjk/hack.ts"; task({ @@ -147,9 +147,10 @@ test (cat eddy) = 'ed edd eddy' { name: "dyn_vars", ghjkTs: ` +export { sophon } from "$ghjk/hack.ts"; import { file } from "$ghjk/hack.ts"; -export const ghjk = file({}); +const ghjk = file({}); const { env, task } = ghjk; diff --git a/tests/utils.ts b/tests/utils.ts index 6aaf7dbe..d7c0d2c5 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -157,13 +157,14 @@ export function genTsGhjkFile( ).join("\n"); return ` +export { sophon } from "$ghjk/mod.ts"; import { file } from "$ghjk/mod.ts"; const confStr = \` ${serializedSecConf} \`; const confObj = JSON.parse(confStr); -export const ghjk = file(confObj); +const ghjk = file(confObj); ${tasks} From bb0c538295763d55adce6e47b16b0f7855b09b11 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 6 Jan 2025 00:24:34 +0300 Subject: [PATCH 4/6] feat: import maps support --- .ghjk/config.json | 3 + README.md | 6 +- deno.jsonc | 4 ++ docs/installation-vars.md | 1 - docs/manual.md | 86 ++++++++++++++++++++----- host/init/mod.ts | 2 +- install/hook.fish | 8 +-- install/hook.sh | 10 +-- install/utils.ts | 4 +- src/deno_systems/bindings.ts | 1 - src/ghjk/cli.rs | 10 ++- src/ghjk/cli/print.rs | 1 - src/ghjk/config.rs | 117 +++++++++++++++++++++++++++++++---- src/ghjk/systems/deno.rs | 11 ++-- tests/envHooks.ts | 4 +- tests/examples.ts | 2 +- tests/hashfile.ts | 4 +- tests/tasks.ts | 8 +-- tests/todo.ts | 1 + tests/utils.ts | 4 +- 20 files changed, 224 insertions(+), 63 deletions(-) create mode 100644 .ghjk/config.json diff --git a/.ghjk/config.json b/.ghjk/config.json new file mode 100644 index 00000000..129c0ea2 --- /dev/null +++ b/.ghjk/config.json @@ -0,0 +1,3 @@ +{ + "deno_json": "../deno.jsonc" +} diff --git a/README.md b/README.md index 8dac25df..40b372cc 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ They serve as recipes for making (mostly) reproducible posix shells. ```ts // NOTE: `ghjk.ts` files are expected to export this sophon object -export { sophon } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts"; -import { file } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; // ports are small programs that install sowtware to your envs -import * as ports from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/ports/mod.ts"; +import * as ports from "ghjk/ports/mod.ts"; const ghjk = file({}); diff --git a/deno.jsonc b/deno.jsonc index 0eb73c80..237aeb68 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,4 +1,8 @@ { + "imports": { + "ghjk/": "/", + "ghjk": "./mod.ts" + }, "tasks": { "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", "test-doc": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", diff --git a/docs/installation-vars.md b/docs/installation-vars.md index 66a81b35..9f67a0e5 100644 --- a/docs/installation-vars.md +++ b/docs/installation-vars.md @@ -8,4 +8,3 @@ | `GHJK_INSTALL_EXE_DIR` | Location to install the `ghjk` exec. | `$HOME/.local/bin` | | `GHJK_INSTALL_HOOK_SHELLS` | Comma separated list of shells to hook. | `bash,fish,zsh` | | `GHJK_INSTALL_HOOK_MARKER` | Marker to use when installing shell hooks. | `ghjk-hook-marker` | -| | | | diff --git a/docs/manual.md b/docs/manual.md index f8a16337..862b7147 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -37,14 +37,11 @@ ghjk init ts Look through the following snippet to understand the basic structure of a `ghjk.ts` file. ```ts -// import the file function from `mod.ts` using the version of ghjk -// one's using. For example -// https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/ -import { file } from ".../mod.ts"; // all ghjk.ts files are expected to export this special `sophon` object -export { sophon } from ".../mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; // import the port for the node program -import node from ".../ports/node.ts"; +import node from "ghjk/ports/node.ts"; // Create the ghjk object using the file functiono. This modifies // the sophon exported above and may only be called once during @@ -64,7 +61,7 @@ ghjk.task("greet", async ($) => { One can look at the [examples](../examples/) found in the ghjk repo for an exploration of the different features available. -## `$GHJK_DIR` +## `$GHJKDIR` Once you have a ghjkfile ready to go, the ghjk CLI can be used to access all the features your ghjkfile is using. Augmenting the CLI are the hooks that were installed into your shells rc file (startup scripts like `~/.bashrc`). @@ -79,7 +76,7 @@ The `$GHJKFILE` environment variable can be set to point the CLI and hooks at a The `.ghjk` dir is used by ghjk for different needs and contains some files you'll want to check into version control. It includes its own `.gitignore` file by default that excludes all items not of interest for version control. -The `$GHJK_DIR` variable can be used to point the CLI at a different directory. +The `$GHJKDIR` variable can be used to point the CLI at a different directory. ## Serialized @@ -128,8 +125,8 @@ $ ghjk --help #### The Lockfile The cached value of the serialization results are stored in the lockfile. -The lockfile is what the different modules of ghjk use to store transient information that needs to be tracked across serializations. -Currently, this is mainly used by the port modules to retain version numbers resolved during installation which is important for the basic need of reproducibility. +The lockfile is what the different systems of ghjk use to store transient information that needs to be tracked across serializations. +Currently, this is mainly used by the ports system to retain version numbers resolved during installation which is important for the basic need of reproducibility. To maintain reproducibility across different machines, this file needs to be checked into version control. Unfortunately, this can lead to version conflicts during git merges for example. @@ -157,7 +154,8 @@ You declare them in your ghjkfile, using typescript functions, and then invoke t The CLI will then load your ghjkfile in a worker and execute your function. ```ts -import { file } from ".../mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; const ghjk = file(); @@ -198,7 +196,8 @@ Ghjk envs then allow you: Let's look at how one configures an environment using the `ghjk.ts` file: ```ts -import { file } from ".../mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; const ghjk = file(); @@ -312,9 +311,9 @@ Any `InstallConfig` objects included in an env will then be resolved and install ```ts // the default export corresponds to the `conf` function -import node from ".../ports/node.ts"; +import node from "ghjk/ports/node.ts"; // the npmi installs executable packages from npm -import npmi from ".../ports/node.ts"; +import npmi from "ghjk/ports/node.ts"; // top level `install` calls go to the `main` env ghjk.install( @@ -346,9 +345,9 @@ The default set includes common utilities like `curl`, `git`, `tar` and others w More ports can be easily added to the allowed port dep set. ```ts -import { file } from ".../mod.ts"; +import { file } from "ghjk/mod.ts"; // barrel export for ports in the ghjk repo -import * as ports from "../../ports/mod.ts"; +import * as ports from "ghjk/ports/mod.ts"; const ghjk = file(); @@ -481,3 +480,58 @@ Otherwise, it's necessary to use the approach described in the section above. run: | echo $GHJK_ENV ``` + +## `config.json` + +One can examine the configuration values used by the CLI using the following command... + +```bash +ghjk print config +# { +# /* json rep of config */ +# } +``` + +These is mostly set of paths to resolve ghjkfiles or other values that need to be resolved before the serializaiton process. +Most of these settings can be configured through the `config.json` file which is looked for at `.ghjk/config.json` by default. +Additionaly, most of these values can be configured through environment variables under keys that are the name of the config value prefixed by `GHJK_`. +So for the `repo_root` config, this would be resolved from the `$GHJK_REPO_ROOT` env var. +Some of the values can be configured globally thorugh a file looked for at `$XDG_CONFIG_PATH/ghjk/config.json`. + +The following snippet shows current config set, their defafults and an explanation of their purpose. + +```jsonc +{ + // Path to the deno config file used to configure the deno runtime + // like import aliases. + // If not found, this is created by default to support the `ghjk` + // alias used by ghjk.ts files. Default creation is disabled if + // the import_map path is set. + "deno_json": "<$ghjkdir/deno.jsonc>", + // Path to an deno.lock file used to lock modules imported by deno. + // Set it to value `off` to disable lockfile usage. + // The `deno.json` spec also supports configuring the deno.lock path + // from within it which will be respected + "deno_lockfile": "<$ghjkdir/deno.lock>", + // Path to an import_map.json for resolving js import aliases + // `deno_json`, if set, will takes precedence over this. + // The `deno.json` spec also supports configuring the import_map path + // from within it which will be respected + "import_map": null, + + // data dir to be used by systems. This is where + // ports get installed and is shared across ghjkdirs. + // *supports global configuration* + "data_dir": "<$XDG_DATA_DIR/ghjk>", + // Cache dir used by deno. This is where + // where deno caches downloaded modules. + // *supports global configuration* + "deno_dir": "<$XDG_DATA_DIR/ghjk/deno>", + // The repo root url used to import the typescript section + // of the ghjk implementation from. + // *supports global configuration* + "repo_root": "", +} +``` + +In addition to a `config.json` files, `config.json5` files are also supported which is a [friendlier superset of JSON](https://json5.org/) with support for comments and more. diff --git a/host/init/mod.ts b/host/init/mod.ts index fd27fc72..1418efff 100644 --- a/host/init/mod.ts +++ b/host/init/mod.ts @@ -14,7 +14,7 @@ const tasks: Record = { desc: "Create a typescript ghjkfile in the current directory.", async fn($, args) { { - const ghjkdir = $.env["GHJK_DIR"] ?? + const ghjkdir = $.env["GHJKDIR"] ?? await findEntryRecursive($.workingDir, ".ghjk"); if (ghjkdir) { throw new Error( diff --git a/install/hook.fish b/install/hook.fish index 0de6ecde..7a5ad3e2 100644 --- a/install/hook.fish +++ b/install/hook.fish @@ -33,12 +33,12 @@ function ghjk_hook --on-variable PWD set --erase GHJK_CLEANUP_FISH end - set --local local_ghjk_dir $GHJK_DIR - # if $GHJKFILE is set, set the GHJK_DIR overriding + set --local local_ghjk_dir $GHJKDIR + # if $GHJKFILE is set, set the GHJKDIR overriding # any set by the user if set --query GHJKFILE set local_ghjk_dir (dirname $GHJKFILE)/.ghjk - # if both GHJKFILE and GHJK_DIR are unset + # if both GHJKFILE and GHJKDIR are unset else if test -z "$local_ghjk_dir" # look for ghjk dirs in pwd and parents set --local cur_dir $PWD @@ -59,7 +59,7 @@ function ghjk_hook --on-variable PWD end if test -n "$local_ghjk_dir" - set --global --export GHJK_LAST_GHJK_DIR $local_ghjk_dir + set --global --export GHJK_LAST_GHJKDIR $local_ghjk_dir # locate the next env set --local next_env_dir $local_ghjk_dir/envs/$next_env diff --git a/install/hook.sh b/install/hook.sh index f943e463..9efd9288 100644 --- a/install/hook.sh +++ b/install/hook.sh @@ -29,12 +29,12 @@ ghjk_hook() { unset GHJK_CLEANUP_POSIX fi - local_ghjk_dir="${GHJK_DIR:-}" - # if $GHJKFILE is set, set the GHJK_DIR overriding + local_ghjk_dir="${GHJKDIR:-}" + # if $GHJKFILE is set, set the GHJKDIR overriding # any set by the user if [ -n "${GHJKFILE+x}" ]; then local_ghjk_dir="$(dirname "$GHJKFILE")/.ghjk" - # if both GHJKFILE and GHJK_DIR are unset + # if both GHJKFILE and GHJKDIR are unset elif [ -z "$local_ghjk_dir" ]; then # look for ghjk dirs in pwd parents # use do while format to allow detection of .ghjk in root dirs @@ -54,8 +54,8 @@ ghjk_hook() { fi if [ -n "$local_ghjk_dir" ]; then - GHJK_LAST_GHJK_DIR="$local_ghjk_dir" - export GHJK_LAST_GHJK_DIR + GHJK_LAST_GHJKDIR="$local_ghjk_dir" + export GHJK_LAST_GHJKDIR # locate the next env next_env_dir="$local_ghjk_dir/envs/$next_env" diff --git a/install/utils.ts b/install/utils.ts index a6796845..9bb6cbaa 100644 --- a/install/utils.ts +++ b/install/utils.ts @@ -11,7 +11,7 @@ export function ghjk_sh( functionName = "__ghjk_shim", ) { return `${functionName} () { - GHJK_DIR="${gcx.ghjkDir}" \\ + GHJKDIR="${gcx.ghjkDir}" \\ ${Deno.execPath()} "$@" }`; } @@ -25,7 +25,7 @@ export function ghjk_fish( functionName = "__ghjk_shim", ) { return `function ${functionName} - GHJK_DIR="${gcx.ghjkDir}" \\ + GHJKDIR="${gcx.ghjkDir}" \\ ${Deno.execPath()} $argv end`; } diff --git a/src/deno_systems/bindings.ts b/src/deno_systems/bindings.ts index 27976649..31da6644 100644 --- a/src/deno_systems/bindings.ts +++ b/src/deno_systems/bindings.ts @@ -47,7 +47,6 @@ const prepareArgs = zod.object({ ghjkfile: zod.string().nullish(), ghjkdir: zod.string(), data_dir: zod.string(), - deno_lockfile: zod.string().nullish(), repo_root: zod.string(), deno_dir: zod.string(), }), diff --git a/src/ghjk/cli.rs b/src/ghjk/cli.rs index 4f26f557..276ee649 100644 --- a/src/ghjk/cli.rs +++ b/src/ghjk/cli.rs @@ -48,7 +48,15 @@ pub async fn cli() -> Res { .collect(), ..default() }, - no_lock: config.deno_lockfile.is_none(), + no_lock: config.deno_no_lockfile, + config_flag: match config.deno_json.as_ref() { + Some(path) => deno::args::ConfigFlag::Path(path.to_string_lossy().into()), + None => deno::args::ConfigFlag::Disabled, + }, + import_map_path: config + .import_map + .as_ref() + .map(|path| path.to_string_lossy().into()), lock: config .deno_lockfile .as_ref() diff --git a/src/ghjk/cli/print.rs b/src/ghjk/cli/print.rs index 9367fb5e..630baeaa 100644 --- a/src/ghjk/cli/print.rs +++ b/src/ghjk/cli/print.rs @@ -34,7 +34,6 @@ impl PrintCommands { println!("{}", cli_config.data_dir.display()); true } - // TODO: rename GHJK_DIR to GHJKDIR PrintCommands::GhjkdirPath => { if let Some(path) = &cli_config.ghjkdir { // TODO: graceful termination on SIGPIPE diff --git a/src/ghjk/config.rs b/src/ghjk/config.rs index b23b9d11..3d02eec9 100644 --- a/src/ghjk/config.rs +++ b/src/ghjk/config.rs @@ -6,8 +6,11 @@ pub struct Config { pub ghjkdir: Option, pub data_dir: PathBuf, pub deno_dir: PathBuf, + pub deno_json: Option, pub deno_lockfile: Option, + pub import_map: Option, pub repo_root: url::Url, + pub deno_no_lockfile: bool, } #[derive(Deserialize)] @@ -21,7 +24,9 @@ struct GlobalConfigFile { struct LocalConfigFile { #[serde(flatten)] global: GlobalConfigFile, + deno_json: Option, deno_lockfile: Option, + import_map: Option, } impl Config { @@ -30,7 +35,7 @@ impl Config { let xdg_dirs = directories::ProjectDirs::from("", "", "ghjk") .expect_or_log("unable to resolve home dir"); - let ghjkdir_path = match path_from_env(&cwd, "GHJK_DIR")? { + let ghjkdir_path = match path_from_env(&cwd, "GHJKDIR")? { Some(val) => Some(val), None => crate::utils::find_entry_recursive(&cwd, ".ghjk") .await @@ -45,9 +50,7 @@ impl Config { match &ghjkdir_path { Some(ghjkfile_path) => { crate::utils::find_entry_recursive( - ghjkfile_path - .parent() - .expect_or_log("invalid GHJK_DIR path"), + ghjkfile_path.parent().expect_or_log("invalid GHJKDIR path"), ghjkfile_name, ) .await? @@ -61,7 +64,7 @@ impl Config { } }; - // if ghjkfile var is set, set the GHJK_DIR overriding + // if ghjkfile var is set, set the GHJKDIR overriding // any set by the user let (ghjkfile_path, ghjkdir_path) = if let Some(path) = ghjkfile_path { let file_path = tokio::fs::canonicalize(&path) @@ -84,7 +87,13 @@ impl Config { ghjkdir: ghjkdir_path.clone(), data_dir: xdg_dirs.data_dir().to_owned(), deno_dir: xdg_dirs.data_dir().join("deno"), + deno_json: ghjkdir_path.as_ref().map(|path| path.join("deno.jsonc")), + import_map: None, + // import_map: ghjkdir_path + // .as_ref() + // .map(|path| path.join("import_map.json")), deno_lockfile: ghjkdir_path.as_ref().map(|path| path.join("deno.lock")), + deno_no_lockfile: false, repo_root: { if cfg!(debug_assertions) { url::Url::from_file_path(&cwd) @@ -139,6 +148,7 @@ impl Config { .await .expect_or_log("tokio error")?; + // create .gitignore if let Some(path) = &config.ghjkdir { let ignore_path = path.join(".gitignore"); if !crate::utils::file_exists(&ignore_path).await? { @@ -154,6 +164,47 @@ hash.json", .wrap_err_with(|| format!("error writing ignore file at {ignore_path:?}"))?; } } + // create deno.json + if let Some(deno_json_path) = &config.deno_json { + if !crate::utils::file_exists(deno_json_path).await? { + let parent = deno_json_path + .parent() + .expect_or_log("deno_json path error"); + tokio::fs::create_dir_all(&parent) + .await + .wrap_err_with(|| format!("error creating ghjkdir at {deno_json_path:?}"))?; + let mut deno_json = json!({}); + if let Some(import_map_path) = &config.import_map { + deno_json = deno_json.destructure_into_self(json!({ + "importMap": pathdiff::diff_paths(import_map_path, parent) + .unwrap_or_else(|| import_map_path.clone()) + })) + } else { + deno_json = deno_json.destructure_into_self(json!({ + "imports": { + "ghjk/": config.repo_root.to_string(), + "ghjk": config.repo_root.join("./mod.ts").expect_or_log("repo root error").to_string() + }, + })) + } + if let Some(deno_lockfile_path) = &config.deno_lockfile { + deno_json = deno_json.destructure_into_self(json!({ + "lock": pathdiff::diff_paths(deno_lockfile_path, parent) + .unwrap_or_else(|| deno_lockfile_path.clone()) + })) + } else { + deno_json = deno_json.destructure_into_self(json!({ + "lock": "./deno.lock", + })) + } + tokio::fs::write( + &deno_json_path, + serde_json::to_vec_pretty(&deno_json).expect_or_log("json error"), + ) + .await + .wrap_err_with(|| format!("error writing deno_json file at {deno_json_path:?}"))?; + } + } Ok(config) } @@ -168,13 +219,16 @@ hash.json", .wrap_err("error reading config file")? .try_deserialize() .wrap_err("error deserializing config file")?; + let parent = file_path + .parent() + .ok_or_else(|| ferr!("error getting path to config parent dir"))?; if let Some(path) = data_dir { self.data_dir = - resolve_config_path(&path, file_path).wrap_err("error resolving data_dir")?; + resolve_config_path(&path, parent).wrap_err("error resolving data_dir")?; } if let Some(path) = deno_dir { self.deno_dir = - resolve_config_path(&path, file_path).wrap_err("error resolving deno_dir")?; + resolve_config_path(&path, parent).wrap_err("error resolving deno_dir")?; } if let Some(path) = repo_root { self.repo_root = deno_core::resolve_url_or_path(&path, file_path) @@ -193,6 +247,8 @@ hash.json", repo_root, }, deno_lockfile, + import_map, + deno_json, } = config::Config::builder() .add_source(config::File::with_name(&file_path.to_string_lossy()).required(false)) .build() @@ -200,21 +256,40 @@ hash.json", .try_deserialize() .wrap_err("error deserializing config file")?; + let parent = file_path + .parent() + .ok_or_else(|| ferr!("error getting path to config parent dir"))?; if let Some(path) = data_dir { self.data_dir = - resolve_config_path(&path, file_path).wrap_err("error resolving data_dir")?; + resolve_config_path(&path, parent).wrap_err("error resolving data_dir")?; } if let Some(path) = deno_dir { self.deno_dir = - resolve_config_path(&path, file_path).wrap_err("error resolving deno_dir")?; + resolve_config_path(&path, parent).wrap_err("error resolving deno_dir")?; + } + if let Some(path) = import_map { + // we want to disable the default deno.jsonc if import_map + // is set + if deno_json.is_none() { + self.deno_json = None + } + self.import_map = + Some(resolve_config_path(&path, parent).wrap_err("error resolving import_map")?); + } + if let Some(path) = deno_json { + self.deno_json = + Some(resolve_config_path(&path, parent).wrap_err("error resolving deno_json")?); } if let Some(path) = deno_lockfile { - self.deno_lockfile = Some( - resolve_config_path(&path, file_path).wrap_err("error resolving deno_lockfile")?, - ); + self.deno_lockfile = if path != "off" { + Some(resolve_config_path(&path, parent).wrap_err("error resolving deno_lockfile")?) + } else { + self.deno_no_lockfile = true; + None + }; } if let Some(path) = repo_root { - self.repo_root = deno_core::resolve_url_or_path(&path, file_path) + self.repo_root = deno_core::resolve_url_or_path(&path, parent) .map_err(|err| ferr!(Box::new(err))) .wrap_err("error resolving repo_root")?; } @@ -230,6 +305,8 @@ hash.json", repo_root, }, deno_lockfile, + import_map, + deno_json, } = config::Config::builder() .add_source(config::Environment::with_prefix("GHJK")) .build() @@ -243,10 +320,24 @@ hash.json", if let Some(path) = deno_dir { self.deno_dir = resolve_config_path(&path, cwd).wrap_err("error resolving deno_dir")?; } + if let Some(path) = import_map { + // we want to disable the default deno.jsonc if import_map + // is set + if deno_json.is_none() { + self.deno_json = None + } + self.import_map = + Some(resolve_config_path(&path, cwd).wrap_err("error resolving import_map")?); + } + if let Some(path) = deno_json { + self.deno_json = + Some(resolve_config_path(&path, cwd).wrap_err("error resolving deno_json")?); + } if let Some(path) = deno_lockfile { self.deno_lockfile = if path != "off" { Some(resolve_config_path(&path, cwd).wrap_err("error resolving deno_lockfile")?) } else { + self.deno_no_lockfile = true; None }; } diff --git a/src/ghjk/systems/deno.rs b/src/ghjk/systems/deno.rs index 64b60a70..af1bfa93 100644 --- a/src/ghjk/systems/deno.rs +++ b/src/ghjk/systems/deno.rs @@ -53,7 +53,6 @@ pub async fn systems_from_deno( pub ghjkdir: &'a Path, pub data_dir: &'a Path, pub deno_dir: &'a Path, - pub deno_lockfile: Option<&'a Path>, pub repo_root: &'a url::Url, } @@ -64,11 +63,16 @@ pub async fn systems_from_deno( } let crate::config::Config { repo_root, - ghjkdir: _, data_dir, - deno_lockfile, ghjkfile, deno_dir, + // explictly ignore fields to avoid + // missing additions + deno_lockfile: _, + ghjkdir: _, + deno_json: _, + import_map: _, + deno_no_lockfile: _, } = &gcx.config; json!(BindingArgs { @@ -77,7 +81,6 @@ pub async fn systems_from_deno( ghjkfile: ghjkfile.as_ref().map(|path| path.as_path()), ghjkdir: ghjkdir_path, data_dir, - deno_lockfile: deno_lockfile.as_ref().map(|path| path.as_path()), deno_dir, repo_root }, diff --git a/tests/envHooks.ts b/tests/envHooks.ts index f62ac73f..4a90a372 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -80,8 +80,8 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { sophon } from "$ghjk/hack.ts"; -import { task, env } from "$ghjk/hack.ts"; +export { sophon } from "ghjk/hack.ts"; +import { task, env } from "ghjk/hack.ts"; env("main") .onEnter(task($ => $\`/bin/sh -c 'echo remark > marker'\`)) diff --git a/tests/examples.ts b/tests/examples.ts index dcb1fb5e..4797d0bd 100644 --- a/tests/examples.ts +++ b/tests/examples.ts @@ -21,7 +21,7 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { sophon } from "$ghjk/hack.ts"; +export { sophon } from "ghjk"; `, }, ePoints: [{ cmd: "fish", stdin: testCase.stdin }], diff --git a/tests/hashfile.ts b/tests/hashfile.ts index 25a4142b..9a714dd2 100644 --- a/tests/hashfile.ts +++ b/tests/hashfile.ts @@ -65,8 +65,8 @@ harness(cases.map((testCase) => ({ ...testCase, fs: { "ghjk.ts": ` -export { sophon } from "$ghjk/hack.ts"; -import { task, env } from "$ghjk/hack.ts"; +export { sophon } from "ghjk/hack.ts"; +import { task, env } from "ghjk/hack.ts"; import {stuff} from "./extra.ts" await Array.fromAsync(Deno.readDir("dir")) diff --git a/tests/tasks.ts b/tests/tasks.ts index 1694e669..e28d14f2 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -122,8 +122,8 @@ test (cat eddy) = 'ed edd eddy' { name: "anon", ghjkTs: ` -export { sophon } from "$ghjk/hack.ts"; -import { task } from "$ghjk/hack.ts"; +export { ghjk } from "ghjk/hack.ts"; +import { task } from "ghjk/hack.ts"; task({ dependsOn: [ @@ -147,8 +147,8 @@ test (cat eddy) = 'ed edd eddy' { name: "dyn_vars", ghjkTs: ` -export { sophon } from "$ghjk/hack.ts"; -import { file } from "$ghjk/hack.ts"; +export { sophon } from "ghjk/hack.ts"; +import { file } from "ghjk/hack.ts"; const ghjk = file({}); diff --git a/tests/todo.ts b/tests/todo.ts index 53f57f25..cc230d63 100644 --- a/tests/todo.ts +++ b/tests/todo.ts @@ -1 +1,2 @@ // TODO: tests for lockfile impl +// TODO: tests for config.json diff --git a/tests/utils.ts b/tests/utils.ts index d7c0d2c5..126b29b1 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -157,8 +157,8 @@ export function genTsGhjkFile( ).join("\n"); return ` -export { sophon } from "$ghjk/mod.ts"; -import { file } from "$ghjk/mod.ts"; +export { sophon } from "ghjk/mod.ts"; +import { file } from "ghjk/mod.ts"; const confStr = \` ${serializedSecConf} From c1f4c9bbd6ee743c17df7a1593f80381508ab452 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 6 Jan 2025 01:12:13 +0300 Subject: [PATCH 5/6] fix: address feedback --- deno.jsonc | 4 ++-- docs/manual.md | 4 ++-- examples/env_vars/ghjk.ts | 4 ++-- examples/envs/ghjk.ts | 6 +++--- examples/kitchen/ghjk.ts | 7 +++---- examples/many_installs/ghjk.ts | 6 +++--- examples/tasks/ghjk.ts | 6 +++--- examples/template.ts | 7 +++---- ghjk.ts | 16 ++++++++-------- ports/asdf_plugin_git.ts | 29 +++++++++++++++++++++++++++-- src/ghjk/cli/print.rs | 2 +- src/ghjk/cli/sys.rs | 2 +- src/ghjk/config.rs | 6 +++++- src/ghjk/host/hashfile.rs | 2 +- tests/tasks.ts | 8 ++------ tests/utils.ts | 4 ++-- 16 files changed, 68 insertions(+), 45 deletions(-) diff --git a/deno.jsonc b/deno.jsonc index 237aeb68..021fe84a 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,7 +1,7 @@ { "imports": { - "ghjk/": "/", - "ghjk": "./mod.ts" + "ghjk": "./mod.ts", + "ghjk/": "./" }, "tasks": { "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", diff --git a/docs/manual.md b/docs/manual.md index 862b7147..22dfe2fe 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -345,7 +345,7 @@ The default set includes common utilities like `curl`, `git`, `tar` and others w More ports can be easily added to the allowed port dep set. ```ts -import { file } from "ghjk/mod.ts"; +import { file } from "ghjk"; // barrel export for ports in the ghjk repo import * as ports from "ghjk/ports/mod.ts"; @@ -420,7 +420,7 @@ RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/${GHJK_VERSION}/install. ### Activation When working on non-interactive shells, the ghjk shell hooks are not available. -This means that the default environment won't be activated for that CWD nor will any changes occur on changing directories. +This means that the default environment won't be activated for that CWD, nor will any changes occur on changing directories. It also prevents the `ghjk sync` and `ghjk envs activate` commands from functioning which requires that these hooks be run before each command. In such scenarios, one can directly `source` the activation script for the target env from the `.ghjk` directory. diff --git a/examples/env_vars/ghjk.ts b/examples/env_vars/ghjk.ts index a534f00b..181f7501 100644 --- a/examples/env_vars/ghjk.ts +++ b/examples/env_vars/ghjk.ts @@ -1,5 +1,5 @@ -export { sophon } from "../../hack.ts"; -import { file } from "../../hack.ts"; +export { sophon } from "ghjk/hack.ts"; +import { file } from "ghjk/hack.ts"; const ghjk = file({ defaultEnv: "empty", diff --git a/examples/envs/ghjk.ts b/examples/envs/ghjk.ts index 3a2e7c5d..64b08f3f 100644 --- a/examples/envs/ghjk.ts +++ b/examples/envs/ghjk.ts @@ -1,6 +1,6 @@ -export { sophon } from "../../hack.ts"; -import { config, env, install, task } from "../../hack.ts"; -import * as ports from "../../ports/mod.ts"; +export { sophon } from "ghjk/hack.ts"; +import { config, env, install, task } from "ghjk/hack.ts"; +import * as ports from "ghjk/ports/mod.ts"; config({ // we can change which environment diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index 0430240e..d889b7d8 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -1,7 +1,6 @@ -import { stdDeps } from "../../files/mod.ts"; -export { sophon } from "../../mod.ts"; -import { file } from "../../mod.ts"; -import * as ports from "../../ports/mod.ts"; +export { sophon } from "ghjk"; +import { file, stdDeps } from "ghjk"; +import * as ports from "ghjk/ports/mod.ts"; // we need this export for this file to be a valid ghjkfile // it's the one thing used by the ghjk host implementation to diff --git a/examples/many_installs/ghjk.ts b/examples/many_installs/ghjk.ts index b5a3a154..671e5f05 100644 --- a/examples/many_installs/ghjk.ts +++ b/examples/many_installs/ghjk.ts @@ -1,6 +1,6 @@ -export { sophon } from "../../hack.ts"; -import { config, install } from "../../hack.ts"; -import * as ports from "../../ports/mod.ts"; +export { sophon } from "ghjk/hack.ts"; +import { config, install } from "ghjk/hack.ts"; +import * as ports from "ghjk/ports/mod.ts"; const installs = { python: ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts index 61b227f4..22b5d4c7 100644 --- a/examples/tasks/ghjk.ts +++ b/examples/tasks/ghjk.ts @@ -1,6 +1,6 @@ -export { sophon } from "../../hack.ts"; -import { logger, task } from "../../hack.ts"; -import * as ports from "../../ports/mod.ts"; +export { sophon } from "ghjk/hack.ts"; +import { logger, task } from "ghjk/hack.ts"; +import * as ports from "ghjk/ports/mod.ts"; task("greet", async ($, { argv: [name] }) => { await $`echo Hello ${name}!`; diff --git a/examples/template.ts b/examples/template.ts index 9f7958b6..f3960818 100644 --- a/examples/template.ts +++ b/examples/template.ts @@ -1,8 +1,7 @@ // @ts-nocheck: Deno based - -export { sophon } from "../mod.ts"; // template-import -import { file } from "../mod.ts"; // template-import -// import * as ports from "../ports/mod.ts"; // template-import +export { sophon } from "ghjk"; +import { file } from "ghjk"; +// import * as ports from "ghjk/ports/mod.ts"; // template-import // This export is necessary for typescript ghjkfiles const ghjk = file({ diff --git a/ghjk.ts b/ghjk.ts index 8fe395af..0b7f9a38 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,14 +1,14 @@ // @ts-nocheck: Ghjkfile based on Deno -export { sophon } from "./mod.ts"; -import { $, file } from "./mod.ts"; +export { sophon } from "ghjk"; +import { $, file } from "ghjk"; -import * as ports from "./ports/mod.ts"; -import { switchMap } from "./port.ts"; -import { sedLock } from "./std.ts"; -import { downloadFile, DownloadFileArgs } from "./utils/mod.ts"; -import { unarchive } from "./utils/unarchive.ts"; -import dummy from "./ports/dummy.ts"; +import * as ports from "ghjk/ports/mod.ts"; +import { switchMap } from "ghjk/port.ts"; +import { sedLock } from "ghjk/std.ts"; +import { downloadFile, DownloadFileArgs } from "ghjk/utils/mod.ts"; +import { unarchive } from "ghjk/utils/unarchive.ts"; +import dummy from "ghjk/ports/dummy.ts"; const ghjk = file({}); diff --git a/ports/asdf_plugin_git.ts b/ports/asdf_plugin_git.ts index ab7c4458..a68938b4 100644 --- a/ports/asdf_plugin_git.ts +++ b/ports/asdf_plugin_git.ts @@ -14,6 +14,11 @@ import { std_fs, zod, } from "../port.ts"; +import { + ghConfValidator, + type GithubReleasesInstConf, + readGhVars, +} from "../modules/ports/ghrel.ts"; const git_aa_id = { name: "git_aa", @@ -35,6 +40,7 @@ const confValidator = zod.object({ export type AsdfPluginInstallConf = & InstallConfigSimple + & GithubReleasesInstConf & zod.input; /** @@ -53,6 +59,7 @@ export default function conf(config: AsdfPluginInstallConf) { export function buildDep(): AllowedPortDep { return { + ...readGhVars(), manifest, defaultInst: { portRef: getPortRef(manifest), @@ -63,9 +70,19 @@ export function buildDep(): AllowedPortDep { export class Port extends PortBase { async listAll(args: ListAllArgs) { const conf = confValidator.parse(args.config); + + const repoUrl = new URL(conf.pluginRepo); + if (repoUrl.hostname == "github.com") { + const ghConf = ghConfValidator.parse(args.config); + if (ghConf.ghToken) { + repoUrl.username = ghConf.ghToken; + repoUrl.password = ghConf.ghToken; + } + } + const fullOut = await $`${ depExecShimPath(git_aa_id, "git", args.depArts) - } ls-remote ${conf.pluginRepo} HEAD`.lines(); + } ls-remote ${repoUrl} HEAD`.lines(); return fullOut .filter(Boolean) @@ -83,9 +100,17 @@ export class Port extends PortBase { return; } const conf = confValidator.parse(args.config); + const repoUrl = new URL(conf.pluginRepo); + if (repoUrl.hostname == "github.com") { + const ghConf = ghConfValidator.parse(args.config); + if (ghConf.ghToken) { + repoUrl.username = ghConf.ghToken; + repoUrl.password = ghConf.ghToken; + } + } await $`${ depExecShimPath(git_aa_id, "git", args.depArts) - } clone ${conf.pluginRepo} --depth 1 ${args.tmpDirPath}`; + } clone ${repoUrl} --depth 1 ${args.tmpDirPath}`; await std_fs.copy( args.tmpDirPath, args.downloadPath, diff --git a/src/ghjk/cli/print.rs b/src/ghjk/cli/print.rs index 630baeaa..7e73e13c 100644 --- a/src/ghjk/cli/print.rs +++ b/src/ghjk/cli/print.rs @@ -44,7 +44,7 @@ impl PrintCommands { } } PrintCommands::GhjkfilePath => { - if let Some(path) = &cli_config.ghjkdir { + if let Some(path) = &cli_config.ghjkfile { println!("{}", path.display()); true } else { diff --git a/src/ghjk/cli/sys.rs b/src/ghjk/cli/sys.rs index 841f5f70..cc7790ed 100644 --- a/src/ghjk/cli/sys.rs +++ b/src/ghjk/cli/sys.rs @@ -47,7 +47,7 @@ pub async fn commands_from_systems( if let Some(conflict) = conflict_tracker.insert(sys_cmd.name.clone(), id) { eyre::bail!( - "system commannd conflict under name {:?} for modules {conflict:?} and {id:?}", + "system command conflict under name {:?} for modules {conflict:?} and {id:?}", sys_cmd.name.clone(), ); } diff --git a/src/ghjk/config.rs b/src/ghjk/config.rs index 3d02eec9..64d48e76 100644 --- a/src/ghjk/config.rs +++ b/src/ghjk/config.rs @@ -187,7 +187,11 @@ hash.json", }, })) } - if let Some(deno_lockfile_path) = &config.deno_lockfile { + if config.deno_no_lockfile { + deno_json = deno_json.destructure_into_self(json!({ + "lock": false + })) + } else if let Some(deno_lockfile_path) = &config.deno_lockfile { deno_json = deno_json.destructure_into_self(json!({ "lock": pathdiff::diff_paths(deno_lockfile_path, parent) .unwrap_or_else(|| deno_lockfile_path.clone()) diff --git a/src/ghjk/host/hashfile.rs b/src/ghjk/host/hashfile.rs index f702c0b4..4c1135d8 100644 --- a/src/ghjk/host/hashfile.rs +++ b/src/ghjk/host/hashfile.rs @@ -5,7 +5,7 @@ use super::HostCtx; #[derive(Debug, Serialize, Deserialize)] pub struct HashObj { pub version: String, - /// The cli config used during serializatin + /// The cli config used during serialization pub cli_config: crate::config::Config, /// Hashes of all env vars that were read. pub env_var_hashes: indexmap::IndexMap>, diff --git a/tests/tasks.ts b/tests/tasks.ts index e28d14f2..5c642ea3 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -122,7 +122,7 @@ test (cat eddy) = 'ed edd eddy' { name: "anon", ghjkTs: ` -export { ghjk } from "ghjk/hack.ts"; +export { sophon } from "ghjk/hack.ts"; import { task } from "ghjk/hack.ts"; task({ @@ -148,11 +148,7 @@ test (cat eddy) = 'ed edd eddy' name: "dyn_vars", ghjkTs: ` export { sophon } from "ghjk/hack.ts"; -import { file } from "ghjk/hack.ts"; - -const ghjk = file({}); - -const { env, task } = ghjk; +import { env, task } from "ghjk/hack.ts"; env("main") .var("A", "A#STATIC") diff --git a/tests/utils.ts b/tests/utils.ts index 126b29b1..6f8fa016 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -157,8 +157,8 @@ export function genTsGhjkFile( ).join("\n"); return ` -export { sophon } from "ghjk/mod.ts"; -import { file } from "ghjk/mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; const confStr = \` ${serializedSecConf} From f5099b73fb7ab75c54e3d3173bd4293113559766 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 6 Jan 2025 01:37:39 +0300 Subject: [PATCH 6/6] chore: bump to rc.2 --- .github/workflows/tests.yml | 1 - Cargo.lock | 8 +- Cargo.toml | 2 +- README.md | 8 +- deno.jsonc | 7 +- docs/manual.md | 20 ++-- ghjk.ts | 3 +- host/init/mod.ts | 185 ------------------------------------ 8 files changed, 28 insertions(+), 206 deletions(-) delete mode 100644 host/init/mod.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91af14e3..24aba717 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -86,7 +86,6 @@ jobs: run: brew install fish zsh coreutils cmake - run: | deno task test-rust - deno task test-doc deno task test # test-action: diff --git a/Cargo.lock b/Cargo.lock index 6b1776b2..981c5307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2525,7 +2525,7 @@ dependencies = [ [[package]] name = "denort" -version = "0.3.0-rc.1" +version = "0.3.0-rc.2" dependencies = [ "anyhow", "color-eyre", @@ -3603,7 +3603,7 @@ dependencies = [ [[package]] name = "ghjk" -version = "0.3.0-rc.1" +version = "0.3.0-rc.2" dependencies = [ "ahash", "anyhow", @@ -5917,7 +5917,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "play" -version = "0.3.0-rc.1" +version = "0.3.0-rc.2" dependencies = [ "clap", "color-eyre", @@ -9164,7 +9164,7 @@ checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xtask" -version = "0.3.0-rc.1" +version = "0.3.0-rc.2" dependencies = [ "clap", "color-eyre", diff --git a/Cargo.toml b/Cargo.toml index c3eefa37..44db8fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ exclude = ["src/deno_systems"] resolver = "2" [workspace.package] -version = "0.3.0-rc.1" +version = "0.3.0-rc.2" edition = "2021" [workspace.dependencies] diff --git a/README.md b/README.md index 40b372cc..d1fed987 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Before anything, make sure the following programs are available on the system. Install the ghjk cli using the installer scripts like so: ```bash -curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh" | bash +curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.2/install.sh" | bash ``` Use the following command to create a starter `ghjk.ts` in your project directory: @@ -105,7 +105,8 @@ ghjk.env("ci") .var("CI", "1") .install(ports.opentofu_ghrel()); -// each task describes it's own env as well +// tasks, invokable using CLI commands +// each one describes it's own env as well ghjk.task({ name: "run", inherit: "dev", @@ -135,8 +136,9 @@ Once you've configured your environments: More details can be found in the [user manual](./docs/manual.md). -## Development +## Contributing +Thanks for taking the interest, we have so [many](https://github.com/metatypedev/ghjk/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) cool features to implement yet! Use the following command to enter a shell where the ghjk CLI is based on the code that's in the working tree. This will setup a separate installation at `.dev`. diff --git a/deno.jsonc b/deno.jsonc index 021fe84a..5550d983 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -4,8 +4,7 @@ "ghjk/": "./" }, "tasks": { - "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", - "test-doc": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", + "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A --doc **/*.ts", "test-rust": "cargo test", "ghjk": "cargo run -p ghjk", "cache": "deno cache deps/*", @@ -24,7 +23,9 @@ "./src/deno_systems/bindings.ts", "./src/ghjk/js", "./files/deno/bindings.ts", - "./tools" + "./tools", + "./examples", + "ghjk.ts" ] }, "fmt": { diff --git a/docs/manual.md b/docs/manual.md index 22dfe2fe..656f5ca9 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -17,7 +17,7 @@ There are installer scripts available in the repo. ```bash # stable -curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/install.sh" | bash +curl -fsSL "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.2/install.sh" | bash ``` This will install the CLI and add configuration your shell rc files the necessary hooks ghjk needs to function. @@ -39,6 +39,9 @@ Look through the following snippet to understand the basic structure of a `ghjk. ```ts // all ghjk.ts files are expected to export this special `sophon` object export { sophon } from "ghjk"; +// by default, a `deno.jsonc` file is created in the `.ghjk/` directory +// which will provide the `ghjk` import alias configured to the CLI's version +// of ghjk import { file } from "ghjk"; // import the port for the node program import node from "ghjk/ports/node.ts"; @@ -109,7 +112,7 @@ Thankfully, through the great sandbox provided through Deno's implementation, th - Environment variables read during serialization - Configuration used by the ghjk cli -This doesn't cover everything though and the `ghjk.ts` implementation generally assumes a declarative paradigm of programming. +This doesn't cover everything though, and the `ghjk.ts` implementation generally assumes a declarative paradigm of programming. You'll generally want to avoid any logic that's not deterministic and depends on inputs like time or RNGs. If you encounter any edge cases or want to force re-serialization, you can remove the hashfile at `.ghjk/hash.json` which contains hashes for change tracking. @@ -126,7 +129,7 @@ $ ghjk --help The cached value of the serialization results are stored in the lockfile. The lockfile is what the different systems of ghjk use to store transient information that needs to be tracked across serializations. -Currently, this is mainly used by the ports system to retain version numbers resolved during installation which is important for the basic need of reproducibility. +Currently, this is mainly used by the ports system to retain version numbers resolved during installation, which is important for the basic need of reproducibility. To maintain reproducibility across different machines, this file needs to be checked into version control. Unfortunately, this can lead to version conflicts during git merges for example. @@ -411,7 +414,7 @@ Namely, it's good practice to: ```dockerfile # sample of how one would install ghjk for use in a Dockerfile -ARG GHJK_VERSION=v0.3.0-rc.1 +ARG GHJK_VERSION=v0.3.0-rc.2 # /usr/bin is available in $PATH by default making ghjk immediately avail RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/${GHJK_VERSION}/install.sh" \ | GHJK_INSTALL_EXE_DIR=/usr/bin sh @@ -492,13 +495,13 @@ ghjk print config # } ``` -These is mostly set of paths to resolve ghjkfiles or other values that need to be resolved before the serializaiton process. -Most of these settings can be configured through the `config.json` file which is looked for at `.ghjk/config.json` by default. -Additionaly, most of these values can be configured through environment variables under keys that are the name of the config value prefixed by `GHJK_`. +These are generally values that need to be resolved before the serializaiton process. +Most of these settings can be configured through the `config.json` file, which is looked for at `.ghjk/config.json` by default. +Additionally, most of these values can be configured through environment variables under keys that are the name of the config value prefixed by `GHJK_`. So for the `repo_root` config, this would be resolved from the `$GHJK_REPO_ROOT` env var. Some of the values can be configured globally thorugh a file looked for at `$XDG_CONFIG_PATH/ghjk/config.json`. -The following snippet shows current config set, their defafults and an explanation of their purpose. +The following snippet shows the current config set, their defafults, and an explanation of their purpose. ```jsonc { @@ -535,3 +538,4 @@ The following snippet shows current config set, their defafults and an explanati ``` In addition to a `config.json` files, `config.json5` files are also supported which is a [friendlier superset of JSON](https://json5.org/) with support for comments and more. +Note that environment varible resolved config takes precedence over the local `config.json` which takes precedence over globally configured values. diff --git a/ghjk.ts b/ghjk.ts index 0b7f9a38..1815743d 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -149,7 +149,8 @@ ghjk.task( ghjk.task( "lock-sed", async ($) => { - const GHJK_VERSION = "0.3.0-rc.1"; + const GHJK_VERSION = "0.3.0-rc.2"; + await sedLock( $.path(import.meta.dirname!), { diff --git a/host/init/mod.ts b/host/init/mod.ts deleted file mode 100644 index 1418efff..00000000 --- a/host/init/mod.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { DenoTaskDefArgs, task$ } from "../../files/mod.ts"; -import { zod } from "../../deps/common.ts"; -import { - findEntryRecursive, - importRaw, - Path, - unwrapZodRes, -} from "../../utils/mod.ts"; - -// NOTE: only limited subset of task featutres are avail. -// no environments and deps -const tasks: Record = { - "init-ts": { - desc: "Create a typescript ghjkfile in the current directory.", - async fn($, args) { - { - const ghjkdir = $.env["GHJKDIR"] ?? - await findEntryRecursive($.workingDir, ".ghjk"); - if (ghjkdir) { - throw new Error( - `already in a ghjkdir context located at: ${ghjkdir}`, - ); - } - } - const ghjkFilePath = $.workingDir.join("ghjk.ts"); - if (!await ghjkFilePath.exists()) { - const templatePath = import.meta.resolve("./template.ts"); - const ghjkRoot = import.meta.resolve("../../"); - const template = await importRaw(templatePath); - const final = template.replaceAll( - /from "..\/..\/(.*)"; \/\/ template-import/g, - `from "${ghjkRoot}$1";`, - ); - await ghjkFilePath.writeText(final); - $.logger.info("written ghjk.ts to", ghjkFilePath); - await tasks["init-ts-lsp"].fn!($, args); - } - }, - }, - "init-ts-lsp": { - desc: - "Interactively configure working directory for best LSP support of ghjk.ts. Pass --yes to confirm every choice.", - async fn($) { - const all = $.argv[0] == "--yes"; - const ghjkfile = $.env["GHJKFILE"] ?? "ghjk.ts"; - const changeVscodeSettings = all || await $.confirm( - `Configure deno lsp to selectively enable on ${ghjkfile} through .vscode/settings.json?`, - { - default: true, - }, - ); - if (changeVscodeSettings) { - const vscodeSettingsRaw = await $.prompt( - "Path to .vscode/settings.json ghjk working dir", - { - default: ".vscode/settings.json", - }, - ); - await handleVscodeSettings( - $, - ghjkfile, - $.workingDir.join(vscodeSettingsRaw), - ); - } - const ghjkfilePath = $.workingDir.join(ghjkfile); - if (await ghjkfilePath.exists()) { - const content = await ghjkfilePath.readText(); - if (/@ts-nocheck/.test(content)) { - $.logger.info(`@ts-nocheck detected in ${ghjkfile}, skipping`); - return; - } - const changeGhjkts = await $.confirm( - `Mark ${ghjkfile} with @ts-nocheck`, - { - default: true, - }, - ); - if (changeGhjkts) { - await ghjkfilePath.writeText(` -// @ts-nocheck: Ghjkfile based on Deno - -${content}`); - } - } - }, - }, -}; - -async function handleVscodeSettings( - $: ReturnType, - ghjkfile: string, - vscodeSettings: Path, -) { - if (!await vscodeSettings.exists()) { - $.logger.error( - `No file detected at ${vscodeSettings}, creating a new one.`, - ); - const config = { - "deno.enablePaths": [ - ghjkfile, - ], - }; - vscodeSettings.writeJsonPretty(config); - $.logger.info(`Wrote config to ${vscodeSettings}`, config); - return; - } - - const schema = zod.object({ - "deno.enablePaths": zod.string().array().nullish(), - "deno.disablePaths": zod.string().array().nullish(), - deno: zod.object({ - enablePaths: zod.string().array().nullish(), - disablePaths: zod.string().array().nullish(), - }).passthrough().nullish(), - }).passthrough(); - - const originalConfig = await vscodeSettings.readJson() - .catch((err) => { - throw new Error(`error parsing JSON at ${vscodeSettings}`, { - cause: err, - }); - }); - const parsedConfig = unwrapZodRes(schema.safeParse(originalConfig), { - originalConfig, - }, "unexpected JSON discovered at .vscode/settings.json"); - - let writeOut = false; - - if (parsedConfig["deno.enablePaths"]) { - if (!parsedConfig["deno.enablePaths"].includes(ghjkfile)) { - $.logger.info( - `Adding ${ghjkfile} to "deno.enablePaths"`, - ); - parsedConfig["deno.enablePaths"].push(ghjkfile); - writeOut = true; - } else { - $.logger.info( - `Detected ${ghjkfile} in "deno.enablePaths", skipping`, - ); - } - } else if (parsedConfig.deno?.enablePaths) { - if (!parsedConfig.deno.enablePaths.includes(ghjkfile)) { - $.logger.info( - `Adding ${ghjkfile} to deno.enablePaths`, - ); - parsedConfig.deno.enablePaths.push(ghjkfile); - writeOut = true; - } else { - $.logger.info( - `Detected ${ghjkfile} in deno.enablePaths, skipping`, - ); - } - } else if (parsedConfig["deno.disablePaths"]) { - if (parsedConfig["deno.disablePaths"].includes(ghjkfile)) { - throw new Error( - `${ghjkfile} detected in "deno.disablePaths". Confused :/`, - ); - } else { - $.logger.info( - `No ${ghjkfile} in "deno.disablePaths", skipping`, - ); - } - } else if (parsedConfig.deno?.disablePaths) { - if (parsedConfig.deno.disablePaths.includes(ghjkfile)) { - throw new Error( - `${ghjkfile} detected in deno.disablePaths. Confused :/`, - ); - } else { - $.logger.info( - `No ${ghjkfile} in deno.disablePaths, skipping`, - ); - } - } else { - parsedConfig["deno.enablePaths"] = [ghjkfile]; - writeOut = true; - $.logger.info( - `Adding ${ghjkfile} to "deno.enablePaths"`, - ); - } - if (writeOut) { - vscodeSettings.writeJsonPretty(parsedConfig); - $.logger.info(`Wrote config to ${vscodeSettings}`, parsedConfig); - } -} -export default tasks;