diff --git a/.ghjk/config.json b/.ghjk/config.json new file mode 100644 index 0000000..129c0ea --- /dev/null +++ b/.ghjk/config.json @@ -0,0 +1,3 @@ +{ + "deno_json": "../deno.jsonc" +} diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 8ec1e4b..4e2c93a 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 32181f4..fe54526 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" }, @@ -363,7 +403,7 @@ "lock-sed": { "ty": "denoFile@v1", "key": "lock-sed", - "envKey": "bciqocjamyeiuh6llwcdqg4q4ceantuzpbm5bmnlz7pkqz4r2ca7w2eq" + "envKey": "bciqei2rn3w6xakdaaapst7yyp36wjmz3txqtja24gl5v3fgwqcsa34i" }, "cache-v8": { "ty": "denoFile@v1", @@ -390,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", @@ -404,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", @@ -417,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", @@ -430,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", @@ -451,64 +491,15 @@ }, "defaultEnv": "dev", "envsNamed": { - "main": "bciqfnku2tswsz4gapwhys5ox5uiyzcb5r7bmuwzljjeziljcu7efroi", - "_rust": "bciqex5g2cetqvfipwhu6fb3mmyke3y6jvrscjrykf2zl7wfwupiqhca", - "dev": "bciqgcwltl3sbuyrqlhxz2spihe2asdzrgt3axosw3mre7ived23syhy" + "main": "bciqenvipb7pm4gge77liqnjhvdv7nhckd6kqjg3bhc3pc6e4g6o2zwa", + "_rust": "bciqm23m6kl7m2mdbjmcjoleysme4gwtkzeeqrbyrpydpm3fvx3bn25a", + "ci": "bciqm23m6kl7m2mdbjmcjoleysme4gwtkzeeqrbyrpydpm3fvx3bn25a", + "dev": "bciqddyi4oxhgfvgejlmq5s4psdv3ajzc7unzyc2kq35q6nlix6c4yai" } } } ], "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 0000000..2cc072f --- /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 1517cff..24aba71 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 @@ -84,7 +84,9 @@ 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 # test-action: # runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0766cc..4f8a3bd 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/Cargo.lock b/Cargo.lock index 6b1776b..981c530 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 c3eefa3..44db8fd 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 c5f884f..d1fed98 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: @@ -61,17 +61,14 @@ Ghjk is primarily configured through constructs called "environments" or "envs" They serve as recipes for making (mostly) reproducible posix shells. ```ts -import { file } from "https://raw.github.com/metatypedev/ghjk/v0.3.0-rc.1/mod.ts"; +// NOTE: `ghjk.ts` files are expected to export this sophon object +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({}); -// 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; - // top level `install`s go to the `main` env ghjk.install(ports.protoc()); ghjk.install(ports.rust()); @@ -108,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", @@ -138,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 b2f858e..5550d98 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,11 +1,33 @@ { + "imports": { + "ghjk": "./mod.ts", + "ghjk/": "./" + }, "tasks": { - "test": "cargo build -p ghjk && deno test --unstable-worker-options --unstable-kv -A tests/*", - "self": "cargo run -p ghjk", + "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/*", "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", + "./examples", + "ghjk.ts" + ] + }, "fmt": { "exclude": [ ".git", diff --git a/deps/cli.ts b/deps/cli.ts index 9829c22..9294775 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/docs/installation-vars.md b/docs/installation-vars.md index 967a944..9f67a0e 100644 --- a/docs/installation-vars.md +++ b/docs/installation-vars.md @@ -2,14 +2,9 @@ | 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 f4dedf3..656f5ca 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. @@ -37,19 +37,20 @@ 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 "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 ".../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 +// serialization. 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; - // install programs (ports) into your env ghjk.install( node({ version: "14.17.0" }), @@ -63,7 +64,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`). @@ -78,7 +79,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 @@ -95,7 +96,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 @@ -109,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. +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. ```bash @@ -128,8 +128,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. @@ -143,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. @@ -157,7 +157,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 +199,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(); @@ -214,7 +216,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: @@ -312,9 +314,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 +348,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"; // 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(); @@ -406,23 +408,23 @@ 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 +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 \ +RUN curl -fsSL "https://raw.github.com/metatypedev/ghjk/${GHJK_VERSION}/install.sh" \ | GHJK_INSTALL_EXE_DIR=/usr/bin sh ``` ### 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. -It also prevents the `ghjk envs activate` command from functioning which requires that these hooks be run before each command. +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. ```bash @@ -481,3 +483,59 @@ 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 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 the 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. +Note that environment varible resolved config takes precedence over the local `config.json` which takes precedence over globally configured values. diff --git a/examples/env_vars/ghjk.ts b/examples/env_vars/ghjk.ts index 1d98f92..181f750 100644 --- a/examples/env_vars/ghjk.ts +++ b/examples/env_vars/ghjk.ts @@ -1,4 +1,5 @@ -import { file } from "../../hack.ts"; +export { sophon } from "ghjk/hack.ts"; +import { file } from "ghjk/hack.ts"; const ghjk = file({ defaultEnv: "empty", @@ -11,7 +12,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 3a2e7c5..64b08f3 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 ca2aa82..d889b7d 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -1,7 +1,10 @@ -import { stdDeps } from "../../files/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 +// interact with your ghjkfile const ghjk = file({ // configre an empty env so that no ports are avail by default in our workdir defaultEnv: "empty", @@ -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 b5a3a15..671e5f0 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 61b227f..22b5d4c 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/host/init/template.ts b/examples/template.ts similarity index 55% rename from host/init/template.ts rename to examples/template.ts index fdf1460..f396081 100644 --- a/host/init/template.ts +++ b/examples/template.ts @@ -1,20 +1,18 @@ // @ts-nocheck: Deno based +export { sophon } from "ghjk"; +import { file } from "ghjk"; +// import * as ports from "ghjk/ports/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 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 8bf47f1..e294f45 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,6 +22,11 @@ async function serialize(args: zod.infer) { const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(args.uri); + if (!mod.sophon) { + throw new Error( + `no sophon found on exported ghjk object from ghjk.ts: ${args.uri}`, + ); + } const rawConfig = await mod.sophon.getConfig(args.uri, mod.secureConfig); const config = JSON.parse(JSON.stringify(rawConfig)); return { diff --git a/files/deno/worker.ts b/files/deno/worker.ts deleted file mode 100644 index ca55221..0000000 --- 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 dc30b66..64f08a6 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 @@ -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/files/types.ts b/files/types.ts new file mode 100644 index 0000000..010baf8 --- /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 79e8be0..1815743 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,17 +1,30 @@ // @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 * as ports from "./ports/mod.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 { sophon } from "ghjk"; +import { $, file } from "ghjk"; +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({}); + +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 +33,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,10 +146,11 @@ task( }, ); -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/hack.ts b/hack.ts index 3d2ebeb..7212e0d 100644 --- a/hack.ts +++ b/hack.ts @@ -5,12 +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"; const ghjk = file(); - -export const sophon = Object.freeze(ghjk.sophon); 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 deleted file mode 100644 index 510c20a..0000000 --- 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["GHJK_DIR"] ?? - 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().optional(), - "deno.disablePaths": zod.string().array().optional(), - deno: zod.object({ - enablePaths: zod.string().array().optional(), - disablePaths: zod.string().array().optional(), - }).passthrough().optional(), - }).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 discovored 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; diff --git a/host/types.ts b/host/types.ts deleted file mode 100644 index 68283c8..0000000 --- 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/install.sh b/install.sh index 1eaa503..8d08373 100755 --- a/install.sh +++ b/install.sh @@ -22,16 +22,17 @@ 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) -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 - VERSION="v$VERSION" + cat >&2 < bash EOF exit 1 fi -tar -C "$TMP_DIR" -xzf "$TMP_DIR/$ASSET.$EXT" "$EXE" +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}" -"$TMP_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" +"$GHJK_INSTALL_EXE_DIR/$EXE" deno run -A "$GHJK_INSTALLER_URL" -rm -r "$TMP_DIR" SHELL_TYPE=$(basename "$SHELL") @@ -152,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 @@ -165,10 +166,10 @@ if [ -n "$SHELL_CONFIG" ]; then else cat <; /** - * {@inheritdoc AddInstall} + * {@inheritDoc AddInstall} */ install: AddInstall; /** - * {@inheritdoc AddTask} + * {@inheritDoc AddTask} */ task: AddTask; /** - * {@inheritdoc AddTasks} + * {@inheritDoc AddTasks} */ tasks: AddTasks; /** @@ -133,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 @@ -206,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, @@ -307,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 996970a..748bd1d 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); diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 323fa05..b1e9ccb 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/mod.ts b/modules/mod.ts index 210079a..65dc5a8 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 98f9b7b..d683551 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 249c127..97fa847 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 680a43e..dedd01c 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -74,6 +74,11 @@ async function importAndExec( ) { const _shimHandle = shimDenoNamespace(args.envVars); const mod = await import(uri); + 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/modules/tasks/mod.ts b/modules/tasks/mod.ts index c5349c2..84e4fe3 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 4472aca..35add19 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 561fa2b..516cef4 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/asdf_plugin_git.ts b/ports/asdf_plugin_git.ts index ab7c445..a68938b 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/ports/cargobi.ts b/ports/cargobi.ts index fb3d468..9afa08a 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 22b6cce..2a1ed79 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 d1f58b7..31da664 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,9 @@ 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(), repo_root: zod.string(), deno_dir: zod.string(), }), diff --git a/src/deno_systems/types.ts b/src/deno_systems/types.ts index 85b0e17..796dc68 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 75692e1..276ee64 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 { @@ -45,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() @@ -104,7 +115,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; @@ -131,8 +142,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(); @@ -148,14 +162,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 { @@ -204,6 +218,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,10 +244,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: init::InitCommands, }, /// Access the deno cli Deno { @@ -241,155 +261,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 extracted and serialized config from the ghjkfile. - Config { - /// 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::Config { .. } => match serialized_config { - Some(config) => { - let conf_json = serde_json::to_string_pretty(&config)?; - println!("{conf_json}"); - true - } - None => false, - }, - }) - } -} - -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 0000000..c067f76 --- /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 0000000..7e73e13 --- /dev/null +++ b/src/ghjk/cli/print.rs @@ -0,0 +1,69 @@ +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 + } + 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.ghjkfile { + 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 0000000..cc7790e --- /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 command 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/config.rs b/src/ghjk/config.rs index 5157c90..64d48e7 100644 --- a/src/ghjk/config.rs +++ b/src/ghjk/config.rs @@ -1,13 +1,16 @@ use crate::interlude::*; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Config { pub ghjkfile: Option, 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) @@ -92,8 +101,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 + "/"; @@ -140,9 +148,10 @@ impl Config { .await .expect_or_log("tokio error")?; + // create .gitignore 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:?}"))?; @@ -155,6 +164,51 @@ 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 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()) + })) + } 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) } @@ -169,13 +223,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) @@ -194,6 +251,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() @@ -201,21 +260,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")?; } @@ -231,6 +309,8 @@ hash.json", repo_root, }, deno_lockfile, + import_map, + deno_json, } = config::Config::builder() .add_source(config::Environment::with_prefix("GHJK")) .build() @@ -244,10 +324,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/host.rs b/src/ghjk/host.rs index 4cbcb10..b2a5f07 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; @@ -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 5ec14e0..4c1135d 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 serialization + 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 5aba03c..0d575a6 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 f8324dd..0a2ab48 100644 --- a/src/ghjk/main.rs +++ b/src/ghjk/main.rs @@ -1,14 +1,13 @@ #[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; + pub use color_eyre::{eyre, Section, SectionExt}; pub use denort::deno::{ self, deno_runtime::{ @@ -56,12 +55,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 bee5aa8..af1bfa9 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 }, @@ -126,6 +129,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 f3d7422..2c8b1ec 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 { @@ -259,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 f62ac73..4a90a37 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 new file mode 100644 index 0000000..4797d0b --- /dev/null +++ b/tests/examples.ts @@ -0,0 +1,29 @@ +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 -r .ghjk +rm ghjk.ts +ghjk init ts +ghjk sync +`, +}]; + +harness(cases.map((testCase) => ({ + ...testCase, + fs: { + "ghjk.ts": ` +export { sophon } from "ghjk"; +`, + }, + ePoints: [{ cmd: "fish", stdin: testCase.stdin }], + name: `examples/${testCase.name}`, +}))); diff --git a/tests/hashfile.ts b/tests/hashfile.ts index a722737..9a714dd 100644 --- a/tests/hashfile.ts +++ b/tests/hashfile.ts @@ -49,6 +49,14 @@ __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 +GHJK_DENO_LOCKFILE=deno.lock ghjk sync +test (cat tstamp) -lt (__ghjk_get_mtime_ts .ghjk/hash.json); or exit 101 `, }, ]; @@ -57,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 00b19ea..5c642ea 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 { sophon } from "ghjk/hack.ts"; +import { task } from "ghjk/hack.ts"; task({ dependsOn: [ @@ -147,12 +147,8 @@ test (cat eddy) = 'ed edd eddy' { name: "dyn_vars", ghjkTs: ` -import { file } from "$ghjk/hack.ts"; - -const ghjk = file({}); - -export const sophon = ghjk.sophon; -const { env, task } = ghjk; +export { sophon } from "ghjk/hack.ts"; +import { env, task } from "ghjk/hack.ts"; env("main") .var("A", "A#STATIC") diff --git a/tests/todo.ts b/tests/todo.ts index 53f57f2..cc230d6 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 bc3e023..6f8fa01 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); @@ -157,7 +157,8 @@ export function genTsGhjkFile( ).join("\n"); return ` -import { file } from "$ghjk/mod.ts"; +export { sophon } from "ghjk"; +import { file } from "ghjk"; const confStr = \` ${serializedSecConf} @@ -165,8 +166,6 @@ ${serializedSecConf} const confObj = JSON.parse(confStr); const ghjk = file(confObj); -export const sophon = ghjk.sophon; - ${tasks} `; diff --git a/tools/dev.ts b/tools/dev.ts index d6e737c..6284dab 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 464a43d..62d533f 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();