diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 74ec5489..11f98672 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -532,6 +532,48 @@ "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/utils/mod.ts": "fe8b14465fbcbf3a952af48083a17304c294f296591752dff3ca141386c2d46b", "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/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", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/host/mod.ts": "af5a9704c3a5b410b322afe0bc8caaaac5b28e1e1591d82b0c5fb53f92cbc97f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/main.ts": "ecd5e83be2d8f351058ad44424cad1f36dd2e3d76f6e8409afc47682a9eff01a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/posix.ts": "b22f9564d9773548d537c95265e694a2630c3fe1fd63354d6f4790e275545299", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/reducer.ts": "76ee6974c9d4885da0898e01c498dcfdd99a3652a5a564d679577931a680e781", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/types.ts": "9ff28d47aa60042df42fbb98a46f7689d8111be462237f5fb81771011e429088", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/mod.ts": "646cfe12c181f378ffd865890e07ba0a2c92b70cf10687f43de49864ca15c482", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/deno.ts": "75b85d8cdc129e56d7bd1bfbfdc4a6f4685e86933c41908e48fbc51be7a57fee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/exec.ts": "ddc6bc7cbed464fdd94038a0df8668138411e94e49ae639615b93e734e37d311", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/mod.ts": "25901b5a03625353cc0d9c024daca806eb2513b153faede5ecad73b428542721", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" } } diff --git a/.ghjk/lock.json b/.ghjk/lock.json index cad9504a..efabdb07 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -1,80 +1,101 @@ { "version": "0", - "platform": "aarch64-darwin", + "platform": "x86_64-linux", "moduleEntries": { "ports": { "version": "0", "configResolutions": { - "bciqay4m4kmzfduj5t2clgejxgpe5zwper6lyyaxt7rhbjalaqd32nhq": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - }, "bciqjlw6cxddajjmznoemlmnu7mgbbm7a3hfmnd2x5oivwajmiqui5ey": { - "version": "v0.2.64", + "version": "v0.2.69", "buildDepConfigs": {}, "portRef": "act_ghrel@0.1.0", "specifiedVersion": false }, "bciqao2s3r3r33ruox4qknfrxqrmemuccxn64dze2ylojrzp2bwvt4ji": { - "version": "3.7.1", + "version": "4.0.1", "buildDepConfigs": { "cpy_bs_ghrel": { - "version": "3.12.4", + "version": "3.12.7", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false } }, "portRef": "cpy_bs_ghrel@0.1.0", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "pipi_pypi@0.1.0", "packageName": "pre-commit", "specifiedVersion": false }, - "bciqij3g6mmbjn4a6ps4eipcy2fmw2zumgv5a3gbxycthroffihwquoi": { - "version": "3.12.4", + "bciqh4dfyevkzzvmnhuz7lcfqttkg5neg4ewhoxzofyuicavgk6pb7ia": { + "version": "3.12.7", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false } }, "portRef": "cpy_bs_ghrel@0.1.0", - "specifiedVersion": false + "specifiedVersion": true }, "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false }, + "bciqoawx3omfmmhaw25mgrujoxl5wkdwfzbmidfqah2zst7cildtcpeq": { + "version": "3.8.0.0", + "buildDepConfigs": { + "cpy_bs_ghrel": { + "version": "3.12.7", + "buildDepConfigs": { + "tar_aa": { + "version": "1.35", + "buildDepConfigs": {}, + "portRef": "tar_aa@0.1.0", + "specifiedVersion": false + }, + "zstd_aa": { + "version": "v1.5.6,", + "buildDepConfigs": {}, + "portRef": "zstd_aa@0.1.0", + "specifiedVersion": false + } + }, + "portRef": "cpy_bs_ghrel@0.1.0", + "specifiedVersion": true + } + }, + "portRef": "pipi_pypi@0.1.0", + "packageName": "vale", + "specifiedVersion": false + }, "bciqfvlwwndlfuqibybkgee3fgt7cst5ltpztmm3by6hib5veial5spy": { "version": "v1.44.2", "buildDepConfigs": {}, @@ -100,14 +121,14 @@ "installs": [ "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", - "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q", + "bciqpu4klxr3hl6ujhmflrlfd3dxp47ijq26mnathb26ojzwkeggy5ii", "bciqmgggy7hd5as3zz7pzbx54va7lq657bdxvthntxphhlbsl2434dgq" ], - "allowedBuildDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" + "allowedBuildDeps": "bciqicqqpm2733snzw4pmjnkbw7qbbji7t7cpwj2nbrok6abrzauooqa" }, "ghjkEnvProvInstSet___test": { "installs": [], - "allowedBuildDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" + "allowedBuildDeps": "bciqicqqpm2733snzw4pmjnkbw7qbbji7t7cpwj2nbrok6abrzauooqa" } } } @@ -207,29 +228,39 @@ }, "packageName": "pre-commit" }, - "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q": { + "bciqpu4klxr3hl6ujhmflrlfd3dxp47ijq26mnathb26ojzwkeggy5ii": { "port": { "ty": "denoWorker@v1", - "name": "cpy_bs_ghrel", + "name": "pipi_pypi", "platforms": [ "x86_64-linux", "aarch64-linux", "x86_64-darwin", "aarch64-darwin", "x86_64-windows", - "aarch64-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": "zstd_aa" + "name": "cpy_bs_ghrel" } ], - "moduleSpecifier": "file:///ports/cpy_bs.ts" - } + "moduleSpecifier": "file:///ports/pipi.ts" + }, + "packageName": "vale" }, "bciqmgggy7hd5as3zz7pzbx54va7lq657bdxvthntxphhlbsl2434dgq": { "version": "1.44.2", @@ -409,42 +440,6 @@ "portRef": "rustup_rustlang@0.1.0" } }, - "bciqjcmf46h2h6teenwbsda35igg4hea6ro5vh6nfieehk4jkuiqaj2a": { - "manifest": { - "ty": "denoWorker@v1", - "name": "rust_rustup", - "platforms": [ - "x86_64-linux", - "aarch64-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": "rustup_rustlang" - } - ], - "moduleSpecifier": "file:///ports/rust.ts" - }, - "defaultInst": { - "portRef": "rust_rustup@0.1.0" - } - }, "bciqpgt5wsiw4y7qzovqbt2yrdgq5mvhhjpcg6cxzt4w4taudyen44ca": { "manifest": { "ty": "denoWorker@v1", @@ -462,29 +457,38 @@ "portRef": "cargo_binstall_ghrel@0.1.0" } }, - "bciqo7cq7igschrhers3wiibbqpaavdf33fdfdalr4cu7gxr7cblifby": { + "bciqakxf4wsx3mtku4x5exrl44k4r4kyq6gaw4va5hsb3v26cipmmekq": { "manifest": { "ty": "denoWorker@v1", - "name": "pnpm_ghrel", + "name": "cpy_bs_ghrel", "platforms": [ - "aarch64-linux", "x86_64-linux", - "aarch64-darwin", + "aarch64-linux", "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" + "aarch64-darwin", + "x86_64-windows", + "aarch64-windows" ], "version": "0.1.0", - "moduleSpecifier": "file:///ports/pnpm.ts" + "buildDeps": [ + { + "name": "tar_aa" + }, + { + "name": "zstd_aa" + } + ], + "moduleSpecifier": "file:///ports/cpy_bs.ts" }, "defaultInst": { - "portRef": "pnpm_ghrel@0.1.0" + "version": "3.12.7", + "portRef": "cpy_bs_ghrel@0.1.0" } }, - "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli": { + "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi": { "manifest": { "ty": "denoWorker@v1", - "name": "asdf_plugin_git", + "name": "node_org", "platforms": [ "aarch64-linux", "x86_64-linux", @@ -496,84 +500,93 @@ "version": "0.1.0", "buildDeps": [ { - "name": "git_aa" - } - ], - "resolutionDeps": [ - { - "name": "git_aa" + "name": "tar_aa" } ], - "moduleSpecifier": "file:///ports/asdf_plugin_git.ts" + "moduleSpecifier": "file:///ports/node.ts" }, "defaultInst": { - "portRef": "asdf_plugin_git@0.1.0" + "portRef": "node_org@0.1.0" } }, - "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi": { + "bciqhjmjvlo4aqvwxjkrmoiledobmhkfe7iovqe3wndxiw2aootwn4lq": { "manifest": { "ty": "denoWorker@v1", - "name": "node_org", + "name": "rust_rustup", "platforms": [ - "aarch64-linux", "x86_64-linux", - "aarch64-darwin", + "aarch64-linux", "x86_64-darwin", + "aarch64-darwin", + "x86_64-windows", "aarch64-windows", - "x86_64-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": "rustup_rustlang" } ], - "moduleSpecifier": "file:///ports/node.ts" + "moduleSpecifier": "file:///ports/rust.ts" }, "defaultInst": { - "portRef": "node_org@0.1.0" + "portRef": "rust_rustup@0.1.0", + "profile": "minimal" } }, - "bciqctvtiscapp6cmlaxuaxnyac664hs3y3xsa5kqh4ctmhbsiehusly": { + "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli": { "manifest": { "ty": "denoWorker@v1", - "name": "cpy_bs_ghrel", + "name": "asdf_plugin_git", "platforms": [ - "x86_64-linux", "aarch64-linux", - "x86_64-darwin", + "x86_64-linux", "aarch64-darwin", - "x86_64-windows", - "aarch64-windows" + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" ], "version": "0.1.0", "buildDeps": [ { - "name": "tar_aa" - }, + "name": "git_aa" + } + ], + "resolutionDeps": [ { - "name": "zstd_aa" + "name": "git_aa" } ], - "moduleSpecifier": "file:///ports/cpy_bs.ts" + "moduleSpecifier": "file:///ports/asdf_plugin_git.ts" }, "defaultInst": { - "portRef": "cpy_bs_ghrel@0.1.0" + "portRef": "asdf_plugin_git@0.1.0" } }, - "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi": { + "bciqicqqpm2733snzw4pmjnkbw7qbbji7t7cpwj2nbrok6abrzauooqa": { "tar_aa": "bciqb6ua63xodzwxngnbjq35hfikiwzb3dclbqkc7e6xgjdt5jin4pia", "git_aa": "bciqfl5s36w335ducrb6f6gwb3vuwup7vzqwwg67pq42xtkngsnxqobi", "curl_aa": "bciqcfe7qyxmokpn6pgtaj35r5qg74jkehuu6cvyrtcsnegvwlm64oqy", "unzip_aa": "bciqgkpwxjmo5phw5se4ugyiz4xua3xrd54quzmk7wdwpq3vghglogjy", "zstd_aa": "bciqmcvyepuficjj3mwshsbfecwdmzch5gwxqo557icnq4zujtdllh4a", "rustup_rustlang": "bciqk4ivbyqvpxwcaj5reufmveqldiizo6xmqiqq7njtaczgappydoka", - "rust_rustup": "bciqjcmf46h2h6teenwbsda35igg4hea6ro5vh6nfieehk4jkuiqaj2a", "cargo_binstall_ghrel": "bciqpgt5wsiw4y7qzovqbt2yrdgq5mvhhjpcg6cxzt4w4taudyen44ca", - "pnpm_ghrel": "bciqo7cq7igschrhers3wiibbqpaavdf33fdfdalr4cu7gxr7cblifby", - "asdf_plugin_git": "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli", + "cpy_bs_ghrel": "bciqakxf4wsx3mtku4x5exrl44k4r4kyq6gaw4va5hsb3v26cipmmekq", "node_org": "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi", - "cpy_bs_ghrel": "bciqctvtiscapp6cmlaxuaxnyac664hs3y3xsa5kqh4ctmhbsiehusly" + "rust_rustup": "bciqhjmjvlo4aqvwxjkrmoiledobmhkfe7iovqe3wndxiw2aootwn4lq", + "asdf_plugin_git": "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli" } } } diff --git a/.vscode/settings.json b/.vscode/settings.json index 0fae416b..cd2b6816 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "deno.enablePaths": [ - "." + ".", + "ghjk.ts" ], "deno.suggest.completeFunctionCalls": true, "deno.inlayHints.variableTypes.enabled": true, @@ -18,7 +19,13 @@ "editor.defaultFormatter": "denoland.vscode-deno" }, "cSpell.words": [ - "ghjk" + "DENO", + "ghjk", + "ghjkfile", + "Hashfile", + "POSIX", + "runtimes", + "sophon" ], "spellright.language": [ "en-US-10-1." diff --git a/README.md b/README.md index 70c34cde..d64ac403 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ ghjk /gk/ is a programmable runtime manager and an attempt at a successor for [a ## Introduction -ghjk offers a unified abstraction to manage package managers (e.g. cargo, pnpm, poetry), languages runtimes (e.g. nightly rust, node@18, python@latest) and developer tools (e.g. pre-commit, eslint, protoc). It enables you to define a consistent environment across your dev environments, CI/CD pipelines and containers keeping everything well-defined in your repo and providing a great DX. - -ghjk was designed to be an intermediate alternative between [Earthly](https://github.com/earthly/earthly)/[Dagger](https://github.com/dagger/dagger) (lighter and more flexible) and complex building tools like [Bazel](https://github.com/bazelbuild/bazel/)/[Nix-based devenv](https://github.com/cachix/devenv) (simpler and more extensible). This makes it especially convenient for mono-repos and long-lived projects. See [Metatype](https://github.com/metatypedev/metatype) and its [ghjkfile](https://github.com/metatypedev/metatype/blob/main/ghjk.ts) for a real world example. +ghjk offers a unified abstraction to manage package managers (e.g. cargo, pnpm, poetry), languages runtimes (e.g. nightly rust, node@18, python@latest) and developer tools (e.g. pre-commit, eslint, protoc). +It enables you to define a consistent environment across your dev environments, CI/CD pipelines and containers keeping everything well-defined in your repo and providing a great DX. +This makes it especially convenient for mono-repos and long-lived projects. See [Metatype](https://github.com/metatypedev/metatype) and its [ghjkfile](https://github.com/metatypedev/metatype/blob/main/ghjk.ts) for a real world example.

ghjk @@ -21,58 +21,56 @@ ghjk was designed to be an intermediate alternative between [Earthly](https://gi ## Features -- Soft-reproducable developer environments. -- Install posix programs from different backend like npm, pypi, crates.io. -- Tasks written in typescript. -- Run tasks when entering/exiting envs. +- Install standalone posix programs or those found on different backends and registries + - [npm](./ports/npmi.ts) + - [pypi](./ports/pipi.ts) + - [crates.io](./ports/cargobi.ts) + - [ ] [Github releases](https://github.com/metatypedev/ghjk/issues/79) +- Tasks written in typescript + - Ergonomically powered by [`dax`](https://github.com/dsherret/dax). + - Built on Deno, most dependencies are [an import away](https://docs.deno.com/runtime/fundamentals/modules/#importing-third-party-modules-and-libraries). +- Soft-reproducible posix environments. + - Declaratively compose envs for developer shells, CI and tasks. + - Run tasks when entering/exiting envs. ## Getting started -```bash -# stable -curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install.sh | bash -# latest (main) -curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install.sh | GHJK_VERSION=main bash/fish/zsh -``` +Before anything, make sure the following programs are available on the system. -In your project, create a configuration file called `ghjk.ts` that look something like: +- git +- tar (preferably GNU tar) +- curl +- unzip +- zstd -```ts -// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the 'sophon' proxy -// object exported here. -// WARN: always import `hack.ts` file first -export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts"; -import { - install, - task, -} from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts"; -import node from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/ports/node.ts"; - -// install programs (ports) into your env -install(node({ version: "14.17.0" })); - -// write simple scripts and execute them using -// `$ ghjk x greet` -task("greet", async ($, { argv: [name] }) => { - await $`echo Hello ${name}!`; -}); +Install the ghjk cli using the installer scripts like so: + +```bash +curl -fsSL https://raw.github.com/metatypedev/ghjk/v0.2.1/install.sh | bash ``` -Use the following command to then access your environment: +Use the following command to create a starter `ghjk.ts` in your project directory: ```bash -ghjk sync +ghjk init ts ``` ### Environments Ghjk is primarily configured through constructs called "environments" or "envs" for short. -They serve as recipes for making (mostly) reproducable posix shells. +They serve as recipes for making (mostly) reproducible posix shells. ```ts -export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts"; -import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts"; -import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mod.ts"; +import { file } from "https://raw.github.com/metatypedev/ghjk/v0.2.1/mod.ts"; +// ports are small programs that install sowtware to your envs +import * as ports from "https://raw.github.com/metatypedev/ghjk/v0.2.1/ports/mod.ts"; + +const ghjk = file({}); + +// NOTE: `ghjk.ts` files are expected to export this sophon object +// all the functions on the ghjk object are ultimately modifying the 'sophon' proxy +// object exported here. +export const sophon = ghjk.sophon; // top level `install`s go to the `main` env ghjk.install(ports.protoc()); @@ -105,8 +103,10 @@ ghjk.env({ installs: [ports.cargobi({ crateName: "cargo-chef" }), ports.zstd()], }); -// builder syntax is also availaible -ghjk.env("ci").var("CI", "1").install(ports.opentofu_ghrel()); +// builder syntax is also available +ghjk.env("ci") + .var("CI", "1") + .install(ports.opentofu_ghrel()); // each task describes it's own env as well ghjk.task({ @@ -114,93 +114,44 @@ ghjk.task({ inherit: "dev", fn: () => console.log("online"), }); -``` - -Once you've configured your environments: - -- `$ ghjk envs cook $name` to reify and install an environment. -- `$ ghjk envs activate $name` to switch to an environment. -- And **most** usefully, `$ ghjk sync $name` to cook and _then_ activate an - environment. - - If shell is already in the specified env, it only does cooking. - - Make sure to `sync` or `cook` your envs after changes. -- If no `$name` is provided, most of these commands will operate on the default - or currently active environment. - -### Ports - -TBD: this feature is in development. -Look in the [kitchen sink](./examples/kitchen/ghjk.ts) for what's currently implemented. - -### Tasks - -TBD: this feature is still in development. -Look in the [tasks example](./examples/tasks/ghjk.ts) for what's currently implemented. -#### Anonymous tasks - -Tasks that aren't give names cannot be invoked from the CLI. -They can be useful for tasks that are meant to be common dependencies of other tasks. - -### `hack.ts` - -The imports from the `hack.ts` module, while nice and striaght forward to use, hold and modify global state. -Any malicious third-party module your ghjkfile imports will thus be able to access them as well, provided they import the same version of the module. - -```ts -// evil.ts -import { env, task } from "https://.../ghjk/hack.ts"; - -env("trueBase").install(ports.act(), ports.pipi({ packageName: "ruff" })); - -env("test").vars({ DEBUG: 1 }); - -// `stdSecureConfig` is a quick way to make an up to spec `secureConfig`. -export const secureConfig = stdSecureConfig({ - defaultBaseEnv: "trueBase", - defaultEnv: "test", +ghjk.config({ + defaultBaseEnv: "main", + defaultEnv: "main", // by default, nodejs, python and other runtime // ports are not allowed to be used // during the build process of other ports. // Disable this security measure here. - // (More security features inbound!.) enableRuntimes: true, }); ``` -To prevent this scenario, the exports from `hack.ts` inspect the call stack and panic if they detect more than one module using them. -This means if you want to spread your ghjkfile across multiple modules, you'll need to use functions described below. - -> [!CAUTION] -> The panic protections of `hack.ts` described above only work if the module is the first import in your ghjkfile. -> If a malicious script gets imported first, it might be able to modify global primordials and get around them. -> We have more ideas to explore on hardening Ghjk security. -> This _hack_ is only a temporary compromise while Ghjk is in alpha state. - -The `hack.ts` file is only optional though and a more verbose but safe way exists through... +Once you've configured your environments: -```ts -import { file } from "https://.../ghjk/mod.ts"; +- `$ ghjk envs cook $name` to reify and install an environment. +- `$ ghjk envs activate $name` to activate/switch to an environment. +- And **most** usefully, `$ ghjk sync $name` to cook and _then_ activate an + environment. + - Make sure to `sync` or `cook` your envs after changes. +- If no `$name` is provided, most of these commands will operate on the default + or currently active environment. -const ghjk = file({ - // items from `config()` are availaible here - defaultEnv: "dev", +More details can be found in the [user manual](./docs/manual.md). - // can even directly add installs, tasks and envs here - installs: [], -}); +## Development -// we still need this export for this file to be a valid ghjkfile -export const sophon = ghjk.sophon; +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`. -// the builder functions are also accessible here -const { install, env, task, config } = ghjk; +```bash +$ deno task dev bash/fish/zsh ``` -If you intend on using un-trusted third-party scripts in your ghjk, it's recommended you avoid `hack.ts`. - -## Development +Run the tests in the repository through the deno task: ```bash -$ cat install.sh | GHJK_INSTALLER_URL=$(pwd)/install.ts bash/fish/zsh +$ deno task test ``` + +Most tests are isolated from each other using containers. +Set `$GHJK_TEST_E2E_TYPE` to `local` to use the local test runner. diff --git a/docs/available-commands.md b/docs/available-commands.md deleted file mode 100644 index 79353a4d..00000000 --- a/docs/available-commands.md +++ /dev/null @@ -1,15 +0,0 @@ - - -| Command | Description | Subcommands/Flags | -|----------------|-------------|-------------------| -| ```ghjk sync``` | Synchronize your shell to what's in your config. | | -| ```ghjk envs ls``` | List environments defined in the ghjkfile. | | -| ```ghjk envs activate ``` | Activate an environment. | | -| ```ghjk ports resolve``` | Resolve all installs declared in config. | | -| ```ghjk ports outdated``` | Show a version table for installs. | `--update-all`: update all installs which their versions is not specified in the config.
`--update-only `: update a selected install | -| ```ghjk print``` | Emit different discovered and built values to stdout. | | -| ```ghjk deno``` | Access the deno cli. | | -| ```ghjk completions``` | Generate shell completions. | | - -You can use the following flag to get help around the CLI. -`ghjk --help` or `ghjk -h` diff --git a/docs/installation-vars.md b/docs/installation-vars.md new file mode 100644 index 00000000..967a944a --- /dev/null +++ b/docs/installation-vars.md @@ -0,0 +1,15 @@ +# Installer vars + +| 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` | +| `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/known-issues.md b/docs/known-issues.md new file mode 100644 index 00000000..3a43f1e3 --- /dev/null +++ b/docs/known-issues.md @@ -0,0 +1,33 @@ +# Known issues + +## Cache invalidation of imported scripts + +Currently, ghjk is unable to track changes to local typescript files imported by the ghjk.ts file. +You can fore re-serialization of the ghjkfile by deleting the `.ghjk/hash.json` file. + +## Github API rate-limit + +The Github API is rate-limited to 60 calls per hour for un-authenticated requests. +Since many of the ports get their files from github releases, its easy to get past this limit especially in CI contexts. +This will manifest in failed 403 responses from ports trying to access the Github API. + +The best solution to this problem is to provide Github authentication tokens using the `GITHUB_TOKEN` environment variable. +Authenticated requests have a rate-limit of 5000 calls per hour. +Most of the ports will make use of this in their API calls if this environment variable is detected. + +If you're using Github CI runners, this environment variable is [automatically](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication) provided for you. +Otherwise, you'll need to generate [personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) which can be used for this purpose. +More information on the Github rate limits can be found [here](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28). + +## Leaking secure Github tokens + +Currently, any values captured by ports have to be persisted in the lockfile. +As most ports today make use of the `GITHUB_TOKEN` environment variable, it's easy to leak these tokens into the ghjk lockfile which is intended to be committed. +Until a better solution can be devised for this, it's recommended to avoid using `GITHUB_TOKEN`s on the development machine where commits are authored. +If the token ends up in your lockfile, be sure to clean it out before a commit is made. + +```bash +rm .ghjk/lock.json +# this command will re-resolve all ports +ghjk p resolve +``` diff --git a/docs/manual.md b/docs/manual.md new file mode 100644 index 00000000..b0282fba --- /dev/null +++ b/docs/manual.md @@ -0,0 +1,481 @@ +# User Manual + +Ghjk is a toolkit for declarative and programmatic configuration of POSIX runtime environments. +Currently in heavy development, it features working implementations of: +- Tool installation and management +- Task runner +- Declarative and dynamic environment variables + +This user manual is designed to be read on the Github web app within the repo that hosts the ghjk codebase. + + +## Installation + +Before anything, the ghjk CLI should be installed. +There are installer scripts available in the repo. + + +```bash +# stable +curl -fsSL https://raw.github.com/metatypedev/ghjk/v0.2.1/install.sh | bash +``` + +This will install the CLI and add configuration your shell rc files the necessary hooks ghjk needs to function. +Installation can be customized through a number of environment variables that can be found [here](./installation-vars.md). + +## `ghjk.ts` + +Ghjk is configured through a ghjkfile. +Currently, a typescript based ghjkfile is available and the rest of this documents will use typescript for configuration. +Use the following command to create a starter file in the current directory: + +```bash +# initialize a `ghjk.ts` file +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.2.1/ +import { file } from ".../mod.ts"; +// import the port for the node program +import node from ".../ports/node.ts"; + +const ghjk = file(); + +// all ghjk.ts files are expected to export this special `sophon` object +// all the functions from the ghjk object are modifying the sophon +export const sophon = ghjk.sophon; + +// install programs (ports) into your env +ghjk.install( + node({ version: "14.17.0" }), +); + +// declare tasks to be available from the command line. +ghjk.task("greet", async ($) => { + await $`echo Hello ${$.argv}!`; +}); +``` + +One can look at the [examples](../examples/) found in the ghjk repo for an exploration of the different features available. + +## `$GHJK_DIR` + +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`). +These hooks check and modify your shell environment when you create a new one or `cd` (change directory) into a ghjk relevant directory. + +What constitutes a ghjk relevant directory? +- One that contains a recognized ghjkfile format like any file called `ghjk.ts` +- One that contains a `.ghjk` directory + +Note that if any parent directory contains these files, the current directory is considered part of that ghjk context. +The `$GHJKFILE` environment variable can be set to point the CLI and hooks at a different ghjkfile. + +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. + +## Serialized + +The ghjk CLI loads your typescript file in a worker to get at the actual configuration. +This process is called _serialization_. +The CLI generally operates on the output of this serialization though it might need to load your ghjkfile in a worker again, to execute task functions you've written for example. +While the details of the output are not important, this _serialize then do_ workflow defines how ghjk functions as we should see. + +The ghjk CLI serializes any discovered ghjkfile immediately when invoked. +In fact, what commands are available on the CLI are determined by the outputs of serialization. +If you declared tasks for example, ghjk will add the `tasks` sections to invoke them. + +To look at what the ghjkfile looks like serialized, you can use the following command: + +```bash +# look at the serialized form the ghjkfile +ghjk print config +``` + +#### The Hashfile + +Loading up typescript files in workers is not the quickest of operations as it turns out. +Ghjk caches output of this serialization to improve the latency of the CLI commands. +This raises the question how well the cache invalidation works in ghjk and that's a good question. +Cache invalidation is one of the hardest problems in computer science according to lore. + +Thankfully, through the great sandbox provided through Deno's implementation, the cache is invalidated when the following items change: +- The contents of the ghjkfile +- Files accessed during serialization +- Environment variables read during serialization + +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. +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 +# remove the hashfile to force re-serialization +$ rm .ghjk/hash.json +$ 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. + +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. + +One can always remove the `.ghjk/lock.json` to remove the lockfile and recreate it. +But this can not only lead to loss of information, it can take a long time since the ports module must query different package registries to resolve versions and more. + +The best way to resolve ghjk merge conflicts is to: +- Resolve the ghjkfile conflict in a traditional manner +- Instead of manually resolving the lockfile, just pick one version entirely + - 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. +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. + +The lockfile format itself is still in flux and there are plans to improve the merge conflict experience going forward. + +## Tasks + +Tasks are pretty simple to use. +You declare them in your ghjkfile, using typescript functions, and then invoke them from the the CLI. +The CLI will then load your ghjkfile in a worker and execute your function. + +```ts +import { file } from ".../mod.ts"; + +const ghjk = file(); + +ghjk.task("greet", async ($) => { + await $`echo Hello ${$.argv}!`; +}); +``` + +```bash +# list the available tasks +$ ghjk tasks + +# x is an alias for tasks +$ ghjk x + +# invoke the greet task +# ghjk x greet ghjk +``` + +The `$` object is a enhanced version of the one from the [dax](https://jsr.io/david/jsr) library. +Amongst many things, it allows easy execution of shell commands in a cross platform way. +Look at the official documentation for all of it's illustrious powers. + +Tasks can also depend on each other meaning that the depended on task is always executed first. +Any arguments to the tasks are also passed on the `$` object or the second parameter object. +Look at the [tasks example](../examples/tasks/ghjk.ts) for more details.. + +## Envs + +Ghjk's environments, simply put, are a set of configurations for a POSIX environment. +POSIX environments are primarily defined by the current working directory and the set environment variables. +Ghjk envs then allow you: +- Set environment variables of course +- Add existing paths or newly installed program (ports) to the special `$PATH` variables +- Execute logic on entering and exiting envs +- Do all of this declaratively and in a composable manner + +Let's look at how one configures an environment using the `ghjk.ts` file: + +```ts +import { file } from ".../mod.ts"; + +const ghjk = file(); + +ghjk.env("my-env") + .var("MY_VAR", "hello POSIX!") + // we can return strings from typescript functions for dynamic + // variables + .var("MY_VAR_DYN", () => `Entered at ${new Date().toJSON()}`) + .onEnter(task(($) => console.log(`entering my-env`))) + .onExit(task(($) => console.log(`entering 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. +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: + +```bash +# look at avail sub commands +$ ghjk envs +# alias for envs +$ ghjk e +# list available envs +$ ghjk envs ls +``` + +Before we can _activate_ an environment, it needs to be _cooked_. +That is, entering an environment is a two step process. + +Cooking is what we call preparing the environment. +Required programs for the env are resolved and installed. +The shims for these programs are prepared. +The shell scripts to activate/deactivate it are prepared. +The results of env cooking are stored inside the `.ghjk/envs` directory. + +```bash +# cook a named env +$ ghjk e cook my-env +``` + +Once an environment is _cooked_, _activation_ is simple enough. +The name of the currently active environment is set to the `$GHJK_ENV` environment variable. + +```bash +# activate using the CLI +$ ghjk e activate my-env +$ echo $GHJK_ENV +# my-env +$ echo $MY_VAR +# hello POSIX! +``` + +When an env is activated in a shell session, the `ghjk_deactivate` command will be made available for deactivation. +This will remove the set variables and restore old ones if any were overwritten. +The ghjk shell hooks auto-deactivate any active environments from you shell, when it `cd`s away into a directory that's not part of the context. + +```bash +$ ghjk_deactivate +$ echo $MY_VAR +# +``` + +Note that the CLI activate command depends on the the ghjk shell hooks being available. +If not in an interactive shell, look at the CI section of this document for what options are available. + +#### `sync` + +The _cook_ and _activate_ process is common enough that there's a command available that does both, `sync`. +The `sync` command and both the `cook` and `activate` commands will operate on the currently active env if no env name argument is provided. +If no value is found at `$GHJK_ENV`, they'll use the set default env as described in the next section. + +```bash +# cook and activate an environment +$ ghjk sync my-env +``` + +### Default Env + +By default, the `main` environment is the one that's activated whenever you `cd` into the ghjk context. +You can change which env is activated by default using the `defaultEnv` setting. + +```bash +ghjk.config({ + defaultEnv: "my-env", +}); +``` + +`main` also serves as the default base all other envs inherit from. +The `defaultBaseEnv` parameter can be used to change this. + +```bash +ghjk.config({ + defaultBaseEnv: "main", +}); +``` + +## Ports + +Ports are small programs that ghjk executes to download and install programs. +When the env that includes a port installation is activated, a path to shims of the programs will be added to the special `$PATH` env variables. +This extends to modifying the appropriate `$PATH` variables for libraries or any environment variables needed for the program to function. +Currently, ports that are written in Deno flavoured typescript are supported and there's a small collection of such programs provided in the ghjk repository. + +The modules that implement port programs are also expected to expose a `conf` function as their default export. +The `conf` functions prove as a point of configuration for the port installation. +They return `InstallConfig` objects that describe user configuration along with where the port can be found and how to use it. +Any `InstallConfig` objects included in an env will then be resolved and installed when it's cooked. + +```ts +// the default export corresponds to the `conf` function +import node from ".../ports/node.ts"; +// the npmi installs executable packages from npm +import npmi from ".../ports/node.ts"; + +// top level `install` calls go to the `main` env +ghjk.install( + // configure installation for the node port + node({ version: "1.2.3" }), + // configure npmi to install the eslint package + npmi({ packageName: "eslint", version: "9" }) +); +``` + +We can then `sync` the main env to install and access the programs. + +```bash +# cook and activate +$ ghjk sync main +# the programs provided by the ports should now be available +$ node --version +$ eslint --version +``` + +### `buildDeps` + +While the Deno standard library and ESM url imports allow ports to do a lot, some ports require other programs to succeed at their tasks. +For example, the `npmi` port, which installs executable packages from npm, relies on the `npm` program for the actual functionality. +This is achieved by allowing ports to depend on other ports that they can use for tasks such as resolving available versions, downloading appropriate files, archive extraction, compilation...etc. + +As a soft security measure, ports are restricted to what other port they're allowed to depend on. +The default set includes common utilities like `curl`, `git`, `tar` and others which are used by most ports. +More ports can be easily added to the allowed port dep set. + +```ts +import { file } from ".../mod.ts"; +// barrel export for ports in the ghjk repo +import * as ports from "../../ports/mod.ts"; + +const ghjk = file(); + +ghjk.install( + ports.npmi({ packageName: "tsx" }) +) + +ghjk.config({ + allowedBuildDeps: [ + ports.node(), + ], +}); +``` + +The standard set of allowed port deps can be found [here](../modules/ports/std.ts). + +#### `enableRuntimes` + +The default set excludes scripting runtimes like `python` and `node` as another soft security measure. +Commonly used ports like `npmi`, `pipi` and `cargobi` rely on such ports to build and install programs from popular registries. +The `enableRuntimes` toggle can be used to add these common dependencies to the allowed build set. + +```ts +ghjk.config({ + enableRuntimes: true, +}); +``` + +One can look at the list of ports included by the flag [here](../modules/ports/std_runtime.ts) + +#### Ambient ports + +Ambient ports reuse programs already available on the system instead of downloading and installing one from the internet. +For a variety of reasons, the standard set of allowed port deps includes a number of these. +Please install the following programs first before attempting to use ghjk ports: + +- git +- tar (preferably GNU tar) +- curl +- unzip +- zstd + +### Writing ports + +The ports implementations is going through a lot of breaking changes. +If you need to author a new port right away, please look at the available implementations. + +## CI + +While the ghjk CLI and hooks are primarily designed for interactive shells in mind, they also support non-interactive use cases like scripts for CI jobs and for use in build tools. +The primarily difference between the two scenarios is how activation of envs is achieved as we shall see below. + +### Installation + +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. +- 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.2.1 +# /usr/bin is available in $PATH by default making ghjk immediately avail +RUN GHJK_INSTALL_EXE_DIR=/usr/bin \ + curl -fsSL https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh | bash +``` + +### 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. +In such scenarios, one can directly `source` the activation script for the target env from the `.ghjk` directory. + +```bash +# cooking must be done to make the activations scripts available +ghjk cook my-env +# there are scripts for POSIX and fish shells +source .ghjk/envs/my-env/activate.sh +echo $GHJK_ENV +# my-env +echo $MY_VAR +# hello POSIX! +``` + +Make sure to activate the environment for every shell session in your CI scripts. +In a Dockerfile, which use POSIX sh, we'll need to: + +```dockerfile +# set GHJK_ENV for use +ENV GHJK_ENV=ci +ENV GHJK_ACTIVATE=.ghjk/envs/$GHJK_ENV/activate.sh +# cook $GHJK_ENV +RUN ghjk envs cook + +# each RUN command is a separate shell session +# and requires explicit activation +RUN source "$GHJK_ACTIVATE" \ + && echo $MY_VAR +``` + +This extra boilerplate can be avoided by using the SHELL command available in some Dockerfile implementations or by using command processors more advanced that POSIX sh. + +```dockerfile +# contraption to make sh load the activate script at startup +SHELL ["/bin/sh", "-c", "source .ghjk/envs/my-env/activate.sh; sh -c $*", "sh"] +RUN echo $MY_VAR +``` + +### Github action + +For users of Github CI, there's an action available on the [marketplace](https://github.com/marketplace/actions/ghjk-everything) that is able to: +- Installs ghjk CLI and hooks +- Caches the ghjk share directory +- Cooks the `$GHJK_ENV` or default environment + +Note that the default shell used by github workflows is POSIX `sh`. +It's necessary to switch over to the `bash` shell to have the hooks auto activate your environment. +Otherwise, it's necessary to use the approach described in the section above. + +```yaml + my-job: + steps: + - uses: metatypedev/setup-ghjk@v1 + - shell: bash # must use bash shell for auto activation + run: | + echo $GHJK_ENV +``` diff --git a/examples/env_vars/ghjk.ts b/examples/env_vars/ghjk.ts index e670d440..1d98f92c 100644 --- a/examples/env_vars/ghjk.ts +++ b/examples/env_vars/ghjk.ts @@ -16,6 +16,6 @@ const { env, task } = ghjk; env("main") .var("A", "A#STATIC") - .var("C", ($) => $`echo C [$A, $B]`.text()) .var("B", () => "B#DYNAMIC") + .var("C", ($) => $`echo C [$A, $B]`.text()) .onEnter(task(($) => $`echo enter $A, $B, $C`)); diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index a88897e4..ca2aa829 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -119,4 +119,5 @@ env("dev") .onEnter(task({ workingDir: "..", fn: ($) => $`ls`, - })); + })) + .onExit(task(($) => $`echo exit`)); diff --git a/files/mod.ts b/files/mod.ts index 44c03228..dc30b666 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -25,8 +25,7 @@ import { unwrapZodRes, } from "../utils/mod.ts"; import * as std_ports from "../modules/ports/std.ts"; -import * as cpy from "../ports/cpy_bs.ts"; -import * as node from "../ports/node.ts"; +import runtime_ports from "../modules/ports/std_runtime.ts"; // host import type { SerializedConfig } from "../host/types.ts"; import * as std_modules from "../modules/std.ts"; @@ -1085,15 +1084,14 @@ export function stdDeps(args = { enableRuntimes: false }) { if (args.enableRuntimes) { out.push( ...reduceAllowedDeps([ - node.default(), - cpy.default(), + ...runtime_ports, ]), ); } return out; } -function task$( +export function task$( argv: string[], env: Record, workingDir: string, diff --git a/ghjk.ts b/ghjk.ts index 3560a6ab..f18bba97 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,3 +1,5 @@ +// @ts-nocheck: Ghjkfile based on Deno + export { sophon } from "./hack.ts"; import { config, install, task } from "./hack.ts"; import * as ports from "./ports/mod.ts"; @@ -6,6 +8,7 @@ import { sedLock } from "./std.ts"; config({ defaultBaseEnv: "test", enableRuntimes: true, + allowedBuildDeps: [ports.cpy_bs({ version: "3.12.7" })], }); // these are just for quick testing @@ -17,7 +20,7 @@ const DENO_VERSION = "1.44.2"; install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], - ports.cpy_bs(), + ports.pipi({ packageName: "vale" })[0], ports.deno_ghrel({ version: DENO_VERSION }), ); @@ -39,6 +42,16 @@ task( [/(GHJK_VERSION="\$\{GHJK_VERSION:-v).*(\}")/, GHJK_VERSION], [/(DENO_VERSION="\$\{DENO_VERSION:-v).*(\}")/, DENO_VERSION], ], + "./docs/*.md": [ + [ + /(.*\/metatypedev\/ghjk\/v)[^/]*(\/.*)/, + GHJK_VERSION, + ], + [ + /(GHJK_VERSION\s*=\s*v)[^\s]*(.*)/, + GHJK_VERSION, + ], + ], "./README.md": [ [ /(.*\/metatypedev\/ghjk\/v)[^/]*(\/.*)/, diff --git a/hack.ts b/hack.ts index bb093358..3d2ebeb8 100644 --- a/hack.ts +++ b/hack.ts @@ -15,6 +15,7 @@ export const config = Object.freeze(firstCallerCheck(ghjk.config)); export const env = Object.freeze(firstCallerCheck(ghjk.env)); export const install = Object.freeze(firstCallerCheck(ghjk.install)); export const task = Object.freeze(firstCallerCheck(ghjk.task)); +export const tasks = Object.freeze(firstCallerCheck(ghjk.tasks)); // capture exit fn to avoid malicous caller from // changing it on Deno object diff --git a/host/init/mod.ts b/host/init/mod.ts new file mode 100644 index 00000000..9e0727c2 --- /dev/null +++ b/host/init/mod.ts @@ -0,0 +1,182 @@ +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}`, 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]; + $.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/init/template.ts b/host/init/template.ts new file mode 100644 index 00000000..fdf1460e --- /dev/null +++ b/host/init/template.ts @@ -0,0 +1,22 @@ +// @ts-nocheck: Deno based + +import { file } from "../../mod.ts"; // template-import +// import * as ports from "../../ports/mod.ts"; // template-import + +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(), +); + +ghjk.task("greet", async ($) => { + await $`echo Hello ${$.argv}`; +}); diff --git a/host/mod.ts b/host/mod.ts index 8a62b233..45149bf4 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -11,9 +11,11 @@ import { import validators, { SerializedConfig } from "./types.ts"; import * as std_modules from "../modules/std.ts"; import * as denoFile from "../files/deno/mod.ts"; +import { task$ } from "../files/mod.ts"; import type { ModuleBase } from "../modules/mod.ts"; import { GhjkCtx } from "../modules/types.ts"; import { serializePlatform } from "../modules/ports/types/platform.ts"; +import { default as initTasks } from "./init/mod.ts"; export interface CliArgs { ghjkShareDir: string; @@ -102,6 +104,26 @@ export async function cli(args: CliArgs) { } } + const initCmd = new cliffy_cmd.Command() + .description("Commands for setting up cwd for ghjk.") + .action(function () { + this.showHelp(); + }); + for (const [name, def] of Object.entries(initTasks)) { + initCmd.command( + name.replace("init-", ""), + new cliffy_cmd.Command() + .description(def.desc!) + .useRawArgs() + .action(async (_, ...argv) => { + const env = Deno.env.toObject(); + const workingDir = Deno.cwd(); + const dollar = task$(argv, env, workingDir, name); + await def.fn!(dollar, { argv, workingDir, env, $: dollar }); + }), + ); + } + const root = new cliffy_cmd.Command() .name("ghjk") .version(GHJK_VERSION) @@ -110,19 +132,8 @@ export async function cli(args: CliArgs) { this.showHelp(); }) .command( - "completions", - new cliffy_cmd.CompletionsCommand(), - ) - .command( - "deno", - new cliffy_cmd.Command() - .description("Access the deno cli.") - .useRawArgs() - .action(async function (_, ...args) { - logger().debug(args); - await $.raw`${Deno.execPath()} ${args}` - .env("DENO_EXEC_PATH", Deno.execPath()); - }), + "init", + initCmd, ) .command( "print", @@ -158,7 +169,7 @@ export async function cli(args: CliArgs) { .command( "ghjkfile-path", new cliffy_cmd.Command() - .description("Print the path of the ghjk.ts used") + .description("Print the path of the ghjk.ts used.") .action(function () { if (!gcx?.ghjkfilePath) { throw new Error("no ghjkfile found."); @@ -171,7 +182,7 @@ export async function cli(args: CliArgs) { "config", new cliffy_cmd.Command() .description( - "Print the extracted ans serialized config from the ghjkfile", + "Print the extracted and serialized config from the ghjkfile.", ) .option( "--json", @@ -189,10 +200,27 @@ export async function cli(args: CliArgs) { ); }), ), + ) + .command( + "completions", + new cliffy_cmd.CompletionsCommand(), + ) + .command( + "deno", + new cliffy_cmd.Command() + .description("Access the deno cli.") + .useRawArgs() + .action(async function (_, ...args) { + logger().debug(args); + await $.raw`${Deno.execPath()} ${args}` + .env("DENO_EXEC_PATH", Deno.execPath()); + }), ); + for (const [name, subcmd] of Object.entries(subcmds)) { root.command(name, subcmd); } + try { await root.parse(Deno.args); } catch (err) { @@ -313,14 +341,6 @@ async function commandsFromConfig(hcx: HostCtx, gcx: GhjkCtx) { } } - if ( - !hcx.lockedFlagSet && wasReSerialized && ( - !foundHashObj || !deep_eql(newHashObj, foundHashObj) - ) - ) { - await hashFilePath.writeJsonPretty(newHashObj); - } - // `writeLockFile` can be invoked multiple times // so we keep track of the last lockfile wrote // out to disk @@ -358,6 +378,16 @@ async function commandsFromConfig(hcx: HostCtx, gcx: GhjkCtx) { lastLockObj = { ...newLockObj }; await lockFilePath.writeJsonPretty(newLockObj); } + + // we only write out hashfile when the serialization + // result was saved in the lock file + if ( + !hcx.lockedFlagSet && wasReSerialized && ( + !foundHashObj || !deep_eql(newHashObj, foundHashObj) + ) + ) { + await hashFilePath.writeJsonPretty(newHashObj); + } }, }; } diff --git a/install/mod.ts b/install/mod.ts index d63eca51..32105067 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -201,15 +201,15 @@ export async function install( } if (!args.skipExecInstall) { + const installDir = await $.path(args.ghjkExecInstallDir).ensureDir(); switch (Deno.build.os) { case "linux": case "freebsd": case "solaris": case "illumos": case "darwin": { - const installDir = await $.path(args.ghjkExecInstallDir).ensureDir(); const exePath = installDir.resolve(`ghjk`); - logger.info("installing executable", { exePath }); + logger.debug("installing executable", { exePath }); // use an isolated cache by default const denoCacheDir = args.ghjkDenoCacheDir @@ -244,6 +244,12 @@ export async function install( default: throw new Error(`${Deno.build.os} is not yet supported`); } + logger.warn( + "make sure to add the following to your $PATH to access the ghjk CLI", + ); + logger.warn( + installDir.toString(), + ); } logger.info("install success"); } diff --git a/mod.ts b/mod.ts index 891c777a..58ee37e7 100644 --- a/mod.ts +++ b/mod.ts @@ -51,6 +51,14 @@ export type AddTask = { ): string; }; +/** + * Define and register multiple tasks. + */ +export type AddTasks = { + (args: (DenoTaskDefArgs | TaskFn)[]): string[]; + (args: Record>): string[]; +}; + export type FileArgs = { /** * The env to activate by default. When entering the working @@ -111,6 +119,10 @@ type DenoFileKnobs = { * {@inheritdoc AddTask} */ task: AddTask; + /** + * {@inheritdoc AddTasks} + */ + tasks: AddTasks; /** * {@inheritDoc AddEnv} */ @@ -216,6 +228,34 @@ export const file = Object.freeze(function file( ), }); + function task( + nameOrArgsOrFn: string | DenoTaskDefArgs | TaskFn, + argsOrFn?: Omit | TaskFn, + argsMaybe?: Omit, + ) { + let args: DenoTaskDefArgs; + if (typeof nameOrArgsOrFn == "object") { + args = nameOrArgsOrFn; + } else if (typeof nameOrArgsOrFn == "function") { + args = { + ...(argsOrFn ?? {}), + fn: nameOrArgsOrFn, + }; + } else if (typeof argsOrFn == "object") { + args = { ...argsOrFn, name: nameOrArgsOrFn }; + } else if (argsOrFn) { + args = { + ...(argsMaybe ?? {}), + name: nameOrArgsOrFn, + fn: argsOrFn, + }; + } else { + args = { + name: nameOrArgsOrFn, + }; + } + return builder.addTask({ ...args, ty: "denoFile@v1" }); + } // we return a bunch of functions here // to ease configuring the main environment // including overloads @@ -226,33 +266,18 @@ export const file = Object.freeze(function file( mainEnv.install(...configs); }, - task( - nameOrArgsOrFn: string | DenoTaskDefArgs | TaskFn, - argsOrFn?: Omit | TaskFn, - argsMaybe?: Omit, + task, + + tasks( + defs: + | (DenoTaskDefArgs | TaskFn)[] + | Record>, ) { - let args: DenoTaskDefArgs; - if (typeof nameOrArgsOrFn == "object") { - args = nameOrArgsOrFn; - } else if (typeof nameOrArgsOrFn == "function") { - args = { - ...(argsOrFn ?? {}), - fn: nameOrArgsOrFn, - }; - } else if (typeof argsOrFn == "object") { - args = { ...argsOrFn, name: nameOrArgsOrFn }; - } else if (argsOrFn) { - args = { - ...(argsMaybe ?? {}), - name: nameOrArgsOrFn, - fn: argsOrFn, - }; + if (Array.isArray(defs)) { + return defs.map((def) => task(def)); } else { - args = { - name: nameOrArgsOrFn, - }; + return Object.entries(defs).map(([key, val]) => task(key, val)); } - return builder.addTask({ ...args, ty: "denoFile@v1" }); }, env( diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 6258f72b..516173b3 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -181,7 +181,7 @@ export class EnvsModule extends ModuleBase { }); const env = ecx.config.envs[envKey]; if (!env) { - throw new Error(`no env found under "${envKeyMaybe}"`); + throw new Error(`no env found under "${envKey}"`); } // deno-lint-ignore no-console console.log($.inspect(await showableEnv(gcx, env, envKey))); diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 5e775f50..5812f1f7 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -238,16 +238,25 @@ async function writeActivators( `# shellcheck disable=SC2016`, `# SC2016: disabled because single quoted expressions are used for the cleanup scripts`, ``, - `if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then`, - ` eval "$GHJK_CLEANUP_POSIX"`, - `fi`, - `export GHJK_CLEANUP_POSIX="";`, + `# this file bust be sourced from an existing sh/bash/zsh session using the \`source\` command`, + `# it cannot be executed directly`, + ``, + `ghjk_deactivate () {`, + ` if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then`, + ` eval "$GHJK_CLEANUP_POSIX"`, + ` unset GHJK_CLEANUP_POSIX`, + ` fi`, + `}`, + `ghjk_deactivate`, + ``, ``, `# the following variables are used to make the script more human readable`, `${ghjkDirVar}="${gcx.ghjkDir.toString()}"`, `${shareDirVar}="${gcx.ghjkShareDir.toString()}"`, ``, `# env vars`, + `# we keep track of old values before this script is run`, + `# so that we can restore them on cleanup`, ...Object.entries(envVars).flatMap(([key, val]) => { const safeVal = val.replaceAll("\\", "\\\\").replaceAll("'", "'\\''"); // avoid triggering unbound variable if -e is set @@ -273,7 +282,7 @@ async function writeActivators( // i.e. export KEY='OLD $VALUE OF KEY' // but $VALUE won't be expanded when the cleanup actually runs // we also unset the key if it wasn't previously set - `$([ -z "$\{${key}+x}" ] && echo 'export ${key}= '\\'"$\{${key}:-unreachable}""';" || echo 'unset ${key};');`, + `$([ -z "$\{${key}+x}" ] && echo 'export ${key}='\\'"$\{${key}:-unreachable}""';" || echo 'unset ${key};');`, `export ${key}='${safeVal}';`, ``, ]; @@ -295,7 +304,7 @@ async function writeActivators( }), ``, `# hooks that want to invoke ghjk are made to rely`, - `# on this shim to improving latency`, + `# on this shim instead to improve latency`, // the ghjk executable is itself a shell script // which execs deno, we remove the middleman here // also, the ghjk executable is optional @@ -323,25 +332,33 @@ async function writeActivators( // // fish version fish: [ - `if set --query GHJK_CLEANUP_FISH`, - ` eval $GHJK_CLEANUP_FISH`, - ` set --erase GHJK_CLEANUP_FISH`, + `# this file bust be sourced from an existing fish session using the \`source\` command`, + `# it cannot be executed directly`, + ``, + `function ghjk_deactivate`, + ` if set --query GHJK_CLEANUP_FISH`, + ` eval $GHJK_CLEANUP_FISH`, + ` set --erase GHJK_CLEANUP_FISH`, + ` end`, `end`, + `ghjk_deactivate`, ``, `# the following variables are used to make the script more human readable`, `set ${ghjkDirVar} "${gcx.ghjkDir.toString()}"`, `set ${shareDirVar} "${gcx.ghjkShareDir.toString()}"`, ``, `# env vars`, + `# we keep track of old values before this script is run`, + `# so that we can restore them on cleanup`, ...Object.entries(envVars).flatMap(([key, val]) => { const safeVal = val.replaceAll("\\", "\\\\").replaceAll("'", "\\'"); // read the comments from the posix version of this section // the fish version is notably simpler since - // - we can escape single quates within single quotes + // - we can escape single quotes within single quotes // - we don't have to deal with 'set -o nounset' return [ `set --global --append GHJK_CLEANUP_FISH 'test "$${key}" = \\'${safeVal}\\'; and '` + - `(if set -q ${key}; echo 'set --global --export ${key} \\'' "$${key}" "';"; else; echo 'set -e ${key};'; end;);`, + `(if set -q ${key}; echo 'set --global --export ${key} \\''"$${key}""';"; else; echo 'set -e ${key};'; end;);`, `set --global --export ${key} '${val}';`, ``, ]; @@ -358,7 +375,7 @@ async function writeActivators( }), ``, `# hooks that want to invoke ghjk are made to rely`, - `# on this shim to improving latency`, + `# on this shim to improve latency`, ghjk_fish(gcx, denoDir, ghjkShimName), ``, `# only run the hooks in interactive mode`, diff --git a/modules/ports/ambient.ts b/modules/ports/ambient.ts index 6b290535..80031363 100644 --- a/modules/ports/ambient.ts +++ b/modules/ports/ambient.ts @@ -12,7 +12,7 @@ export class AmbientAccessPort extends PortBase { ); } } - async latestStable() { + override async latestStable() { const execPath = await this.pathToExec(); let versionOut; try { @@ -44,11 +44,11 @@ export class AmbientAccessPort extends PortBase { return [await this.latestStable()]; } - async listBinPaths(): Promise { + override async listBinPaths(): Promise { return [await this.pathToExec()]; } - async download() { + override async download() { // no op } diff --git a/modules/ports/ghrel.ts b/modules/ports/ghrel.ts index a15a6e55..6b7f27b6 100644 --- a/modules/ports/ghrel.ts +++ b/modules/ports/ghrel.ts @@ -50,7 +50,7 @@ export abstract class GithubReleasePort extends PortBase { return []; } - async download(args: DownloadArgs): Promise { + override async download(args: DownloadArgs): Promise { const urls = await this.downloadUrls(args); if (urls.length == 0) { throw new Error( @@ -65,7 +65,7 @@ export abstract class GithubReleasePort extends PortBase { ); } - async latestStable(args: ListAllArgs) { + override async latestStable(args: ListAllArgs) { const metadata = await $.withRetries({ count: 10, delay: $.exponentialBackoff(1000), diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 382b82d4..1dcad40d 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -141,7 +141,7 @@ export class PortsModule extends ModuleBase { .command( "outdated", new cliffy_cmd.Command() - .description("Show a version table for installs") + .description("Show a version table for installs.") .option( "-u, --update-install ", "Update specific install", diff --git a/modules/ports/std.ts b/modules/ports/std.ts index a6388490..134efdfd 100644 --- a/modules/ports/std.ts +++ b/modules/ports/std.ts @@ -1,5 +1,6 @@ //! This plugin exports the list of standard ports other -//! plugins are allowed to depend on. +//! plugins are expected to depend on. + import validators, { type AllowedPortDepX, type PortDep, @@ -12,7 +13,6 @@ import { manifest as man_git_aa } from "../../ports/git.ts"; import { manifest as man_curl_aa } from "../../ports/curl.ts"; import { manifest as man_cbin_ghrel } from "../../ports/cargo-binstall.ts"; import { manifest as man_node_org } from "../../ports/node.ts"; -import { manifest as man_pnpm_ghrel } from "../../ports/pnpm.ts"; import { manifest as man_asdf_plugin_git } from "../../ports/asdf_plugin_git.ts"; import { manifest as man_cpy_bs_ghrel } from "../../ports/cpy_bs.ts"; import { manifest as man_rustup_rustlang } from "../../ports/rustup.ts"; @@ -29,14 +29,16 @@ const aaPorts: PortManifest[] = [ const denoPorts: PortManifest[] = [ man_rustup_rustlang, - man_rust_rustup, man_cbin_ghrel, - man_pnpm_ghrel, - man_asdf_plugin_git, + // man_asdf_plugin_git, + // man_rust_rustup, // man_cpy_bs_ghrel, // man_node_org, ]; +/** + * The default set of allowed port deps. + */ const defaultAllowedDeps: AllowedPortDepX[] = [ ...aaPorts, ...denoPorts, @@ -91,10 +93,6 @@ export const node_org = Object.freeze({ name: man_node_org.name, } as PortDep); -export const pnpm_ghrel = Object.freeze({ - name: man_pnpm_ghrel.name, -} as PortDep); - export const cpy_bs_ghrel = Object.freeze({ name: man_cpy_bs_ghrel.name, } as PortDep); diff --git a/modules/ports/std_runtime.ts b/modules/ports/std_runtime.ts new file mode 100644 index 00000000..58f0753a --- /dev/null +++ b/modules/ports/std_runtime.ts @@ -0,0 +1,17 @@ +//! The list of ports enabled when enableRuntimes is used on a ghjkfile configuration. + +import * as cpy_bs from "../../ports/cpy_bs.ts"; +import * as node from "../../ports/node.ts"; +import * as rust from "../../ports/rust.ts"; +import * as asdf_plugin_git from "../../ports/asdf_plugin_git.ts"; + +export default [ + // commonly used by the npmi port for installation using npm + node.default(), + // commonly used by the pipi port for installation using pip + cpy_bs.default(), + // commonly used by the cargobi port for building crates + rust.default(), + // used by the asdf port for installing asdf plugins + asdf_plugin_git.buildDep(), +]; diff --git a/modules/ports/worker.ts b/modules/ports/worker.ts index 5437aa74..de2a21c8 100644 --- a/modules/ports/worker.ts +++ b/modules/ports/worker.ts @@ -201,7 +201,7 @@ export class DenoWorkerPort extends PortBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async latestStable(env: ListAllArgs) { + override async latestStable(env: ListAllArgs) { const req: WorkerReq = { ty: "latestStable", arg: env, @@ -214,7 +214,7 @@ export class DenoWorkerPort extends PortBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async execEnv( + override async execEnv( args: ExecEnvArgs, ) { const req: WorkerReq = { @@ -228,7 +228,7 @@ export class DenoWorkerPort extends PortBase { } throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async listBinPaths( + override async listBinPaths( args: ListBinPathsArgs, ) { const req: WorkerReq = { @@ -243,7 +243,7 @@ export class DenoWorkerPort extends PortBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async listLibPaths( + override async listLibPaths( args: ListBinPathsArgs, ) { const req: WorkerReq = { @@ -258,7 +258,7 @@ export class DenoWorkerPort extends PortBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async listIncludePaths( + override async listIncludePaths( args: ListBinPathsArgs, ) { const req: WorkerReq = { @@ -273,7 +273,7 @@ export class DenoWorkerPort extends PortBase { throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const req: WorkerReq = { ty: "download", arg: args, @@ -285,7 +285,7 @@ export class DenoWorkerPort extends PortBase { } throw new Error(`unexpected response from worker ${JSON.stringify(res)}`); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const req: WorkerReq = { ty: "install", arg: args, diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index 7c87f7ff..222bda79 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -85,7 +85,9 @@ export class TasksModule extends ModuleBase { .action(function () { this.showHelp(); }) - .description("Tasks module."); + .description(`Tasks module. + +The named tasks in your ghjkfile will be listed here.`); for (const cmd of commands) { root.command(cmd.getName(), cmd); } diff --git a/ports/act.ts b/ports/act.ts index 019f2362..9d947403 100644 --- a/ports/act.ts +++ b/ports/act.ts @@ -33,7 +33,7 @@ export class Port extends GithubReleasePort { repoOwner = "nektos"; repoName = "act"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; switch (platform.arch) { @@ -73,7 +73,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/asdf.ts b/ports/asdf.ts index f32d8daf..c0bfd14e 100644 --- a/ports/asdf.ts +++ b/ports/asdf.ts @@ -72,7 +72,7 @@ export class Port extends PortBase { return out.split(/\s/).filter(Boolean).map((str) => str.trim()); } - async latestStable(args: ListAllArgs) { + override async latestStable(args: ListAllArgs) { const binPath = tryDepExecShimPath( std_ports.asdf_plugin_git, "latest-stable", @@ -93,7 +93,7 @@ export class Port extends PortBase { return out.trim(); } - async listBinPaths(args: ListBinPathsArgs) { + override async listBinPaths(args: ListBinPathsArgs) { const binPath = tryDepExecShimPath( std_ports.asdf_plugin_git, "list-bin-paths", @@ -113,7 +113,7 @@ export class Port extends PortBase { return out.split(/\s/).filter(Boolean).map((str) => str.trim()); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { // some plugins don't have a download script despite the spec const binPath = tryDepExecShimPath( std_ports.asdf_plugin_git, @@ -134,7 +134,7 @@ export class Port extends PortBase { ASDF_DOWNLOAD_PATH: args.downloadPath, }); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const conf = confValidator.parse(args.config); await $`${ depExecShimPath(std_ports.asdf_plugin_git, "install", args.depArts) diff --git a/ports/asdf_plugin_git.ts b/ports/asdf_plugin_git.ts index 60bc7669..ab7c4458 100644 --- a/ports/asdf_plugin_git.ts +++ b/ports/asdf_plugin_git.ts @@ -1,8 +1,10 @@ import { $, + AllowedPortDep, defaultLatestStable, depExecShimPath, type DownloadArgs, + getPortRef, type InstallArgs, type InstallConfigSimple, type ListAllArgs, @@ -35,6 +37,13 @@ export type AsdfPluginInstallConf = & InstallConfigSimple & zod.input; +/** + * WARNING: this is probably no the function you want if you intend + * to add `asdf_plugin_git` to your `allowedBuildDeps`. + * + * This module exports a {@link buildDep} function for the purpose of adding + * the port to the allowedBuildDeps list. + */ export default function conf(config: AsdfPluginInstallConf) { return { ...confValidator.parse(config), @@ -42,6 +51,15 @@ export default function conf(config: AsdfPluginInstallConf) { }; } +export function buildDep(): AllowedPortDep { + return { + manifest, + defaultInst: { + portRef: getPortRef(manifest), + }, + }; +} + export class Port extends PortBase { async listAll(args: ListAllArgs) { const conf = confValidator.parse(args.config); @@ -55,11 +73,11 @@ export class Port extends PortBase { .map((line) => line.split(/\s/)[0].slice(0, 10)); } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, args); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { if (await $.path(args.downloadPath).exists()) { // FIXME: remove this once download tracking is part of core return; @@ -74,7 +92,7 @@ export class Port extends PortBase { ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const tmpPath = $.path(args.tmpDirPath); // we copy the repo to a src dir const srcDir = (await tmpPath.ensureDir()).join("src"); diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts index 9c594dca..ff8744bb 100644 --- a/ports/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -33,7 +33,7 @@ export class Port extends GithubReleasePort { repoOwner = "cargo-bins"; repoName = "cargo-binstall"; - downloadUrls( + override downloadUrls( args: DownloadArgs, ) { const { installVersion, platform } = args; @@ -68,7 +68,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/cargobi.ts b/ports/cargobi.ts index 845bb078..fb3d4683 100644 --- a/ports/cargobi.ts +++ b/ports/cargobi.ts @@ -101,11 +101,11 @@ export class Port extends PortBase { return versions.map((ver) => ver.vers); } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, args); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const conf = confValidator.parse(args.config); const fileName = conf.crateName; if (await std_fs.exists(std_path.resolve(args.downloadPath, fileName))) { @@ -181,7 +181,7 @@ export class Port extends PortBase { ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); diff --git a/ports/cmake.ts b/ports/cmake.ts index 99fcea11..979bc722 100644 --- a/ports/cmake.ts +++ b/ports/cmake.ts @@ -9,20 +9,17 @@ import * as ports from "./mod.ts"; * For macOS users, you need to add python as allowed build dependencies * as cmake is downladed via pip install. * - * Example: + * For other platforms, the `asdf_plugin_git` build dependency is required. + * + * Set the `enableRuntimes` flag to setup both cases. + * * ```typescript - * const installs = { - python_latest: ports.cpy_bs({ version: "3.12.2", releaseTag: "20240224" }), -}; - * config({ - stdDeps: true, - allowedBuildDeps: [ - installs.python_latest - ], - enableRuntimes: true -}); - * ``` + * ghjk.config({ + * enableRuntimes: true, + * }); * + * ghjk.install(ports.cmake()); + * ``` */ export default function conf( config: InstallConfigSimple = {}, diff --git a/ports/cpy_bs.ts b/ports/cpy_bs.ts index 3c19603c..89d7caa2 100644 --- a/ports/cpy_bs.ts +++ b/ports/cpy_bs.ts @@ -61,7 +61,7 @@ export default function conf( export class Port extends PortBase { repoOwner = "indygreg"; repoName = "python-build-standalone"; - execEnv( + override execEnv( args: PortArgsBase, ): Record | Promise> { return { @@ -136,7 +136,7 @@ export class Port extends PortBase { .sort((va, vb) => va.localeCompare(vb, undefined, { numeric: true })); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const headers = ghHeaders(args.config); const conf = confValidator.parse(args.config); let tag = conf.releaseTag; @@ -176,7 +176,7 @@ export class Port extends PortBase { ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [_, fileDwnEntry] = await Array.fromAsync( $.path(args.downloadPath).walk(), ); diff --git a/ports/deno_ghrel.ts b/ports/deno_ghrel.ts index 9da0d050..2a70df4d 100644 --- a/ports/deno_ghrel.ts +++ b/ports/deno_ghrel.ts @@ -35,7 +35,7 @@ export class Port extends GithubReleasePort { repoOwner = "denoland"; repoName = "deno"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; const arch = platform.arch; let os; @@ -60,7 +60,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/dummy.ts b/ports/dummy.ts index 8ba1dfcd..8ea5c9cc 100644 --- a/ports/dummy.ts +++ b/ports/dummy.ts @@ -39,7 +39,7 @@ export default function conf(config: DummyInstallConf = {}) { } export class Port extends PortBase { - execEnv() { + override execEnv() { return { DUMMY_ENV: "dummy", }; @@ -49,7 +49,7 @@ export class Port extends PortBase { return ["dummy"]; } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const conf = confValidator.parse(args.config); // TODO: windows suport await $.path(args.downloadPath).join("bin", "dummy").writeText( @@ -61,7 +61,7 @@ echo ${conf.output ?? "dummy hey"}`, ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); await $.removeIfExists(installPath); await std_fs.copy(args.downloadPath, args.installPath); diff --git a/ports/earthly.ts b/ports/earthly.ts index fa41da87..b85c331b 100644 --- a/ports/earthly.ts +++ b/ports/earthly.ts @@ -31,7 +31,7 @@ export class Port extends GithubReleasePort { repoOwner = "earthly"; repoName = "earthly"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; switch (platform.arch) { @@ -57,7 +57,7 @@ export class Port extends GithubReleasePort { ]; } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); diff --git a/ports/infisical.ts b/ports/infisical.ts index 8667c83d..917df6b6 100644 --- a/ports/infisical.ts +++ b/ports/infisical.ts @@ -43,12 +43,12 @@ export class Port extends GithubReleasePort { const all = await super.listAll(args); return all.map((str) => str.replace(/^infisical-cli\/v/, "")); } - async latestStable(args: ListAllArgs) { + override async latestStable(args: ListAllArgs) { const lsv = await super.latestStable(args); return lsv.replace(/^infisical-cli\/v/, ""); } - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; switch (platform.arch) { @@ -84,7 +84,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/jq_ghrel.ts b/ports/jq_ghrel.ts index 27fa2674..e84fe915 100644 --- a/ports/jq_ghrel.ts +++ b/ports/jq_ghrel.ts @@ -38,7 +38,7 @@ export class Port extends GithubReleasePort { repoOwner = "jqlang"; repoName = "jq"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; @@ -64,7 +64,7 @@ export class Port extends GithubReleasePort { .map((out) => ({ ...out, mode: 0o700 })); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); await $.removeIfExists(installPath); diff --git a/ports/meta_cli_ghrel.ts b/ports/meta_cli_ghrel.ts index 97e6c696..3386852c 100644 --- a/ports/meta_cli_ghrel.ts +++ b/ports/meta_cli_ghrel.ts @@ -49,7 +49,7 @@ export class Port extends GithubReleasePort { repoOwner = "metatypedev"; repoName = "metatype"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const conf = confValidator.parse(args.config); const { installVersion, platform } = args; let arch; @@ -88,7 +88,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/mod.ts b/ports/mod.ts index d2833cd6..8e21ba3a 100644 --- a/ports/mod.ts +++ b/ports/mod.ts @@ -1,5 +1,6 @@ export { default as act } from "./act.ts"; export { default as asdf } from "./asdf.ts"; +export { default as asdf_plugin_git } from "./asdf_plugin_git.ts"; export { default as cargo_binstall } from "./cargo-binstall.ts"; export { default as cargobi } from "./cargobi.ts"; export { default as cmake } from "./cmake.ts"; diff --git a/ports/mold.ts b/ports/mold.ts index 3d0f7ce9..345b1869 100644 --- a/ports/mold.ts +++ b/ports/mold.ts @@ -44,7 +44,7 @@ export class Port extends GithubReleasePort { repoOwner = "rui314"; repoName = "mold"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; const os = platform.os; @@ -62,7 +62,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/node.ts b/ports/node.ts index 23405ab6..0cad73e9 100644 --- a/ports/node.ts +++ b/ports/node.ts @@ -44,19 +44,19 @@ export default function conf(config: InstallConfigSimple = {}) { } export class Port extends PortBase { - execEnv(args: ExecEnvArgs) { + override execEnv(args: ExecEnvArgs) { return { NODE_PATH: args.installPath, }; } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, args); } // we wan't to avoid adding libraries found by default at /lib // to PATHs as they're just node_module sources - listLibPaths(): string[] { + override listLibPaths(): string[] { return []; } @@ -109,14 +109,14 @@ export class Port extends PortBase { ].map(dwnUrlOut); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const urls = this.downloadUrls(args); await Promise.all( urls.map((obj) => downloadFile({ ...args, ...obj })), ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/npmi.ts b/ports/npmi.ts index b05ba128..fa61b996 100644 --- a/ports/npmi.ts +++ b/ports/npmi.ts @@ -81,7 +81,7 @@ export class Port extends PortBase { return versions; } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const conf = confValidator.parse(args.config); await $.raw`${ depExecShimPath(std_ports.node_org, "npm", args.depArts) @@ -93,7 +93,7 @@ export class Port extends PortBase { // FIXME: replace shebangs with the runtime dep node path // default shebangs just use #!/bin/env node - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const conf = confValidator.parse(args.config); await std_fs.copy( args.downloadPath, diff --git a/ports/opentofu_ghrel.ts b/ports/opentofu_ghrel.ts index 8cd9c5e6..98aebc4c 100644 --- a/ports/opentofu_ghrel.ts +++ b/ports/opentofu_ghrel.ts @@ -37,7 +37,7 @@ export class Port extends GithubReleasePort { repoOwner = "opentofu"; repoName = "opentofu"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; @@ -65,7 +65,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/pipi.ts b/ports/pipi.ts index 72992799..7669f274 100644 --- a/ports/pipi.ts +++ b/ports/pipi.ts @@ -62,12 +62,12 @@ export class Port extends PortBase { return metadata.versions; } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, args); } // this creates the venv and install the package into it - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const downloadPath = $.path(args.downloadPath); if (await downloadPath.exists()) { return; @@ -142,7 +142,7 @@ export class Port extends PortBase { // this modifies the venv so that it works with ghjk // and exposes the packages and only the package's console scripts - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const tmpPath = $.path(args.tmpDirPath); const conf = confValidator.parse(args.config); @@ -202,7 +202,7 @@ export class Port extends PortBase { .map((execPath) => Deno.symlink( // create a relative symlink - // TODO: open ticket on dsherret/tax about createSymlinkTo(relative) bug + // TODO: open ticket on dsherret/jax about createSymlinkTo(relative) bug ".." + execPath.slice(tmpPath.toString().length), tmpPath .join("bin", $.path(execPath).basename()).toString(), diff --git a/ports/pnpm.ts b/ports/pnpm.ts index 353c661e..24a83e35 100644 --- a/ports/pnpm.ts +++ b/ports/pnpm.ts @@ -31,7 +31,7 @@ export class Port extends GithubReleasePort { repoOwner = "pnpm"; repoName = "pnpm"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; let os; @@ -74,7 +74,7 @@ export class Port extends GithubReleasePort { ]; } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const installPath = $.path(args.installPath); diff --git a/ports/poetry.ts b/ports/poetry.ts index 9b68ae70..b28de019 100644 --- a/ports/poetry.ts +++ b/ports/poetry.ts @@ -53,14 +53,14 @@ export class Port extends PipiPort { return super.listAll({ ...args, config: toPipiConfig(args.config) }); } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, { ...args, config: toPipiConfig(args.config), }); } - download(args: DownloadArgs) { + override download(args: DownloadArgs) { return super.download({ ...args, config: toPipiConfig(args.config) }); } diff --git a/ports/protoc.ts b/ports/protoc.ts index 67b28ac4..e6ea1729 100644 --- a/ports/protoc.ts +++ b/ports/protoc.ts @@ -33,7 +33,7 @@ export class Port extends GithubReleasePort { repoOwner = "protocolbuffers"; repoName = "protobuf"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let os; switch (platform.os) { @@ -66,7 +66,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = $.path(args.downloadPath).join(fileName); diff --git a/ports/ruff.ts b/ports/ruff.ts index 87aa0c7f..fa1d75d2 100644 --- a/ports/ruff.ts +++ b/ports/ruff.ts @@ -41,7 +41,7 @@ export class Port extends GithubReleasePort { repoOwner = "astral-sh"; repoName = "ruff"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; switch (platform.arch) { @@ -89,7 +89,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/rust.ts b/ports/rust.ts index 2819f8bf..5d34b6dd 100644 --- a/ports/rust.ts +++ b/ports/rust.ts @@ -128,13 +128,13 @@ export class Port extends PortBase { return versions; } - async latestStable(args: ListAllArgs) { + override async latestStable(args: ListAllArgs) { const versions = await this.listAll(args); // stable releases are just version numbers, no return versions.findLast((ver) => !ver.match(/[a-zA-Z]/))!; } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const conf = confValidator.parse(args.config); const tmpPath = $.path(args.tmpDirPath); @@ -159,7 +159,7 @@ export class Port extends PortBase { ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); diff --git a/ports/rustup.ts b/ports/rustup.ts index 3ab972e7..0044790b 100644 --- a/ports/rustup.ts +++ b/ports/rustup.ts @@ -60,7 +60,7 @@ export class Port extends PortBase { .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); } - latestStable(args: ListAllArgs): Promise { + override latestStable(args: ListAllArgs): Promise { return defaultLatestStable(this, args); } @@ -99,14 +99,14 @@ export class Port extends PortBase { ].map(dwnUrlOut); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const urls = this.downloadUrls(args); await Promise.all( urls.map((obj) => downloadFile({ ...args, ...obj, mode: 0o700 })), ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); diff --git a/ports/temporal_cli.ts b/ports/temporal_cli.ts index 132f2d6a..d8a7c611 100644 --- a/ports/temporal_cli.ts +++ b/ports/temporal_cli.ts @@ -33,7 +33,7 @@ export class Port extends GithubReleasePort { repoOwner = "temporalio"; repoName = "cli"; - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let arch; switch (platform.arch) { @@ -55,7 +55,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/terraform.ts b/ports/terraform.ts index f811d5a2..4c88bf75 100644 --- a/ports/terraform.ts +++ b/ports/terraform.ts @@ -41,7 +41,7 @@ export class Port extends PortBase { return versions.reverse(); } - async latestStable() { + override async latestStable() { const all = await this.listAll(); // stable versions don't have any additional info in theform of 1.2.3-alpha return all.findLast((str) => !str.match(/-/))!; @@ -68,14 +68,14 @@ export class Port extends PortBase { ].map(dwnUrlOut); } - async download(args: DownloadArgs) { + override async download(args: DownloadArgs) { const urls = this.downloadUrls(args); await Promise.all( urls.map((obj) => downloadFile({ ...args, ...obj })), ); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/ports/wasmedge.ts b/ports/wasmedge.ts index be3f915b..b35175ec 100644 --- a/ports/wasmedge.ts +++ b/ports/wasmedge.ts @@ -63,7 +63,7 @@ export class Port extends GithubReleasePort { repoOwner = "WasmEdge"; repoName = "WasmEdge"; - execEnv(args: ExecEnvArgs) { + override execEnv(args: ExecEnvArgs) { return { WASMEDGE_DIR: args.installPath, // WASMEDGE_LIB_DIR: std_path.resolve(args.installPath, "lib64"), @@ -71,11 +71,11 @@ export class Port extends GithubReleasePort { }; } - listLibPaths(): string[] { + override listLibPaths(): string[] { return ["lib*/*"]; } - downloadUrls(args: DownloadArgs) { + override downloadUrls(args: DownloadArgs) { const { installVersion, platform } = args; let fileName; if (platform.os == "darwin") { @@ -120,7 +120,7 @@ export class Port extends GithubReleasePort { ].map(dwnUrlOut); } - async install(args: InstallArgs) { + override async install(args: InstallArgs) { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = std_path.resolve(args.downloadPath, fileName); diff --git a/std/sedLock.ts b/std/sedLock.ts index 9219b807..ccbb2f12 100644 --- a/std/sedLock.ts +++ b/std/sedLock.ts @@ -40,6 +40,8 @@ export async function sedLock( let dirty = false; + const workSet = [] as [Path, string][]; + await $.co( Object .entries(lines) @@ -88,8 +90,7 @@ export async function sedLock( const newText = rewrite.join("\n"); if (text != newText) { - await path.writeText(newText); - $.logStep(`Updated ${workingDir.relative(path)}`); + workSet.push([path, newText]); dirty = true; } else { // $.logLight(`No change ${workingDir.relative(path)}`); @@ -107,5 +108,13 @@ export async function sedLock( }), ); + // we prefer all settled for the destructive operation + await Promise.allSettled( + workSet.map(async ([path, newText]) => { + await path.writeText(newText); + $.logStep(`Updated ${workingDir.relative(path)}`); + }), + ); + return dirty; } diff --git a/tests/envs.ts b/tests/envs.ts index 7318226a..f18a6937 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -48,9 +48,14 @@ const envVarTestEnvs: EnvDefArgs[] = [ const envVarTestsPosix = ` set -ex # by default, we should be in main -[ "$SONG" = "ditto" ] || exit 101 +[ "$SONG" = "ditto" ] || exit 1010 [ "$GHJK_ENV" = "main" ] || exit 1011 +# vars should be gone after deactivation +ghjk_deactivate +[ "$SONG" = "ditto" ] && exit 1022 +[ "$GHJK_ENV" = "main" ] && exit 1022 + ghjk envs cook sss . .ghjk/envs/sss/activate.sh # by default, envs should be based on main @@ -60,6 +65,7 @@ ghjk envs cook sss [ "$GHJK_ENV" = "sss" ] || exit 1012 # go back to main and "sss" variables shouldn't be around +# through deactivation . .ghjk/envs/main/activate.sh [ "$SONG" = "ditto" ] || exit 104 [ "$SING" = "Seoul Sonyo Sound" ] && exit 105 @@ -78,6 +84,11 @@ set fish_trace 1 test "$SONG" = "ditto"; or exit 101; test "$GHJK_ENV" = "main"; or exit 1010; +# vars should be gone after deactivation +ghjk_deactivate +test "$SONG" = "ditto"; and exit 101; +test "$GHJK_ENV" = "main"; and exit 1010; + ghjk envs cook sss . .ghjk/envs/sss/activate.fish # by default, envs should be based on main diff --git a/tests/ports.ts b/tests/ports.ts index 9559bdf4..d659aab8 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -11,6 +11,9 @@ type CustomE2eTestCase = Omit & { installConf: InstallConfigFat | InstallConfigFat[]; secureConf?: FileArgs; }; + +// FIXME: where did the asdf test go? + // order tests by download size to make failed runs less expensive const cases: CustomE2eTestCase[] = [ // 0 megs @@ -187,6 +190,9 @@ const cases: CustomE2eTestCase[] = [ profile: "minimal", }, }), + secureConf: { + enableRuntimes: true, + }, ePoint: `sd --version`, }, // rust + cargo_binstall + 22 megs @@ -199,6 +205,9 @@ const cases: CustomE2eTestCase[] = [ profile: "minimal", }, }), + secureConf: { + enableRuntimes: true, + }, ePoint: `sd --version`, }, ]; diff --git a/tests/reloadHooks.ts b/tests/reloadHooks.ts index 8f03679a..d190dc99 100644 --- a/tests/reloadHooks.ts +++ b/tests/reloadHooks.ts @@ -38,6 +38,10 @@ echo "test" > $GHJK_NEXTFILE const posixNonInteractiveScript = ` set -eux +# FIXME: make decision about this +# test that ghjk_reload doesn't run by default on non-interactive shells +# [ "\${DUMMY_ENV:-}" = "dummy" ] && exit 1011 + # test that ghjk_reload is avail because BASH_ENV exposed by the suite ghjk_reload [ "\${DUMMY_ENV:-}" = "dummy" ] || exit 101 @@ -103,6 +107,9 @@ test $DUMMY_ENV = "dummy"; or exit 105 const fishNoninteractiveScript = ` set fish_trace 1 +# test that ghjk_reload doesn't run by default on non-interactive shells +# test $DUMMY_ENV = "dummy"; and exit 1011 + # test that ghjk_reload is avail because config.fish exposed by the suite ghjk_reload diff --git a/utils/mod.ts b/utils/mod.ts index 8dfb4c0a..04688421 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -290,7 +290,7 @@ export function inWorker() { self instanceof WorkerGlobalScope; } -export async function findEntryRecursive(path: string, name: string) { +export async function findEntryRecursive(path: string | Path, name: string) { let current = $.path(path); while (true) { const location = `${current}/${name}`;