diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 934a1934..579f4faf 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -18,7 +18,8 @@ "npm:@noble/hashes@1.4.0": "npm:@noble/hashes@1.4.0", "npm:multiformats@13.1.0": "npm:multiformats@13.1.0", "npm:zod-validation-error": "npm:zod-validation-error@3.1.0_zod@3.22.4", - "npm:zod-validation-error@3.2.0": "npm:zod-validation-error@3.2.0_zod@3.22.4" + "npm:zod-validation-error@3.2.0": "npm:zod-validation-error@3.2.0_zod@3.22.4", + "npm:zod-validation-error@3.3.0": "npm:zod-validation-error@3.3.0_zod@3.22.4" }, "jsr": { "@david/dax@0.41.0": { @@ -106,6 +107,12 @@ "zod": "zod@3.22.4" } }, + "zod-validation-error@3.3.0_zod@3.22.4": { + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "dependencies": { + "zod": "zod@3.22.4" + } + }, "zod@3.22.4": { "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dependencies": {} @@ -567,8 +574,46 @@ "https://deno.land/x/zod@v3.23.5/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.23.5/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", "https://deno.land/x/zod@v3.23.5/types.ts": "78d3f06eb313ea754fad0ee389d3c0fa55bc01cf708e6ce0ea7fddd41f31eca2", + "https://deno.land/x/zod@v3.23.8/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", + "https://deno.land/x/zod@v3.23.8/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.23.8/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.23.8/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.23.8/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.23.8/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", + "https://deno.land/x/zod@v3.23.8/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.23.8/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.23.8/helpers/util.ts": "30c273131661ca5dc973f2cfb196fa23caf3a43e224cdde7a683b72e101a31fc", + "https://deno.land/x/zod@v3.23.8/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "https://deno.land/x/zod@v3.23.8/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", + "https://deno.land/x/zod@v3.23.8/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", + "https://deno.land/x/zod@v3.23.8/types.ts": "1b172c90782b1eaa837100ebb6abd726d79d6c1ec336350c8e851e0fd706bf5c", "https://esm.sh/jszip@3.7.1": "f3872a819b015715edb05f81d973b5cd05d3d213d8eb28293ca5471fe7a71773", "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/deps/cli.ts": "4eacc555cf80686b487e7502db63a4cfbc2060a7b847d15b14cf1cc008a3b65c", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/deps/common.ts": "46d30782086ccc79e4a2633fe859723e7686ebc5adb4101e76c4bf2d6d2e94ff", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/host/deno.ts": "330c62197c7af0a01d3ec96705367b789d538f3c820b730c63bb2820fabda7d7", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/host/mod.ts": "2bc9f273262e1c4fb434b1a0389f24464f8b986816ce9480e8e2d63d910e8253", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/host/types.ts": "22c06b190172d08092717ad788ed04b050af58af0cf3f8c78b1511984101e9e4", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/main.ts": "8d6985e59db0b5baf67c9dc330bf8b25ad556341b9ef6088038e8ebb37ed75e5", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/mod.ts": "6aa0b765ce5684842ea531e026926836ffde7d2513e62457bffe9cb4ec7eb0df", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/ambient.ts": "25623410c535e2bfaf51fca1e582e7325a00a7690d5b5e763a12be9407f619cf", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/db.ts": "3f4541d6874c434f2f869774a17fd41c3d86914ed190d412e2f63f564b58ce95", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/mod.ts": "e38ad2d3599b6a5522da436b52e5945bb85cabba2aca27f633eae43e465b5794", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/sync.ts": "46447c2c51c085193f567ddcd2451b14bb33ee2d761edeb91a6153e2ba642f42", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/types.ts": "b3967d9d75def187b3b55f2b0b1357c9cb69a70e475a9280fc66717193b8b43c", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/ports/worker.ts": "25c01e3afddd97d48af89d9c97a9a5188e7db09fceb26a69eac4dabacd8ac4fc", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/std.ts": "ddb2c134c080bb0e762a78f2f2edd69536991cc4257bd29a6fc95944b2f105a9", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/tasks/deno.ts": "f988a4d1062364b99272087fa0c7d54e699944ead3790c5b83140577bda089de", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/tasks/exec.ts": "7a07f2cce79fe16e86f0b74df6d57f0160bac75a8c6d58a03f2883a5ecccddf0", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/tasks/mod.ts": "0edbe1ce953a44b6b0fd45aa9c9dd52c11b12053eef21307eac3b24b6db4745e", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/tasks/types.ts": "536495a17c7a917bdd1c316ecc98ce2947b4959a713f92a175d372196dcaafc0", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/modules/types.ts": "b44609942d7ad66c925c24485057c5b4b2ffcad20c0a94e14dc6af34cf9e8241", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/utils/logger.ts": "86fdf651123d00ea1081bf8001ed9039cd41a79940e6ebadb8484952ab390e73", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/utils/mod.ts": "1ee68d9390259c065144c10663f6e360d29aec36db2af38d02647e304eeeaedc", + "https://raw.githubusercontent.com/metatypedev/ghjk/2725af8/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/deps/cli.ts": "4eacc555cf80686b487e7502db63a4cfbc2060a7b847d15b14cf1cc008a3b65c", "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/deps/common.ts": "46d30782086ccc79e4a2633fe859723e7686ebc5adb4101e76c4bf2d6d2e94ff", "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/host/deno.ts": "330c62197c7af0a01d3ec96705367b789d538f3c820b730c63bb2820fabda7d7", diff --git a/.ghjk/lock.json b/.ghjk/lock.json index e4993d66..e94f7ebf 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -11,7 +11,7 @@ "portRef": "act_ghrel@0.1.0" }, "bciqao2s3r3r33ruox4qknfrxqrmemuccxn64dze2ylojrzp2bwvt4ji": { - "version": "3.7.0", + "version": "3.7.1", "buildDepConfigs": { "cpy_bs_ghrel": { "version": "3.12.3", @@ -60,7 +60,7 @@ "portRef": "zstd_aa@0.1.0" }, "bciqkpfuyqchouu5o3whigod3f5coscq2jdlwde6fztypy3x6fg6xb5q": { - "version": "v26.1", + "version": "v27.0", "buildDepConfigs": {}, "portRef": "protoc_ghrel@0.1.0" } diff --git a/README.md b/README.md index c62b66d1..80bf19a1 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ ghjk.env("main", { ghjk.env("dev", { // by default, all envs are additively based on `main` // pass false here to make env indiependent. - base: false, + inherit: false, // envs can specify standard env vars vars: { CARGO_TARGET_DIR: "my_target" }, installs: [ @@ -106,7 +106,7 @@ ghjk.env({ name: "docker", desc: "for Dockerfile usage", // NOTE: env references are order-independent - base: "ci", + inherit: "ci", installs: [ ports.cargobi({ crateName: "cargo-chef" }), ports.zstd(), @@ -123,7 +123,7 @@ ghjk.env("ci") // each task describes it's own env as well ghjk.task({ name: "run", - base: "dev", + inherit: "dev", fn: () => console.log("online"), }); ``` diff --git a/deno.lock b/deno.lock index 08ec8ec5..dbb09652 100644 --- a/deno.lock +++ b/deno.lock @@ -20,7 +20,8 @@ "npm:@types/node": "npm:@types/node@18.16.19", "npm:multiformats@13.1.0": "npm:multiformats@13.1.0", "npm:zod-validation-error": "npm:zod-validation-error@3.1.0_zod@3.23.3", - "npm:zod-validation-error@3.2.0": "npm:zod-validation-error@3.2.0_zod@3.23.3" + "npm:zod-validation-error@3.2.0": "npm:zod-validation-error@3.2.0_zod@3.23.3", + "npm:zod-validation-error@3.3.0": "npm:zod-validation-error@3.3.0_zod@3.23.3" }, "jsr": { "@david/dax@0.40.1": { @@ -123,6 +124,12 @@ "zod": "zod@3.23.3" } }, + "zod-validation-error@3.3.0_zod@3.23.3": { + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "dependencies": { + "zod": "zod@3.23.3" + } + }, "zod@3.23.3": { "integrity": "sha512-tPvq1B/2Yu/dh2uAIH2/BhUlUeLIUvAjr6dpL/75I0pCYefHgjhXk1o1Kob3kTU8C7yU1j396jFHlsVWFi9ogg==", "dependencies": {} @@ -518,6 +525,19 @@ "https://deno.land/x/zod@v3.23.5/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.23.5/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", "https://deno.land/x/zod@v3.23.5/types.ts": "78d3f06eb313ea754fad0ee389d3c0fa55bc01cf708e6ce0ea7fddd41f31eca2", + "https://deno.land/x/zod@v3.23.8/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", + "https://deno.land/x/zod@v3.23.8/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.23.8/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.23.8/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.23.8/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.23.8/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", + "https://deno.land/x/zod@v3.23.8/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.23.8/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.23.8/helpers/util.ts": "30c273131661ca5dc973f2cfb196fa23caf3a43e224cdde7a683b72e101a31fc", + "https://deno.land/x/zod@v3.23.8/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "https://deno.land/x/zod@v3.23.8/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", + "https://deno.land/x/zod@v3.23.8/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", + "https://deno.land/x/zod@v3.23.8/types.ts": "1b172c90782b1eaa837100ebb6abd726d79d6c1ec336350c8e851e0fd706bf5c", "https://esm.sh/jszip@3.7.1": "f3872a819b015715edb05f81d973b5cd05d3d213d8eb28293ca5471fe7a71773", "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9" } diff --git a/deps/cli.ts b/deps/cli.ts index 01057a2b..da6408eb 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -3,3 +3,4 @@ export * from "./common.ts"; export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; +export * as zod_val_err from "npm:zod-validation-error@3.3.0"; diff --git a/deps/common.ts b/deps/common.ts index 56e6f7ed..e0711079 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -2,8 +2,7 @@ //! FIXME: move files in this module to files called deps.ts //! and located close to their users -export { z as zod } from "https://deno.land/x/zod@v3.23.5/mod.ts"; -export * as zod_val_err from "npm:zod-validation-error@3.2.0"; +export { z as zod } from "https://deno.land/x/zod@v3.23.8/mod.ts"; export * as semver from "https://deno.land/std@0.213.0/semver/mod.ts"; export * as std_log from "https://deno.land/std@0.213.0/log/mod.ts"; export * as std_log_levels from "https://deno.land/std@0.213.0/log/levels.ts"; diff --git a/files/mod.ts b/files/mod.ts index 0d657caa..24b04094 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -53,7 +53,7 @@ export type EnvDefArgs = { * top of a new env. If given a string, will use the identified env as a base * for the task env. */ - base?: string | boolean; + inherit?: string | boolean; desc?: string; vars?: Record; /** @@ -89,7 +89,7 @@ export type TaskDefArgs = { envVars?: Record; allowedPortDeps?: AllowedPortDep[]; installs?: InstallConfigFat[]; - base?: string | boolean; + inherit?: string | boolean; }; export type DenoTaskDefArgs = TaskDefArgs & { @@ -175,8 +175,8 @@ export class Ghjkfile { // be declared before the `env` but still depend on it. // Order-indepency like this makes the `ghjk.ts` way less // brittle. - if (typeof args.base == "string") { - this.addEnv({ name: args.base }); + if (typeof args.inherit == "string") { + this.addEnv({ name: args.inherit }); } let key = args.name; if (!key) { @@ -218,8 +218,8 @@ export class Ghjkfile { env = new EnvBuilder(this, (fin) => finalizer = fin, args.name); this.#seenEnvs[args.name] = [env, finalizer!]; } - if (args.base !== undefined) { - env.base(args.base); + if (args.inherit !== undefined) { + env.inherit(args.inherit); } if (args.installs) { env.install(...args.installs); @@ -331,9 +331,9 @@ export class Ghjkfile { const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) ) { const final = finalizer(); - const envBaseResolved = typeof final.base === "string" - ? final.base - : final.base && defaultBaseEnv != final.name + const envBaseResolved = typeof final.inherit === "string" + ? final.inherit + : final.inherit && defaultBaseEnv != final.name ? defaultBaseEnv : null; all[final.name] = { ...final, envBaseResolved }; @@ -515,11 +515,11 @@ export class Ghjkfile { while (workingSet.length > 0) { const key = workingSet.pop()!; const args = this.#tasks.get(key)!; - const { workingDir, desc, dependsOn, base } = args; + const { workingDir, desc, dependsOn, inherit } = args; - const envBaseResolved = typeof base === "string" - ? base - : base + const envBaseResolved = typeof inherit === "string" + ? inherit + : inherit ? defaultBaseEnv : null; @@ -722,7 +722,7 @@ export class Ghjkfile { type EnvFinalizer = () => { name: string; installSetId: string; - base: string | boolean; + inherit: string | boolean; vars: Record; desc?: string; onEnterHookTasks: string[]; @@ -735,7 +735,7 @@ type EnvFinalizer = () => { export class EnvBuilder { #installSetId: string; #file: Ghjkfile; - #base: string | boolean = true; + #inherit: string | boolean = true; #vars: Record = {}; #desc?: string; #onEnterHookTasks: string[] = []; @@ -751,7 +751,7 @@ export class EnvBuilder { setFinalizer(() => ({ name: this.name, installSetId: this.#installSetId, - base: this.#base, + inherit: this.#inherit, vars: this.#vars, desc: this.#desc, onExitHookTasks: this.#onExitHookTasks, @@ -759,8 +759,8 @@ export class EnvBuilder { })); } - base(base: string | boolean) { - this.#base = base; + inherit(inherit: string | boolean) { + this.#inherit = inherit; return this; } diff --git a/ghjk.ts b/ghjk.ts index 33925ad7..e5589430 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,5 +1,5 @@ export { ghjk } from "./mod.ts"; -import { env, install, stdSecureConfig, task } from "./mod.ts"; +import { $, env, install, stdSecureConfig, task } from "./mod.ts"; import * as ports from "./ports/mod.ts"; // these are just for quick testing diff --git a/host/mod.ts b/host/mod.ts index e5b2cd2b..9caace9c 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -412,15 +412,17 @@ function validateRawConfig( raw: unknown, configPath: Path, ): SerializedConfig { - const res = validators.serializedConfig.safeParse(raw); - if (!res.success) { + try { + return validators.serializedConfig.parse(raw); + } catch (err) { + const validationError = zod_val_err.fromError(err); throw new Error( - `error parsing seralized config from ${configPath}: ${ - zod_val_err.fromZodError(res.error).toString() - }`, + `error parsing seralized config from ${configPath}: ${validationError.toString()}`, + { + cause: validationError, + }, ); } - return res.data; } const lockObjValidator = zod.object({ @@ -440,19 +442,16 @@ async function readLockFile(lockFilePath: Path) { if (!rawStr) return; try { const rawJson = JSON.parse(rawStr); - const res = lockObjValidator.safeParse(rawJson); - if (!res.success) { - throw zod_val_err.fromZodError(res.error); - } - return res.data; + return lockObjValidator.parse(rawJson); } catch (err) { + const validationError = zod_val_err.fromError(err); logger().error( - `error parsing lockfile from ${lockFilePath}: ${err.toString()}`, + `error parsing lockfile from ${lockFilePath}: ${validationError.toString()}`, ); if (Deno.stderr.isTerminal() && await $.confirm("Discard lockfile?")) { return; } else { - throw err; + throw validationError; } } } @@ -477,14 +476,12 @@ async function readHashFile(hashFilePath: Path) { if (!rawStr) return; try { const rawJson = JSON.parse(rawStr); - const res = hashObjValidator.safeParse(rawJson); - if (!res.success) { - throw zod_val_err.fromZodError(res.error); - } - return res.data; + return hashObjValidator.parse(rawJson); } catch (err) { logger().error( - `error parsing hashfile from ${hashObjValidator}: ${err.toString()}`, + `error parsing hashfile from ${hashObjValidator}: ${ + zod_val_err.fromError(err).toString() + }`, ); logger().warn("discarding invalid hashfile"); return; diff --git a/install/ghjk.sh b/install/ghjk.sh index f711d3ac..50ac141b 100644 --- a/install/ghjk.sh +++ b/install/ghjk.sh @@ -1,4 +1,5 @@ #!/bin/sh + export GHJK_SHARE_DIR="${GHJK_SHARE_DIR:-__GHJK_SHARE_DIR__}" export DENO_DIR="${GHJK_DENO_DIR:-__DENO_CACHE_DIR}" export DENO_NO_UPDATE_CHECK=1 @@ -13,9 +14,12 @@ GHJK_MAIN_URL="${GHJK_MAIN_URL:-__MAIN_TS_URL__}" # if ghjkfile var is set, set the GHJK_DIR overriding # any set by the user if [ -n "${GHJKFILE+x}" ]; then + GHJK_DIR="$(dirname "$GHJKFILE")/.ghjk" + # if both GHJKFILE and GHJK_DIR are unset elif [ -z "${GHJK_DIR+x}" ]; then + # look for ghjk dirs in parents cur_dir=$PWD while true; do @@ -30,14 +34,19 @@ elif [ -z "${GHJK_DIR+x}" ]; then fi cur_dir="$next_cur_dir" done + fi if [ -n "${GHJK_DIR+x}" ]; then + export GHJK_DIR mkdir -p "$GHJK_DIR" lock_flag="--lock $GHJK_DIR/deno.lock" + else + lock_flag="--no-lock" + fi # we don't want to quote $lock_flag as it's not exactly a single diff --git a/install/hook.fish b/install/hook.fish index c5672389..c7757744 100644 --- a/install/hook.fish +++ b/install/hook.fish @@ -1,4 +1,4 @@ -function get_ctime_ts +function __ghjk_get_mtime_ts switch (uname -s | tr '[:upper:]' '[:lower:]') case "linux" stat -c "%Y" $argv @@ -9,24 +9,28 @@ function get_ctime_ts end end -function ghjk_reload --on-variable PWD --on-event ghjk_env_dir_change # --on-variable GHJK_ENV +function ghjk_reload --on-variable PWD --on-event ghjk_env_dir_change + # precedence is gven to argv over GHJK_ENV + set --local next_env $argv[1] + test "$argv" = "VARIABLE SET PWD"; and set next_env "" + test -z $next_env; and set next_env "$GHJK_ENV" + test -z $next_env; and set next_env "default" + if set --query GHJK_CLEANUP_FISH # restore previous env eval $GHJK_CLEANUP_FISH set --erase GHJK_CLEANUP_FISH end - set --local cur_dir set --local local_ghjk_dir $GHJK_DIR # if $GHJKFILE is set, set the GHJK_DIR overriding # any set by the user if set --query GHJKFILE - set cur_dir (dirname $GHJKFILE) - set local_ghjk_dir $cur_dir/.ghjk + set local_ghjk_dir (dirname $GHJKFILE)/.ghjk # if both GHJKFILE and GHJK_DIR are unset else if test -z "$local_ghjk_dir" # look for ghjk dirs in pwd and parents - set cur_dir $PWD + set --local cur_dir $PWD while true if test -d $cur_dir/.ghjk; or test -d $cur_dir/ghjk.ts set local_ghjk_dir $cur_dir/.ghjk @@ -41,50 +45,63 @@ function ghjk_reload --on-variable PWD --on-event ghjk_env_dir_change # --on-var end set cur_dir $next_cur_dir end - else - set cur_dir (dirname $local_ghjk_dir) end if test -n "$local_ghjk_dir" - # locate the active env - set --local active_env "$GHJK_ENV" - test -z $active_env; and set --local active_env default - set --local active_env_dir $local_ghjk_dir/envs/$active_env - if test -d $active_env_dir + set --global --export GHJK_LAST_GHJK_DIR $local_ghjk_dir + + # locate the next env + set --local next_env_dir $local_ghjk_dir/envs/$next_env + + if test -d $next_env_dir # load the shim - . $active_env_dir/activate.fish + . $next_env_dir/activate.fish # export variables to assist in change detection - set --global --export GHJK_LAST_ENV_DIR $active_env_dir - set --global --export GHJK_LAST_ENV_DIR_CTIME (get_ctime_ts $active_env_dir/activate.fish) + set --global --export GHJK_LAST_ENV_DIR $next_env_dir + set --global --export GHJK_LAST_ENV_DIR_MTIME (__ghjk_get_mtime_ts $next_env_dir/activate.fish) # FIXME: older versions of fish don't recognize -ot # those in debian for example # FIXME: this assumes ghjkfile is of kind ghjk.ts - if test $active_env_dir/activate.fish -ot $cur_dir/ghjk.ts + if test $next_env_dir/activate.fish -ot $local_ghjk_dir/../ghjk.ts set_color FF4500 - if test $active_env = "default" + if test $next_env = "default" echo "[ghjk] Possible drift from default environment, please sync..." else - echo "[ghjk] Possible drift from active environment ($active_env), please sync..." + echo "[ghjk] Possible drift from active environment ($next_env), please sync..." end set_color normal end else set_color FF4500 - if test $active_env = "default" + if test $next_env = "default" echo "[ghjk] Default environment not found, please sync..." else - echo "[ghjk] Active environment ($active_env) not found, please sync..." + echo "[ghjk] Active environment ($next_env) not found, please sync..." end set_color normal end end end -# trigger reload when the env dir loader ctime changes -function ghjk_env_dir_watcher --on-event fish_postexec - if set --query GHJK_LAST_ENV_DIR; and test (get_ctime_ts $GHJK_LAST_ENV_DIR/activate.fish) -gt "$GHJK_LAST_ENV_DIR_CTIME" - emit ghjk_env_dir_change +set --local tmp_dir "$TMPDIR" +test -z $tmp_dir; and set tmp_dir "/tmp" +set --export --global GHJK_NEXTFILE "$tmp_dir/ghjk.nextfile.$fish_pid" + +# trigger reload when the env dir loader mtime changes +function __ghjk_preexec --on-event fish_preexec + + # trigger reload when either + # exists + if set --query GHJK_NEXTFILE; and test -f "$GHJK_NEXTFILE"; + + ghjk_reload "$(cat $GHJK_NEXTFILE)" + rm "$GHJK_NEXTFILE" + + # activate script has reloaded + else if set --query GHJK_LAST_ENV_DIR; + and test (__ghjk_get_mtime_ts $GHJK_LAST_ENV_DIR/activate.fish) -gt $GHJK_LAST_ENV_DIR_MTIME; + ghjk_reload end end diff --git a/install/hook.sh b/install/hook.sh index 544d43d6..cd050562 100644 --- a/install/hook.sh +++ b/install/hook.sh @@ -1,7 +1,7 @@ -# shellcheck disable=SC2148 +# shellcheck shell=sh # keep this posix compatible as it supports bash and zsh -get_ctime_ts () { +__ghjk_get_mtime_ts () { case "$(uname -s | tr '[:upper:]' '[:lower:]')" in "linux") stat -c "%Y" "$1" @@ -16,6 +16,11 @@ get_ctime_ts () { } ghjk_reload() { + + # precedence is given to argv over GHJK_ENV + # which's usually the current active env + next_env="${1:-${GHJK_ENV:-default}}"; + if [ -n "${GHJK_CLEANUP_POSIX+x}" ]; then # restore previous env eval "$GHJK_CLEANUP_POSIX" @@ -26,11 +31,11 @@ ghjk_reload() { # if $GHJKFILE is set, set the GHJK_DIR overriding # any set by the user if [ -n "${GHJKFILE+x}" ]; then - cur_dir=$(dirname "$GHJKFILE") - local_ghjk_dir="$cur_dir/.ghjk" + local_ghjk_dir="$(dirname "$GHJKFILE")/.ghjk" # if both GHJKFILE and GHJK_DIR are unset elif [ -z "$local_ghjk_dir" ]; then # look for ghjk dirs in pwd parents + # use do while format to allow detection of .ghjk in root dirs cur_dir=$PWD while true; do if [ -d "$cur_dir/.ghjk" ] || [ -e "$cur_dir/ghjk.ts" ]; then @@ -38,48 +43,45 @@ ghjk_reload() { break fi # recursively look in parent directory - # use do while format to allow detection of .ghjk in root dirs next_cur_dir="$(dirname "$cur_dir")" if [ "$next_cur_dir" = / ] && [ "$cur_dir" = "/" ]; then break fi cur_dir="$next_cur_dir" done - else - cur_dir=$(dirname "$local_ghjk_dir") fi if [ -n "$local_ghjk_dir" ]; then - # export GHJK_DIR - # locate the active env - active_env="${GHJK_ENV:-default}"; - active_env_dir="$local_ghjk_dir/envs/$active_env" - if [ -d "$active_env_dir" ]; then + GHJK_LAST_GHJK_DIR="$local_ghjk_dir" + export GHJK_LAST_GHJK_DIR + + # locate the next env + next_env_dir="$local_ghjk_dir/envs/$next_env" + + if [ -d "$next_env_dir" ]; then # load the shim # shellcheck source=/dev/null - . "$active_env_dir/activate.sh" + . "$next_env_dir/activate.sh" # export variables to assist in change detection - GHJK_LAST_ENV_DIR="$active_env_dir" - GHJK_LAST_ENV_DIR_CTIME="$(get_ctime_ts "$active_env_dir/activate.sh")" + GHJK_LAST_ENV_DIR="$next_env_dir" + GHJK_LAST_ENV_DIR_MTIME="$(__ghjk_get_mtime_ts "$next_env_dir/activate.sh")" export GHJK_LAST_ENV_DIR - export GHJK_LAST_ENV_DIR_CTIME + export GHJK_LAST_ENV_DIR_MTIME - # FIXME: -ot not valid in POSIX # FIXME: this assumes ghjkfile is of kind ghjk.ts - # shellcheck disable=SC3000-SC4000 - if [ "$active_env_dir/activate.sh" -ot "$cur_dir/ghjk.ts" ]; then - if [ "$active_env" = "default" ]; then + if [ "$(__ghjk_get_mtime_ts "$local_ghjk_dir/../ghjk.ts")" -gt "$(__ghjk_get_mtime_ts "$next_env_dir/activate.sh")" ]; then + if [ "$next_env" = "default" ]; then printf "\033[0;33m[ghjk] Possible drift from default environment, please sync...\033[0m\n" else - printf "\033[0;33m[ghjk] Possible drift from active environment (%s), please sync...\033[0m\n" "$active_env" + printf "\033[0;33m[ghjk] Possible drift from active environment (%s), please sync...\033[0m\n" "$next_env" fi fi else - if [ "$active_env" = "default" ]; then + if [ "$next_env" = "default" ]; then printf "\033[0;31m[ghjk] Default environment not set up, please sync...\033[0m\n" else - printf "\033[0;31m[ghjk] Active environment (%s) not set up, please sync...\033[0m\n" "$active_env" + printf "\033[0;31m[ghjk] Active environment (%s) not set up, please sync...\033[0m\n" "$next_env" fi fi fi @@ -87,16 +89,27 @@ ghjk_reload() { # memo to detect directory changes export GHJK_LAST_PWD="$PWD" +export GHJK_NEXTFILE="${TMPDIR:-/tmp}/ghjk.nextfile.$$" precmd() { # trigger reload when either # - the PWD changes - # - the env dir loader ctime changes - if [ "$GHJK_LAST_PWD" != "$PWD" ] || - [ "$(get_ctime_ts "$GHJK_LAST_ENV_DIR/activate.sh")" -gt "$GHJK_LAST_ENV_DIR_CTIME" ]; then + if [ "$GHJK_LAST_PWD" != "$PWD" ]; then + ghjk_reload export GHJK_LAST_PWD="$PWD" - # export GHJK_LAST_ENV="$GHJK_ENV" + + # -nextfile exists + elif [ -f "$GHJK_NEXTFILE" ]; then + + ghjk_reload "$(cat "$GHJK_NEXTFILE")" + rm "$GHJK_NEXTFILE" + + # - the env dir loader mtime changes + elif [ "$(__ghjk_get_mtime_ts "$GHJK_LAST_ENV_DIR/activate.sh")" -gt "$GHJK_LAST_ENV_DIR_MTIME" ]; then + + ghjk_reload + fi } diff --git a/install/mod.ts b/install/mod.ts index 7af7f4fb..e0814348 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -168,13 +168,12 @@ export async function install( const ghjkShareDir = $.path(Deno.cwd()) .resolve(args.ghjkShareDir); - logger.debug("unpacking vfs", { ghjkShareDir }); + logger.info("unpacking vfs", { ghjkShareDir }); await unpackVFS( await getHooksVfs(), ghjkShareDir, [[/__GHJK_SHARE_DIR__/g, ghjkShareDir.toString()]], ); - for (const shell of args.shellsToHook) { const { homeDir } = args; @@ -183,7 +182,7 @@ export async function install( } const rcPath = $.path(homeDir).join(shellConfig[shell]); - logger.debug("installing hook", { + logger.info("installing hook", { ghjkShareDir, shell, marker: args.shellHookMarker, @@ -205,7 +204,7 @@ export async function install( case "darwin": { const installDir = await $.path(args.ghjkExecInstallDir).ensureDir(); const exePath = installDir.resolve(`ghjk`); - logger.debug("installing executable", { exePath }); + logger.info("installing executable", { exePath }); // use an isolated cache by default const denoCacheDir = args.ghjkDenoCacheDir diff --git a/mod.ts b/mod.ts index 4bb39ad0..23aa2837 100644 --- a/mod.ts +++ b/mod.ts @@ -25,7 +25,7 @@ const DEFAULT_BASE_ENV_NAME = "main"; const file = new Ghjkfile(); const mainEnv = file.addEnv({ name: DEFAULT_BASE_ENV_NAME, - base: false, + inherit: false, allowedPortDeps: stdDeps(), desc: "the default default environment.", }); diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index b5c96899..9ea3f4d7 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -91,23 +91,9 @@ export class EnvsModule extends ModuleBase { - If no [envName] is specified and no env is currently active, this activates the configured default env [${ecx.config.defaultEnv}].`) .arguments("[envName:string]") - .option( - "--shell ", - "The shell to use. Tries to detect the current shell if not provided.", - ) - .action(async function ({ shell: shellMaybe }, envNameMaybe) { - const shell = shellMaybe ?? await detectShellPath(); - if (!shell) { - throw new Error( - "unable to detct shell in use. Use `--shell` flag to explicitly pass shell program.", - ); - } + .action(async function (_, envNameMaybe) { const envName = envNameMaybe ?? ecx.config.defaultEnv; - // FIXME: the ghjk process will be around and consumer resources - // with approach. Ideally, we'd detach the child and exit but this is blocked by - // https://github.com/denoland/deno/issues/5501 is closed - await $`${shell}` - .env({ GHJK_ENV: envName }); + await activateEnv(gcx, envName); }), ) .command( @@ -148,22 +134,10 @@ Just simply cooks and activates an environment. - If no [envName] is specified and no env is currently active, this syncs the configured default env [${ecx.config.defaultEnv}]. - If the environment is already active, this doesn't launch a new shell.`) .arguments("[envName:string]") - .option( - "--shell ", - "The shell to use. Tries to detect the current shell if not provided.", - ) - .action(async function ({ shell: shellMaybe }, envNameMaybe) { - const shell = shellMaybe ?? await detectShellPath(); - if (!shell) { - throw new Error( - "unable to detct shell in use. Use `--shell` flag to explicitly pass shell program.", - ); - } + .action(async function (_, envNameMaybe) { const envName = envNameMaybe ?? ecx.activeEnv; await reduceAndCookEnv(gcx, ecx, envName); - if (ecx.activeEnv != envName) { - await $`${shell}`.env({ GHJK_ENV: envName }); - } + await activateEnv(gcx, envName); }), }; } @@ -322,3 +296,21 @@ async function showableEnv( envName, }; } + +async function activateEnv(gcx: GhjkCtx, envName: string) { + const nextfile = Deno.env.get("GHJK_NEXTFILE"); + if (nextfile) { + await $.path(gcx.ghjkDir).join("envs", "next").writeText(envName); + } else { + const shell = await detectShellPath(); + if (!shell) { + throw new Error( + "unable to detct shell in use. Use `--shell` flag to explicitly pass shell program.", + ); + } + // FIXME: the ghjk process will be around and consumer resources + // with approach. Ideally, we'd detach the child and exit but this is blocked by + // https://github.com/denoland/deno/issues/5501 is closed + await $`${shell}`.env({ GHJK_ENV: envName }); + } +} diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 833fe091..767e6b82 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -185,85 +185,135 @@ async function writeActivators( onEnterHooks: [string, string[]][], onExitHooks: [string, string[]][], ) { - // ghjk.sh sets the DENO_DIR so we can usually - // assume it's set - const denoDir = Deno.env.get("DENO_DIR") ?? ""; + const ghjkDirVar = "_ghjk_dir"; + const shareDirVar = "_ghjk_share_dir"; + pathVars = { + ...Object.fromEntries( + Object.entries(pathVars).map(( + [key, val], + ) => [ + key, + val + .replace(gcx.ghjkDir.toString(), "$" + ghjkDirVar) + .replace(gcx.ghjkShareDir.toString(), "$" + shareDirVar), + ]), + ), + }; + const ghjkShimName = "__ghjk_shim"; - const onEnterHooksEscaped = onEnterHooks.map( - ([cmd, args]) => - [cmd == "ghjk" ? ghjkShimName : cmd, ...args] - .join(" ").replaceAll("'", "'\\''"), + const onEnterHooksEscaped = onEnterHooks.map(([cmd, args]) => + [cmd == "ghjk" ? ghjkShimName : cmd, ...args] + .join(" ").replaceAll("'", "'\\''") ); - const onExitHooksEscaped = onExitHooks.map( - ([cmd, args]) => - [cmd == "ghjk" ? ghjkShimName : cmd, ...args] - .join(" ").replaceAll("'", "'\\''"), + const onExitHooksEscaped = onExitHooks.map(([cmd, args]) => + [cmd == "ghjk" ? ghjkShimName : cmd, ...args] + .join(" ").replaceAll("'", "'\\''") ); - const activate = { + + // ghjk.sh sets the DENO_DIR so we can usually + // assume it's set + const denoDir = Deno.env.get("DENO_DIR") ?? ""; + const scripts = { // // posix shell version posix: [ - `if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then - eval "$GHJK_CLEANUP_POSIX" -fi`, + `if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then`, + ` eval "$GHJK_CLEANUP_POSIX"`, + `fi`, `export GHJK_CLEANUP_POSIX="";`, - "\n# env vars", - ...Object.entries(env).map(([key, val]) => + ``, + `# the following variables are used to make the script more human readable`, + `${ghjkDirVar}="${gcx.ghjkDir.toString()}"`, + `${shareDirVar}="${gcx.ghjkShareDir.toString()}"`, + ``, + `# env vars`, + ...Object.entries(env).flatMap(([key, val]) => [ // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution - `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX"export ${key}='$${key}';"; -export ${key}='${val}';` - ), - "\n# path vars", - ...Object.entries(pathVars).map(([key, val]) => + // NOTE: single quoting embedded in double quotes still expands allowing us to + // capture the value of $KEY before activator + // NOTE: we only restore the old $KEY value at cleanup if $KEY is the one set by the activator + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'[ \"$${key}\"'" = '${val}' ] && export ${key}='$${key}';";`, + `export ${key}='${val}';`, + ``, + ]), + ``, + `# path vars`, + ...Object.entries(pathVars).flatMap(([key, val]) => [ // NOTE: double quote the path vars for expansion // single quote GHJK_CLEANUP additions to avoid expansion/exec before eval - `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${key}=$(echo "$${key}" | tr ":" "\\n" | grep -vE "^${val}" | tr "\\n" ":");${key}="\${${key}%:}";'; -export ${key}="${val}:$${key}"; -` - ), - "\n# hooks that want to invoke ghjk are made to rely", - "# on this shim instead improving latency", + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${key}=$(echo "$${key}" | tr ":" "\\n" | grep -vE "'^${val}'" | tr "\\n" ":");${key}="\${${key}%:}";';`, + `export ${key}="${val}:$${key}";`, + ``, + ]), + ``, + `# hooks that want to invoke ghjk are made to rely`, + `# on this shim instead improving latency`, ghjk_sh(gcx, denoDir, ghjkShimName), - "\n# on enter hooks", - ...onEnterHooksEscaped, - "\n# on exit hooks", + ``, + `case "$-" in`, + ` *i*)`, + ``, + ` # on enter hooks`, + ...onEnterHooksEscaped.map((line) => ` ${line}`), + ``, + ` # on exit hooks`, ...onExitHooksEscaped.map( - (command) => `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${command};';`, + (cmd) => ` GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${cmd};';`, ), - ].join("\n"), + ` :`, + ` ;;`, + ` *)`, + ` :`, + ` ;;`, + `esac`, + ``, + ], // // fish version fish: [ - `if set --query GHJK_CLEANUP_FISH - eval $GHJK_CLEANUP_FISH - set --erase GHJK_CLEANUP_FISH -end`, - "\n# env vars", - ...Object.entries(env).map(([key, val]) => - `set --global --append GHJK_CLEANUP_FISH "set --global --export ${key} '$${key}';"; -set --global --export ${key} '${val}';` - ), - "\n# path vars", - ...Object.entries(pathVars).map(([key, val]) => - `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${key} (string match --invert --regex "^${val}" $${key});'; -set --global --export --prepend ${key} ${val}; -` - ), - "\n# hooks that want to invoke ghjk are made to rely", - "# on this shim instead improving latency", + `if set --query GHJK_CLEANUP_FISH`, + ` eval $GHJK_CLEANUP_FISH`, + ` set --erase GHJK_CLEANUP_FISH`, + `end`, + ``, + `# the following variables are used to make the script more human readable`, + `set ${ghjkDirVar} "${gcx.ghjkDir.toString()}"`, + `set ${shareDirVar} "${gcx.ghjkShareDir.toString()}"`, + ``, + `# env vars`, + ...Object.entries(env).flatMap(([key, val]) => [ + `set --global --append GHJK_CLEANUP_FISH 'test "$${key}"'" = '${val}'; and set --global --export ${key} '$${key}';";`, + `set --global --export ${key} '${val}';`, + ``, + ]), + ``, + `# path vars`, + ...Object.entries(pathVars).flatMap(([key, val]) => [ + `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${key} (string match --invert --regex '"^${val}"' $${key});';`, + `set --global --export --prepend ${key} ${val};`, + ``, + ]), + ``, + `# hooks that want to invoke ghjk are made to rely`, + `# on this shim instead improving latency`, ghjk_fish(gcx, denoDir, ghjkShimName), - "\n# on enter hooks", - ...onEnterHooksEscaped, - "\n# on exit hooks", - ...onExitHooksEscaped.map( - (command) => `set --global --append GHJK_CLEANUP_FISH '${command};';`, + ``, + `if status is-interactive;`, + ` # on enter hooks`, + ...onEnterHooksEscaped.map((line) => ` ${line}`), + , + ``, + ` # on exit hooks`, + ...onExitHooksEscaped.map((cmd) => + ` set --global --append GHJK_CLEANUP_FISH '${cmd};';` ), - ].join("\n"), + `end`, + ], }; const envPathR = await $.path(envDir).ensureDir(); await Promise.all([ - envPathR.join(`activate.fish`).writeText(activate.fish), - envPathR.join(`activate.sh`).writeText(activate.posix), + envPathR.join(`activate.fish`).writeText(scripts.fish.join("\n")), + envPathR.join(`activate.sh`).writeText(scripts.posix.join("\n")), ]); } diff --git a/modules/ports/ghrel.ts b/modules/ports/ghrel.ts index 6ada10a9..7345f766 100644 --- a/modules/ports/ghrel.ts +++ b/modules/ports/ghrel.ts @@ -1,9 +1,4 @@ -import { - $, - downloadFile, - DownloadFileArgs, - exponentialBackoff, -} from "../../utils/mod.ts"; +import { $, downloadFile, DownloadFileArgs } from "../../utils/mod.ts"; import { zod } from "../../deps/common.ts"; import { PortBase } from "./base.ts"; import type { DownloadArgs, ListAllArgs } from "./types.ts"; @@ -73,7 +68,7 @@ export abstract class GithubReleasePort extends PortBase { async latestStable(args: ListAllArgs) { const metadata = await $.withRetries({ count: 10, - delay: exponentialBackoff(1000), + delay: $.exponentialBackoff(1000), action: async () => await $.request( `https://api.github.com/repos/${this.repoOwner}/${this.repoName}/releases/latest`, @@ -88,7 +83,7 @@ export abstract class GithubReleasePort extends PortBase { async listAll(args: ListAllArgs) { const metadata = await $.withRetries({ count: 10, - delay: exponentialBackoff(1000), + delay: $.exponentialBackoff(1000), action: async () => await $.request( `https://api.github.com/repos/${this.repoOwner}/${this.repoName}/releases`, diff --git a/ports/cpy_bs.ts b/ports/cpy_bs.ts index 1a6f050a..3c19603c 100644 --- a/ports/cpy_bs.ts +++ b/ports/cpy_bs.ts @@ -10,7 +10,6 @@ import { depExecShimPath, downloadFile, dwnUrlOut, - exponentialBackoff, osXarch, PortBase, std_fs, @@ -79,7 +78,7 @@ export class Port extends PortBase { async latestMeta(headers: Record) { const meta = await $.withRetries({ count: 10, - delay: exponentialBackoff(1000), + delay: $.exponentialBackoff(1000), action: async () => await $.request( `https://raw.githubusercontent.com/${this.repoOwner}/${this.repoName}/latest-release/latest-release.json`, @@ -113,7 +112,7 @@ export class Port extends PortBase { // on every release const metadata = await $.withRetries({ count: 10, - delay: exponentialBackoff(1000), + delay: $.exponentialBackoff(1000), action: async () => await $.request( `https://api.github.com/repos/${this.repoOwner}/${this.repoName}/releases/tags/${tag}`, diff --git a/tests/envHooks.ts b/tests/envHooks.ts index ab399a79..cf20e562 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -6,6 +6,7 @@ set -eux export GHJK_WD=$PWD # hook creates a marker file +[ "$GHJK_ENV" = 'main' ] || exit 111 [ $(cat "$GHJK_WD/marker") = 'remark' ] || exit 101 pushd ../ @@ -19,68 +20,6 @@ popd [ $(cat $GHJK_WD/marker) = 'remark' ] || exit 103 `; -const bashInteractiveScript = [ - // simulate interactive mode by evaluating the prompt - // before each line - ` -eval_PROMPT_COMMAND() { - local prompt_command - for prompt_command in "\${PROMPT_COMMAND[@]}"; do - eval "$prompt_command" - done -} -`, - ...posixInteractiveScript - .split("\n").map((line) => - `eval_PROMPT_COMMAND -${line} -` - ), -] - .join("\n"); - -const zshInteractiveScript = [ - // simulate interactive mode by evaluating precmd - // before each line - ...posixInteractiveScript - .split("\n").map((line) => - `precmd -${line} -` - ), -] - .join("\n"); - -const posixNonInteractiveScript = ` -set -eux - -export GHJK_WD=$PWD - -# test that ghjk_reload is avail because BASH_ENV exposed by the suite -ghjk_reload - -# hook creates a marker file -[ $(cat "$GHJK_WD/marker") = 'remark' ] || exit 101 - -pushd ../ -# no reload so it's stil avail -[ $(cat "$GHJK_WD/marker") = 'remark' ] || exit 102 - -ghjk_reload -# marker should be gone by now -[ ! -e "$GHJK_WD/marker" ] || exit 103 - -# cd back in -popd - -# not avail yet -[ ! -e "$GHJK_WD/marker" ] || exit 104 - -ghjk_reload -# now it should be avail -[ $(cat "$GHJK_WD/marker") = 'remark' ] || exit 105 -`; - const fishScript = ` set fish_trace 1 export GHJK_WD=$PWD @@ -99,6 +38,17 @@ popd test (cat $GHJK_WD/marker) = 'remark'; or exit 103 `; +const fishInteractiveScript = [ + // simulate interactive mode by emitting prexec after each line + // after each line + ...fishScript + .split("\n").flatMap((line) => [ + line, + `emit fish_preexec`, + ]), +] + .join("\n"); + type CustomE2eTestCase = Omit & { ePoint: string; stdin: string; @@ -107,37 +57,21 @@ const cases: CustomE2eTestCase[] = [ { name: "bash_interactive", // -s: read from stdin - // -l: login/interactive mode - ePoint: `bash -sl`, - stdin: bashInteractiveScript, - }, - { - name: "bash_scripting", - ePoint: `bash -s`, - stdin: posixNonInteractiveScript, + // -l: login mode + // -i: make it interactive + ePoint: `bash -sil`, + stdin: posixInteractiveScript, }, { name: "zsh_interactive", - ePoint: `zsh -sl`, - stdin: zshInteractiveScript, - }, - { - name: "zsh_scripting", - ePoint: `zsh -s`, - stdin: posixNonInteractiveScript, + ePoint: `zsh -sil`, + stdin: posixInteractiveScript + .split("\n").filter((line) => !/^#/.test(line)).join("\n"), }, { name: "fish_interactive", - ePoint: `fish -l`, - stdin: fishScript, - }, - { - name: "fish_scripting", - ePoint: `fish`, - // the fish implementation triggers changes - // on any pwd changes so it's identical to - // interactive usage - stdin: fishScript, + ePoint: `fish -il`, + stdin: fishInteractiveScript, }, ]; diff --git a/tests/envs.ts b/tests/envs.ts index 4ffd0993..0be9b369 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -40,7 +40,7 @@ const envVarTestEnvs: EnvDefArgs[] = [ }, { name: "yuki", - base: false, + inherit: false, vars: { HUMM: "Soul Lady", }, @@ -110,7 +110,7 @@ const installTestEnvs: EnvDefArgs[] = [ }, { name: "foo", - base: false, + inherit: false, installs: [ dummy({ output: "foo" }), ], diff --git a/tests/reloadHooks.ts b/tests/reloadHooks.ts index 376fdc8c..b26f1e51 100644 --- a/tests/reloadHooks.ts +++ b/tests/reloadHooks.ts @@ -3,6 +3,9 @@ import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; import dummy from "../ports/dummy.ts"; import type { InstallConfigFat } from "../port.ts"; +// TODO: test for hook reload when ghjk.ts is touched +// TODO: test for hook reload when nextfile is touched + const posixInteractiveScript = ` set -eux [ "$DUMMY_ENV" = "dummy" ] || exit 101 @@ -14,8 +17,10 @@ sh -c "dummy" pushd ../ # it shouldn't be avail here -[ $(set +e; dummy) ] && exit 102 +set +ex +[ $(dummy) ] && exit 102 [ "$DUMMY_ENV" = "dummy" ] && exit 103 +set -ex # cd back in popd @@ -23,39 +28,12 @@ popd # now it should be avail dummy [ "$DUMMY_ENV" = "dummy" ] || exit 106 -`; -const bashInteractiveScript = [ - // simulate interactive mode by evaluating the prompt - // before each line - ` -eval_PROMPT_COMMAND() { - local prompt_command - for prompt_command in "\${PROMPT_COMMAND[@]}"; do - eval "$prompt_command" - done -} -`, - ...posixInteractiveScript - .split("\n").map((line) => - `eval_PROMPT_COMMAND -${line} -` - ), -] - .join("\n"); - -const zshInteractiveScript = [ - // simulate interactive mode by evaluating precmd - // before each line - ...posixInteractiveScript - .split("\n").map((line) => - `precmd -${line} -` - ), -] - .join("\n"); +[ "$GHJK_ENV" = "main" ] || exit 107 +ghjk e cook test +echo "test" > $GHJK_NEXTFILE +[ "$GHJK_ENV" = "test" ] || exit 108 +`; const posixNonInteractiveScript = ` set -eux @@ -89,6 +67,17 @@ ghjk_reload # now it should be avail dummy [ "$DUMMY_ENV" = "dummy" ] || exit 106 + +[ "$GHJK_ENV" = "main" ] || exit 107 +ghjk e cook test + +ghjk_reload test +[ "$GHJK_ENV" = "test" ] || exit 110 +ghjk_reload +[ "$GHJK_ENV" = "test" ] || exit 111 + +GHJK_ENV=test ghjk_reload +[ "$GHJK_ENV" = "test" ] || exit 112 `; const fishScript = ` @@ -112,8 +101,60 @@ dummy; or exit 123 test $DUMMY_ENV = "dummy"; or exit 105 `; +const fishNoninteractiveScript = ` +set fish_trace 1 +# test that ghjk_reload is avail because config.fish exposed by the suite +ghjk_reload + +${fishScript} + +# must cook test first +ghjk envs cook test + +test $GHJK_ENV = "main"; or exit 107 + +# manually switch to test +ghjk_reload test +test "$GHJK_ENV" = "test"; or exit 108 + +# re-invoking reload won't go back to main +ghjk_reload +test "$GHJK_ENV" = "test"; or exit 109 + +# go back to main +ghjk_reload main +test "$GHJK_ENV" = "main"; or exit 111 + +# changing GHJK_ENV manually gets respected +GHJK_ENV=test ghjk_reload +test "$GHJK_ENV" = "test"; or exit 112`; + +const fishInteractiveScript = [ + fishScript, + // simulate interactive mode by emitting postexec after each line + // after each line + ...` +ghjk e cook test +test $GHJK_ENV = "main"; or exit 107 + +echo "test" > $GHJK_NEXTFILE +test "$GHJK_ENV" = "test"; or exit 108 + +ghjk_reload main +test "$GHJK_ENV" = "main"; or exit 111 + +GHJK_ENV=test ghjk_reload +test "$GHJK_ENV" = "test"; or exit 112 +` + .split("\n").flatMap((line) => [ + line, + `emit fish_preexec`, + ]), +] + .join("\n"); + type CustomE2eTestCase = Omit & { - installConf?: InstallConfigFat | InstallConfigFat[]; + installConf?: InstallConfigFat[]; ePoint: string; stdin: string; }; @@ -121,9 +162,10 @@ const cases: CustomE2eTestCase[] = [ { name: "bash_interactive", // -s: read from stdin - // -l: login/interactive mode - ePoint: `bash -sl`, - stdin: bashInteractiveScript, + // -l: login mode + // -i: interactive mode + ePoint: `bash -sli`, + stdin: posixInteractiveScript, }, { name: "bash_scripting", @@ -132,8 +174,9 @@ const cases: CustomE2eTestCase[] = [ }, { name: "zsh_interactive", - ePoint: `zsh -sl`, - stdin: zshInteractiveScript, + ePoint: `zsh -sli`, + stdin: posixInteractiveScript + .split("\n").filter((line) => !/^#/.test(line)).join("\n"), }, { name: "zsh_scripting", @@ -142,8 +185,8 @@ const cases: CustomE2eTestCase[] = [ }, { name: "fish_interactive", - ePoint: `fish -l`, - stdin: fishScript, + ePoint: `fish -il`, + stdin: fishInteractiveScript, }, { name: "fish_scripting", @@ -151,14 +194,24 @@ const cases: CustomE2eTestCase[] = [ // the fish implementation triggers changes // on any pwd changes so it's identical to // interactive usage - stdin: fishScript, + stdin: fishNoninteractiveScript, }, ]; harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: genTsGhjkFile( - { installConf: testCase.installConf ?? dummy(), taskDefs: [] }, + { + envDefs: [ + { + name: "main", + installs: testCase.installConf ? testCase.installConf : [dummy()], + }, + { + name: "test", + }, + ], + }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], name: `reloadHooks/${testCase.name}`, diff --git a/tests/utils.ts b/tests/utils.ts index 904f8efd..ad15166a 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -179,6 +179,7 @@ export async function localE2eTest(testCase: E2eTestCase) { export type TaskDef = & Omit & Required>; + export function genTsGhjkFile( { installConf, secureConf, taskDefs, envDefs }: { installConf?: InstallConfigFat | InstallConfigFat[]; @@ -200,12 +201,14 @@ export function genTsGhjkFile( ? val.replaceAll(/\\/g, "\\\\") : val, ); + const serializedSecConf = JSON.stringify( // undefined is not recognized by JSON.parse // so we stub it with null secureConf ?? null, (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, ); + const tasks = (taskDefs ?? []).map( (def) => { const stringifiedSection = JSON.stringify( @@ -220,6 +223,7 @@ export function genTsGhjkFile( })`; }, ).join("\n"); + const envs = (envDefs ?? []).map( (def) => { const stringifiedSection = JSON.stringify( @@ -233,6 +237,7 @@ export function genTsGhjkFile( })`; }, ).join("\n"); + return ` export { ghjk } from "$ghjk/mod.ts"; import * as ghjk from "$ghjk/mod.ts"; diff --git a/utils/mod.ts b/utils/mod.ts index 62d782a1..df1f8a6b 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -196,6 +196,20 @@ export const $ = dax.build$( requestBuilder: new dax.RequestBuilder() .showProgress(Deno.stderr.isTerminal()), extras: { + exponentialBackoff(initialDelayMs: number) { + let delay = initialDelayMs; + let attempt = 0; + + return { + next() { + if (attempt > 0) { + delay *= 2; + } + attempt += 1; + return delay; + }, + }; + }, inspect(val: unknown) { return Deno.inspect(val, { colors: isColorfulTty(), @@ -236,7 +250,7 @@ export async function findEntryRecursive(path: string, name: string) { } } -export function home_dir(): string | null { +export function homeDir() { switch (Deno.build.os) { case "linux": case "darwin": @@ -249,7 +263,7 @@ export function home_dir(): string | null { } export function dirs() { - const home = home_dir(); + const home = homeDir(); if (!home) { throw new Error("cannot find home dir"); } @@ -285,21 +299,6 @@ export async function importRaw(spec: string, timeout: dax.Delay = "1m") { ); } -export function exponentialBackoff(initialDelayMs: number) { - let delay = initialDelayMs; - let attempt = 0; - - return { - next() { - if (attempt > 0) { - delay *= 2; - } - attempt += 1; - return delay; - }, - }; -} - export async function shimScript( { shimPath, execPath, os, defArgs, envOverrides, envDefault }: { shimPath: string;