From 5fdcfc193752bee48a89e4443207e2922bfcf19b Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Thu, 28 Mar 2024 08:35:38 +0300 Subject: [PATCH 01/21] feat(envs): Envs module --- README.md | 30 ------ host/mod.ts | 6 +- host/types.ts | 15 +-- mod.ts | 47 +++++---- modules/envs/mod.ts | 84 +++++++++++++++ modules/envs/posix.ts | 230 +++++++++++++++++++++++++++++++++++++++++ modules/envs/source.ts | 0 modules/envs/types.ts | 70 +++++++++++++ modules/mod.ts | 4 +- modules/ports/mod.ts | 38 ++++--- modules/ports/sync.ts | 11 +- modules/ports/types.ts | 36 ++++--- modules/tasks/exec.ts | 9 +- modules/tasks/mod.ts | 47 ++++++--- modules/tasks/types.ts | 59 ++++++++--- modules/types.ts | 2 +- utils/mod.ts | 19 ++++ 17 files changed, 571 insertions(+), 136 deletions(-) create mode 100644 modules/envs/mod.ts create mode 100644 modules/envs/posix.ts create mode 100644 modules/envs/source.ts create mode 100644 modules/envs/types.ts diff --git a/README.md b/README.md index f38a831e..3c75880a 100644 --- a/README.md +++ b/README.md @@ -40,36 +40,6 @@ ghjk.install( ); ``` -## How it works - -The only required dependency is `deno`. Everything else is managed automatically -and looks as follows (abstracting away some implementation details): - -- the installer sets up a directory hook in your shell profile - - `.bashrc` - - `.zshrc` - - `.config/fish/config.fish` -- for every visited directory, the hook looks for `$PWD/ghjk.ts` in the - directory or its parents, and - - adds the `$HOME/.local/share/ghjk/envs/$PWD/shims/{bin,lib,include}` to your - paths - - sources environment variables in - `$HOME/.local/share/ghjk/envs/$PWD/loader.{sh,fish}` and clear previously - loaded ones (if any) -- you can then - - sync your runtime with `ghjk ports sync` which - - installs the missing tools at `$HOME/.local/share/ghjk/ports/installs` - - regenerates the shims with symlinks and environment variables - - detects any violation of the enforced rules - - [ ] `ghjk ports list`: list installed tools and versions - - [ ] `ghjk ports outdated`: list outdated tools - - [ ] `ghjk ports cleanup`: remove unused tools and versions - -## Extending `ghjk` - -```ts -``` - ## Development ```bash diff --git a/host/mod.ts b/host/mod.ts index 3dec14c3..cd204008 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -34,7 +34,7 @@ export async function cli(args: CliArgs) { logger().debug({ ghjkfilePath, ghjkDir }); - const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, state: new Map() }; + const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, blackboard: new Map() }; const hcx = { fileHashMemoStore: new Map() }; const { subCommands, serializedConfig } = await readConfig(gcx, hcx); @@ -245,8 +245,8 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { const pMan = await instance.processManifest( gcx, man, + newLockObj.config.blackboard, lockEntries[man.id], - newLockObj.config.globalEnv, ); instances.push([man.id, instance, pMan] as const); subCommands[man.id] = instance.command(gcx, pMan); @@ -290,7 +290,7 @@ async function readAndSerializeConfig( ): Promise { switch (configPath.extname()) { case "": - logger().warning("config file has no extension, assuming deno config"); + logger().warn("config file has no extension, assuming deno config"); /* falls through */ case ".ts": { logger().debug("serializing ts config", configPath); diff --git a/host/types.ts b/host/types.ts index 094c809b..a4d64f80 100644 --- a/host/types.ts +++ b/host/types.ts @@ -1,21 +1,22 @@ import { zod } from "../deps/common.ts"; import moduleValidators from "../modules/types.ts"; -import portsValidator from "../modules/ports/types.ts"; +// import portsValidator from "../modules/ports/types.ts"; -const globalEnv = zod.object({ - installs: zod.record(zod.string(), portsValidator.installConfigFat), - allowedPortDeps: zod.record(zod.string(), portsValidator.allowedPortDep), -}); +/* const blackboard = zod.object({ + // installs: zod.record(zod.string(), portsValidator.installConfigFat), + // allowedPortDeps: zod.record(zod.string(), portsValidator.allowedPortDep), +}); */ +const blackboard = zod.record(zod.string(), zod.unknown()); const serializedConfig = zod.object( { modules: zod.array(moduleValidators.moduleManifest), - globalEnv, + blackboard, }, ); export type SerializedConfig = zod.infer; -export type GlobalEnv = zod.infer; +export type Blackboard = zod.infer; export default { serializedConfig, diff --git a/mod.ts b/mod.ts index db2f94bb..cb5ac6a2 100644 --- a/mod.ts +++ b/mod.ts @@ -10,8 +10,7 @@ import portsValidators from "./modules/ports/types.ts"; import type { AllowedPortDep, InstallConfigFat, - PortsModuleConfig, - PortsModuleConfigBase, + PortsModuleConfigHashed, PortsModuleSecureConfig, } from "./modules/ports/types.ts"; import logger from "./utils/logger.ts"; @@ -20,17 +19,17 @@ import * as std_ports from "./modules/ports/std.ts"; import * as cpy from "./ports/cpy_bs.ts"; import * as node from "./ports/node.ts"; // hosts -import type { GlobalEnv, SerializedConfig } from "./host/types.ts"; +import type { SerializedConfig } from "./host/types.ts"; import * as std_modules from "./modules/std.ts"; // tasks import type { - TaskDef, - TaskEnv, - TasksModuleConfig, + TaskDefHashed, + TaskEnvHashed, + TasksModuleConfigHashed, } from "./modules/tasks/types.ts"; import { dax, jsonHash, objectHash } from "./deps/common.ts"; -const portsConfig: PortsModuleConfigBase = { installs: [] }; +const portsConfig: PortsModuleConfigHashed = { installs: [], allowedDeps: {} }; export type TaskFnArgs = { $: dax.$Type; @@ -39,7 +38,7 @@ export type TaskFnArgs = { }; export type TaskFn = (args: TaskFnArgs) => Promise; -export type TaskFnDef = TaskDef & { +export type TaskFnDef = TaskDefHashed & { fn: TaskFn; // command: cliffy_cmd.Command; }; @@ -47,10 +46,7 @@ export type TaskFnDef = TaskDef & { // TODO tasks config const tasks = {} as Record; -const globalEnv: GlobalEnv = { - installs: {}, - allowedPortDeps: {}, -}; +const bb = new Map(); // FIXME: ses.lockdown to freeze primoridials // freeze the object to prevent malicious tampering of the secureConfig @@ -72,16 +68,16 @@ function registerInstall(config: InstallConfigFat) { // jsonHash.digest is async const hash = objectHash(jsonHash.canonicalize(config as jsonHash.Tree)); - if (!globalEnv.installs[hash]) { - globalEnv.installs[hash] = config; + if (!bb.has(hash)) { + bb.set(hash, config); } return hash; } function registerAllowedPortDep(dep: AllowedPortDep) { const hash = objectHash(jsonHash.canonicalize(dep as jsonHash.Tree)); - if (!globalEnv.allowedPortDeps[hash]) { - globalEnv.allowedPortDeps[hash] = dep; + if (!bb.has(hash)) { + bb.set(hash, dep); } return hash; } @@ -92,12 +88,15 @@ function registerAllowedPortDep(dep: AllowedPortDep) { export type TaskDefNice = & Omit & Partial> - & Partial> + & Partial> & { allowedPortDeps?: AllowedPortDep[]; installs?: InstallConfigFat[] }; export function task(name: string, config: TaskDefNice) { - const allowedPortDeps = [ - ...(config.allowedPortDeps ?? (config.installs ? stdDeps() : [])), - ].map(registerAllowedPortDep); + const allowedPortDeps = Object.fromEntries( + [ + ...(config.allowedPortDeps ?? (config.installs ? stdDeps() : [])), + ] + .map((dep) => [dep.manifest.name, registerAllowedPortDep(dep)]), + ); // TODO validate installs? const installs = (config.installs ?? []).map(registerInstall); @@ -117,7 +116,7 @@ export function task(name: string, config: TaskDefNice) { } function addInstall( - cx: PortsModuleConfigBase, + cx: PortsModuleConfigHashed, configUnclean: InstallConfigFat, ) { const res = portsValidators.installConfigFat.safeParse(configUnclean); @@ -186,7 +185,7 @@ async function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { ] as const ), ]); - const fullPortsConfig: PortsModuleConfig = { + const fullPortsConfig: PortsModuleConfigHashed = { installs: portsConfig.installs, allowedDeps: allowedDeps, }; @@ -203,7 +202,7 @@ async function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { }], ), ); - const tasksConfig: TasksModuleConfig = { + const tasksConfig: TasksModuleConfigHashed = { tasks: Object.fromEntries( cmdJsons2, ), @@ -217,7 +216,7 @@ async function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { id: std_modules.tasks, config: tasksConfig, }], - globalEnv, + blackboard: Object.fromEntries(bb.entries()), }; return config; } catch (cause) { diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts new file mode 100644 index 00000000..aac41511 --- /dev/null +++ b/modules/envs/mod.ts @@ -0,0 +1,84 @@ +/* + Design: + - `$ ghjk env foo` to switch to environment + - By default, all things go to the `main` environment +*/ + +export * from "./types.ts"; + +import { cliffy_cmd, zod } from "../../deps/cli.ts"; +import { Json } from "../../utils/mod.ts"; + +import validators from "./types.ts"; +import type { EnvsModuleConfigX } from "./types.ts"; +import type { GhjkCtx, ModuleManifest } from "../types.ts"; +import { ModuleBase } from "../mod.ts"; + +import { Blackboard } from "../../host/types.ts"; + +export type EnvsCtx = {}; +const lockValidator = zod.object({ + version: zod.string(), +}); +type EnvsLockEnt = zod.infer; + +export class EnvsModule extends ModuleBase { + processManifest( + _ctx: GhjkCtx, + manifest: ModuleManifest, + _bb: Blackboard, + _lockEnt: EnvsLockEnt | undefined, + ) { + const res = validators.envsModuleConfig.safeParse(manifest.config); + if (!res.success) { + throw new Error("error parsing module config", { + cause: { + config: manifest.config, + zodErr: res.error, + }, + }); + } + const config: EnvsModuleConfigX = { + ...res.data, + }; + + return Promise.resolve({ + config, + }); + } + + command( + _gcx: GhjkCtx, + _ecx: EnvsCtx, + ) { + const root: cliffy_cmd.Command = new cliffy_cmd + .Command() + .alias("e") + .action(function () { + this.showHelp(); + }) + .description("Envs module."); + return root; + } + + loadLockEntry( + _gcx: GhjkCtx, + raw: Json, + ) { + const entry = lockValidator.parse(raw); + + if (entry.version != "0") { + throw new Error(`unexepected version tag deserializing lockEntry`); + } + + return entry; + } + genLockEntry( + _gcx: GhjkCtx, + _tcx: EnvsCtx, + ) { + return { + version: "0", + }; + } +} diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts new file mode 100644 index 00000000..01556ade --- /dev/null +++ b/modules/envs/posix.ts @@ -0,0 +1,230 @@ +import { std_fs, std_path } from "../../deps/cli.ts"; +import type { + EnvRecipeX, + ProvisionReducer, + WellKnownEnvRecipeX, + WellKnownProvision, +} from "./types.ts"; +import validators from "./types.ts"; +import { wellKnownProvisionTypes } from "./types.ts"; +import getLogger from "../../utils/logger.ts"; +import { $, PathRef } from "../../utils/mod.ts"; +import type { GhjkCtx } from "../types.ts"; + +const logger = getLogger(import.meta); + +export type ProvisionReducerStore = Map; +export function getProvisionReducerStore( + gcx: GhjkCtx, +) { + const id = "provisionReducerStore"; + let store = gcx.blackboard.get(id) as + | ProvisionReducerStore + | undefined; + if (!store) { + store = new Map(); + gcx.blackboard.set(id, store); + } + return store; +} + +export async function reduceStrangeProvisions( + gcx: GhjkCtx, + env: EnvRecipeX, +) { + const reducerStore = getProvisionReducerStore(gcx); + const reducedSet = [] as WellKnownProvision[]; + for (const item of env.provides) { + if (wellKnownProvisionTypes.includes(item.ty as any)) { + reducedSet.push(validators.wellKnownProvision.parse(item)); + continue; + } + const reducer = reducerStore.get(item.ty); + if (!reducer) { + throw new Error(`no provider reducer found for ty: ${item.ty}`, { + cause: item, + }); + } + const reduced = await reducer(item); + reducedSet.push(validators.wellKnownProvision.parse(reduced)); + } + const out: WellKnownEnvRecipeX = { + ...env, + provides: reducedSet, + }; + return out; +} + +export async function cookUnixEnv( + env: WellKnownEnvRecipeX, + envDir: string, + createShellLoaders = true, +) { + // create the shims for the user's environment + const shimDir = $.path(envDir).join("shims"); + await $.removeIfExists(shimDir); + + const [binShimDir, libShimDir, includeShimDir] = await Promise.all([ + shimDir.join("bin").ensureDir(), + shimDir.join("lib").ensureDir(), + shimDir.join("include").ensureDir(), + ]); + + // extract the env vars exported by the user specified + // installs and shim up their exported artifacts + const binPaths = [] as string[]; + const libPaths = [] as string[]; + const includePaths = [] as string[]; + // FIXME: detect shim conflicts + // FIXME: better support for multi installs + + await Promise.all(env.provides.map((item) => { + switch (item.ty) { + case "posixExec": + binPaths.push(item.absolutePath); + break; + case "posixSharedLib": + libPaths.push(item.absolutePath); + break; + case "headerFile": + includePaths.push(item.absolutePath); + break; + default: + throw Error(`unsupported provision type: ${(item as any).provision}`); + } + })); + // bin shims + void await shimLinkPaths( + binPaths, + binShimDir, + ); + // lib shims + void await shimLinkPaths( + libPaths, + libShimDir, + ); + // include shims + void await shimLinkPaths( + includePaths, + includeShimDir, + ); + // write loader for the env vars mandated by the installs + logger.debug("adding vars to loader", env.vars); + // FIXME: prevent malicious env manipulations + let LD_LIBRARY_ENV: string; + switch (Deno.build.os) { + case "darwin": + LD_LIBRARY_ENV = "DYLD_LIBRARY_PATH"; + break; + case "linux": + LD_LIBRARY_ENV = "LD_LIBRARY_PATH"; + break; + default: + throw new Error(`unsupported os ${Deno.build.os}`); + } + const pathVars = { + PATH: `${envDir}/shims/bin`, + LIBRARY_PATH: `${envDir}/shims/lib`, + [LD_LIBRARY_ENV]: `${envDir}/shims/lib`, + C_INCLUDE_PATH: `${envDir}/shims/include`, + CPLUS_INCLUDE_PATH: `${envDir}/shims/include`, + }; + if (createShellLoaders) { + await writeLoader( + envDir, + env.vars, + pathVars, + ); + } + return { + env: { + ...env.vars, + ...pathVars, + }, + }; +} + +/// This expands globs found in the targetPaths +async function shimLinkPaths( + targetPaths: string[], + shimDir: PathRef, +) { + // map of filename to shimPath + const shims: Record = {}; + // a work sack to append to incase there are globs expanded + const foundTargetPaths = [...targetPaths]; + while (foundTargetPaths.length > 0) { + const file = foundTargetPaths.pop()!; + if (std_path.isGlob(file)) { + foundTargetPaths.push( + ...(await Array.fromAsync(std_fs.expandGlob(file))) + .map((entry) => entry.path), + ); + continue; + } + const filePath = $.path(file); + const fileName = filePath.basename(); + const shimPath = shimDir.resolve(fileName); + + if (shims[fileName]) { + throw new Error( + `duplicate shim found when adding shim for file "${fileName}"`, + ); + } + try { + await $.path(shimPath).remove(); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) { + throw error; + } + } + await shimPath.createSymlinkTo(filePath, { kind: "absolute" }); + shims[fileName] = shimPath.toString(); + } + return shims; +} + +// create the loader scripts +// loader scripts are responsible for exporting +// different environment variables from the ports +// and mainpulating the path strings +async function writeLoader( + envDir: string, + env: Record, + pathVars: Record, +) { + const loader = { + posix: [ + `export GHJK_CLEANUP_POSIX="";`, + ...Object.entries(env).map(([k, v]) => + // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX"export ${k}='$${k}';"; +export ${k}='${v}';` + ), + ...Object.entries(pathVars).map(([k, v]) => + // NOTE: double quote the path vars for expansion + // single quote GHJK_CLEANUP additions to avoid expansion/exec before eval + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${k}=$(echo "$${k}" | tr ":" "\\n" | grep -vE "^${envDir}" | tr "\\n" ":");${k}="\${${k}%:}";'; +export ${k}="${v}:$${k}"; +` + ), + ].join("\n"), + fish: [ + `set --erase GHJK_CLEANUP_FISH`, + ...Object.entries(env).map(([k, v]) => + `set --global --append GHJK_CLEANUP_FISH "set --global --export ${k} '$${k}';"; +set --global --export ${k} '${v}';` + ), + ...Object.entries(pathVars).map(([k, v]) => + `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${k} (string match --invert --regex "^${envDir}" $${k});'; +set --global --export --prepend ${k} ${v}; +` + ), + ].join("\n"), + }; + const envPathR = await $.path(envDir).ensureDir(); + await Promise.all([ + envPathR.join(`loader.fish`).writeText(loader.fish), + envPathR.join(`loader.sh`).writeText(loader.posix), + ]); +} diff --git a/modules/envs/source.ts b/modules/envs/source.ts new file mode 100644 index 00000000..e69de29b diff --git a/modules/envs/types.ts b/modules/envs/types.ts new file mode 100644 index 00000000..1fac39c7 --- /dev/null +++ b/modules/envs/types.ts @@ -0,0 +1,70 @@ +import { std_path, zod } from "../../deps/common.ts"; + +const absolutePath = zod.string().refine((path) => std_path.isAbsolute(path)); + +const envVars = zod.record(zod.string(), zod.string()); + +const provision = zod.object({ ty: zod.string() }).passthrough(); + +export const wellKnownProvisionTypes = [ + "posixExec", + "posixSharedLib", + "headerFile", +] as const; + +const wellKnownProvision = zod.discriminatedUnion( + "ty", + [ + // the types require that the discrim union array is not + // empty so we move the first item out of the `map` statement + // to appease typescript + zod.object({ ty: zod.literal(wellKnownProvisionTypes[0]), absolutePath }), + ...wellKnownProvisionTypes.slice(1).map((ty) => + zod.object({ ty: zod.literal(ty), absolutePath }) + ), + ], +); + +const envRecipe = zod.object({ + vars: envVars, + provides: zod.array(provision), +}); + +const wellKnownEnvRecipe = envRecipe.merge(zod.object({ + provides: zod.array(wellKnownProvision), +})); + +const envsModuleConfig = zod.object({ + envs: zod.record(zod.string(), envRecipe), +}); + +const validators = { + provision, + wellKnownProvision, + envRecipe, + envsModuleConfig, + wellKnownEnvRecipe, +}; +export default validators; + +export type EnvsModuleConfig = zod.input; +export type EnvsModuleConfigX = zod.infer; + +export type Provision = zod.input; +export type WellKnownProvision = zod.input< + typeof validators.wellKnownProvision +>; + +export type EnvRecipe = zod.input; +export type EnvRecipeX = zod.infer; + +export type WellKnownEnvRecipe = zod.input< + typeof validators.wellKnownEnvRecipe +>; +export type WellKnownEnvRecipeX = zod.infer< + typeof validators.wellKnownEnvRecipe +>; + +export type ProvisionReducer = ( + provision: Provision, +) => Promise>; diff --git a/modules/mod.ts b/modules/mod.ts index 86397f43..fca0db6e 100644 --- a/modules/mod.ts +++ b/modules/mod.ts @@ -1,5 +1,5 @@ import { cliffy_cmd } from "../deps/cli.ts"; -import { GlobalEnv } from "../host/types.ts"; +import { Blackboard } from "../host/types.ts"; import type { Json } from "../utils/mod.ts"; import type { GhjkCtx, ModuleManifest } from "./types.ts"; @@ -7,8 +7,8 @@ export abstract class ModuleBase { abstract processManifest( ctx: GhjkCtx, manifest: ModuleManifest, + bb: Blackboard, lockEnt: LockEnt | undefined, - env: GlobalEnv, ): Promise | Ctx; // returns undefined if previous lock entry is no longer valid abstract loadLockEntry( diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index d269f516..73b11e27 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -14,7 +14,7 @@ import { type InstallGraph, syncCtxFromGhjk, } from "./sync.ts"; -import { GlobalEnv } from "../../host/types.ts"; +import { Blackboard } from "../../host/types.ts"; type PortsCtx = { config: PortsModuleConfigX; @@ -34,24 +34,36 @@ export class PortsModule extends ModuleBase { async processManifest( gcx: GhjkCtx, manifest: ModuleManifest, + bb: Blackboard, _lockEnt: PortsLockEnt | undefined, - env: GlobalEnv, ) { - const res = validators.portsModuleConfig.safeParse(manifest.config); - if (!res.success) { - throw new Error("error parsing module config", { - cause: { - config: manifest.config, - zodErr: res.error, - }, - }); + function unwrapParseRes(res: zod.SafeParseReturnType) { + if (!res.success) { + throw new Error("error parsing module config", { + cause: { + zodErr: res.error, + id: manifest.id, + config: manifest.config, + bb, + }, + }); + } + return res.data; } + const hashed = unwrapParseRes( + validators.portsModuleConfigHashed.safeParse(manifest.config), + ); const config: PortsModuleConfigX = { - installs: res.data.installs.map((hash) => env.installs[hash]), + installs: hashed.installs.map((hash) => + unwrapParseRes(validators.installConfigFat.safeParse(bb[hash])) + ), allowedDeps: Object.fromEntries( - Object.entries(res.data.allowedDeps).map(( + Object.entries(hashed.allowedDeps).map(( [key, value], - ) => [key, env.allowedPortDeps[value]]), + ) => [ + key, + unwrapParseRes(validators.allowedPortDep.safeParse(bb[value])), + ]), ), }; diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 0fb4937f..8bfc1ebe 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -38,12 +38,13 @@ export type SyncCtx = DePromisify>; export function getResolutionMemo( gcx: GhjkCtx, ) { - let memoStore = gcx.state.get("resolutionMemoStore") as + const id = "resolutionMemoStore"; + let memoStore = gcx.blackboard.get(id) as | ResolutionMemoStore | undefined; if (!memoStore) { memoStore = new Map(); - gcx.state.set("resolutionMemoStore", memoStore); + gcx.blackboard.set(id, memoStore); } return memoStore; } @@ -60,7 +61,7 @@ export async function syncCtxFromGhjk( sameFsTmpRoot(portsPath.toString()), ]) ).map($.pathToString); - let db = gcx.state.get("installsDb") as + let db = gcx.blackboard.get("installsDb") as | Rc | undefined; if (!db) { @@ -72,10 +73,10 @@ export async function syncCtxFromGhjk( ), (db) => { db[Symbol.dispose](); - gcx.state.delete("installsDb"); + gcx.blackboard.delete("installsDb"); }, ); - gcx.state.set("installsDb", db); + gcx.blackboard.set("installsDb", db); } else { db = db.clone(); } diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 61e42e5e..5f09c706 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -148,27 +148,26 @@ const portsModuleSecureConfig = zod.object({ allowedPortDeps: zod.array(allowedPortDep).nullish(), }); -const portsModuleConfigBase = zod.object({ +const portsModuleConfigHashed = zod.object({ installs: zod.array(zod.string()), -}); - -const portsModuleConfig = portsModuleConfigBase.merge(zod.object({ allowedDeps: zod.record( zod.string(), zod.string(), ), -})); - -const portsModuleConfigBaseX = zod.object({ - installs: zod.array(installConfigFat), }); -const portsModuleConfigX = portsModuleConfigBaseX.merge(zod.object({ +const portsModuleConfig = zod.object({ + installs: zod.array(installConfigFat), allowedDeps: zod.record( zod.string(), allowedPortDep, ), -})); +}); + +const installEnvProvision = zod.object({ + ty: zod.literal("ghjkInstall"), + confHash: zod.string(), +}); const validators = { osEnum, @@ -190,11 +189,11 @@ const validators = { installConfig, installConfigResolved, portManifest, - portsModuleConfigBase, portsModuleSecureConfig, portsModuleConfig, - portsModuleConfigX, + portsModuleConfigHashed, allowedPortDep, + installEnvProvision, string: zod.string(), stringArray: zod.string().min(1).array(), }; @@ -262,8 +261,8 @@ export type InstallConfigResolvedX = zod.infer< typeof validators.installConfigResolved >; -export type PortsModuleConfigBase = zod.infer< - typeof validators.portsModuleConfigBase +export type InstallEnvProvision = zod.infer< + typeof validators.installEnvProvision >; export type AllowedPortDep = zod.input; @@ -281,7 +280,14 @@ export type PortsModuleSecureConfigX = zod.input< export type PortsModuleConfig = zod.input; export type PortsModuleConfigX = zod.infer< - typeof validators.portsModuleConfigX + typeof validators.portsModuleConfig +>; + +export type PortsModuleConfigHashed = zod.input< + typeof validators.portsModuleConfigHashed +>; +export type PortsModuleConfigLiteHashedX = zod.infer< + typeof validators.portsModuleConfigHashed >; /* diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index a4bb5cfe..1042429c 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -11,7 +11,6 @@ import { installFromGraphAndShimEnv, syncCtxFromGhjk, } from "../ports/sync.ts"; -import { GlobalEnv } from "../../host/types.ts"; export type ExecCtx = DePromisify>; @@ -33,7 +32,7 @@ export type TaskGraph = DePromisify>; export async function buildTaskGraph( ecx: ExecCtx, portsConfig: TasksModuleConfigX, - env: GlobalEnv, + // env: Blackboard, ) { const graph = { indie: [] as string[], @@ -50,10 +49,8 @@ export async function buildTaskGraph( await buildInstallGraph( ecx.syncCx, { - installs: task.env.installs.map((hash) => env.installs[hash]), - allowedDeps: Object.fromEntries(task.env.allowedPortDeps.map( - (dep) => [dep.manifest.name, dep], - )), + installs: task.env.installs, + allowedDeps: task.env.allowedPortDeps, }, ), ]), diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index 608737c3..b8c464c7 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -4,6 +4,7 @@ import { cliffy_cmd, zod } from "../../deps/cli.ts"; import { Json } from "../../utils/mod.ts"; import validators from "./types.ts"; +import portValidators from "../ports/types.ts"; import type { TasksModuleConfigX } from "./types.ts"; import type { GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; @@ -14,7 +15,7 @@ import { execTask, type TaskGraph, } from "./exec.ts"; -import { GlobalEnv } from "../../host/types.ts"; +import { Blackboard } from "../../host/types.ts"; export type TasksCtx = { config: TasksModuleConfigX; @@ -29,27 +30,45 @@ export class TasksModule extends ModuleBase { async processManifest( ctx: GhjkCtx, manifest: ModuleManifest, + bb: Blackboard, _lockEnt: TasksLockEnt | undefined, - env: GlobalEnv, ) { - const res = validators.tasksModuleConfig.safeParse(manifest.config); - if (!res.success) { - throw new Error("error parsing module config", { - cause: { - config: manifest.config, - zodErr: res.error, - }, - }); + function unwrapParseRes(res: zod.SafeParseReturnType) { + if (!res.success) { + throw new Error("error parsing module config", { + cause: { + zodErr: res.error, + id: manifest.id, + config: manifest.config, + bb, + }, + }); + } + return res.data; } + const hashed = unwrapParseRes( + validators.tasksModuleConfigHashed.safeParse(manifest.config), + ); const config: TasksModuleConfigX = { tasks: Object.fromEntries( - Object.entries(res.data.tasks).map( + Object.entries(hashed.tasks).map( ([name, task]) => [name, { ...task, env: { ...task.env, - allowedPortDeps: task.env.allowedPortDeps.map((hash) => - env.allowedPortDeps[hash] + installs: task.env.installs.map((hash) => + unwrapParseRes( + portValidators.installConfigFat.safeParse(bb[hash]), + ) + ), + allowedPortDeps: Object.fromEntries( + Object.entries(task.env.allowedPortDeps) + .map(([key, hash]) => [ + key, + unwrapParseRes( + portValidators.allowedPortDep.safeParse(bb[hash]), + ), + ]), ), }, }], @@ -58,7 +77,7 @@ export class TasksModule extends ModuleBase { }; await using execCx = await execCtxFromGhjk(ctx); - const taskGraph = await buildTaskGraph(execCx, config, env); + const taskGraph = await buildTaskGraph(execCx, config); return { config, taskGraph, diff --git a/modules/tasks/types.ts b/modules/tasks/types.ts index 33025838..403af989 100644 --- a/modules/tasks/types.ts +++ b/modules/tasks/types.ts @@ -6,16 +6,20 @@ import portsValidator from "../ports/types.ts"; const taskName = zod.string().regex(/[^\s]/); const taskEnvBase = zod.object({ - installs: zod.string().array(), env: zod.record(zod.string(), zod.string()), }); -const taskEnv = taskEnvBase.merge(zod.object({ - allowedPortDeps: zod.string().array(), +const taskEnvHashed = taskEnvBase.merge(zod.object({ + installs: zod.string().array(), + allowedPortDeps: zod.record(zod.string(), zod.string()), })); -const taskEnvX = taskEnvBase.merge(zod.object({ - allowedPortDeps: portsValidator.allowedPortDep.array(), +const taskEnv = taskEnvBase.merge(zod.object({ + installs: portsValidator.installConfigFat.array(), + allowedPortDeps: zod.record( + zod.string(), + portsValidator.allowedPortDep, + ), })); const taskDefBase = zod.object({ @@ -27,24 +31,47 @@ const taskDefBase = zod.object({ const taskDef = taskDefBase.merge(zod.object({ env: taskEnv, })); -const taskDefX = taskDefBase.merge(zod.object({ - env: taskEnvX, + +const taskDefHashed = taskDefBase.merge(zod.object({ + env: taskEnvHashed, })); const tasksModuleConfig = zod.object({ tasks: zod.record(taskName, taskDef), }); -const tasksModuleConfigX = zod.object({ - tasks: zod.record(taskName, taskDefX), + +const tasksModuleConfigHashed = zod.object({ + tasks: zod.record(taskName, taskDefHashed), }); -export default { + +const validators = { + taskEnv, + taskEnvHashed, taskDef, + taskDefHashed, tasksModuleConfig, + tasksModuleConfigHashed, }; +export default validators; + +export type TaskEnv = zod.input; +export type TaskEnvX = zod.infer; + +export type TaskEnvHashed = zod.input; +export type TaskEnvHashedX = zod.infer; + +export type TaskDef = zod.input; +export type TaskDefX = zod.infer; + +export type TaskDefHashed = zod.input; +export type TaskDefHashedX = zod.infer; + +export type TasksModuleConfig = zod.input; +export type TasksModuleConfigX = zod.infer; -export type TaskEnv = zod.input; -export type TaskEnvX = zod.infer; -export type TaskDef = zod.input; -export type TaskDefX = zod.infer; -export type TasksModuleConfig = zod.input; -export type TasksModuleConfigX = zod.infer; +export type TasksModuleConfigHashed = zod.input< + typeof validators.tasksModuleConfigHashed +>; +export type TasksModuleConfigHashedX = zod.infer< + typeof tasksModuleConfigHashed +>; diff --git a/modules/types.ts b/modules/types.ts index ae7fc499..e35b0606 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -14,7 +14,7 @@ export type GhjkCtx = { ghjkfilePath: string; ghjkDir: string; ghjkShareDir: string; - state: Map; + blackboard: Map; }; export default { diff --git a/utils/mod.ts b/utils/mod.ts index 960f17ff..c4a70c8e 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -459,3 +459,22 @@ export function thinInstallConfig(fat: InstallConfigFat) { ...lite, }; } + +export function match< + All, + K extends keyof All, +>( + val: K, + branches: All, +): All[K] extends () => infer Inner ? Inner : All[K] { + // return branches[val]; + const branch = branches[val]; + return typeof branch == "function" ? branch() : branch; +} + +const _b = match("hey", { + hey: () => 1, + hello: () => 2, + hi: 3 as const, + holla: 4, +}); From 002a39d469b6d978a5701d616b69bffb9308c973 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 30 Mar 2024 23:59:28 +0300 Subject: [PATCH 02/21] feat: rework `ghjk.ts` interface and integrate with other modules --- ghjk.ts | 4 +- mod.ts | 684 ++++++++++++++++++++++++++++++-------- modules/envs/posix.ts | 60 ++-- modules/envs/reducer.ts | 17 + modules/envs/source.ts | 0 modules/envs/types.ts | 29 +- modules/mod.ts | 3 + modules/ports/db.ts | 70 +++- modules/ports/mod.ts | 105 ++++-- modules/ports/reducers.ts | 140 ++++++++ modules/ports/sync.ts | 32 +- modules/ports/types.ts | 95 ++++-- modules/tasks/deno.ts | 25 +- modules/tasks/exec.ts | 99 ++---- modules/tasks/mod.ts | 69 +--- modules/tasks/types.ts | 47 +-- tests/ports.ts | 6 +- tests/tasks.ts | 6 +- tests/utils.ts | 6 +- utils/mod.ts | 26 ++ 20 files changed, 1067 insertions(+), 456 deletions(-) create mode 100644 modules/envs/reducer.ts delete mode 100644 modules/envs/source.ts create mode 100644 modules/ports/reducers.ts diff --git a/ghjk.ts b/ghjk.ts index 2c71bac2..bb6433f8 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -14,7 +14,7 @@ const ha = ghjk installs: [ ports.protoc(), ], - env: { STUFF: "stuffier" }, + envVars: { STUFF: "stuffier" }, async fn({ $ }) { await $`echo $STUFF; protoc --version; @@ -65,5 +65,5 @@ ghjk.install( ); export const secureConfig = ghjk.secureConfig({ - allowedPortDeps: [...ghjk.stdDeps({ enableRuntimes: true })], + masterPortDepAllowList: ghjk.stdDeps({ enableRuntimes: true }), }); diff --git a/mod.ts b/mod.ts index cb5ac6a2..a833d929 100644 --- a/mod.ts +++ b/mod.ts @@ -10,127 +10,586 @@ import portsValidators from "./modules/ports/types.ts"; import type { AllowedPortDep, InstallConfigFat, + InstallSetHashed, + InstallSetRefProvision, PortsModuleConfigHashed, PortsModuleSecureConfig, } from "./modules/ports/types.ts"; import logger from "./utils/logger.ts"; -import { $, defaultCommandBuilder, thinInstallConfig } from "./utils/mod.ts"; +import { + $, + defaultCommandBuilder, + thinInstallConfig, + unwrapParseRes, +} 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"; -// hosts +// host import type { SerializedConfig } from "./host/types.ts"; import * as std_modules from "./modules/std.ts"; // tasks -import type { - TaskDefHashed, - TaskEnvHashed, - TasksModuleConfigHashed, -} from "./modules/tasks/types.ts"; import { dax, jsonHash, objectHash } from "./deps/common.ts"; +// WARN: this module has side-effects and only ever import +// types from it +import type { ExecTaskArgs } from "./modules/tasks/deno.ts"; +import { TasksModuleConfig } from "./modules/tasks/types.ts"; +// envs +import { + EnvRecipe, + EnvsModuleConfig, + WellKnownProvision, +} from "./modules/envs/types.ts"; + +const DEFAULT_ENV_NAME = "main"; -const portsConfig: PortsModuleConfigHashed = { installs: [], allowedDeps: {} }; +export type EnvDefArgs = { + name: string; + installs?: InstallConfigFat[]; + allowedPortDeps?: AllowedPortDep[]; + /* + * If true or not set, will base the task's env on top + * of the default env (usually `main`). If false, will build on + * top of a new env. If given a string, will use the identified env as a base + * for the task env. + */ + envBase?: string | boolean; +}; export type TaskFnArgs = { $: dax.$Type; argv: string[]; env: Record; }; + export type TaskFn = (args: TaskFnArgs) => Promise; -export type TaskFnDef = TaskDefHashed & { +/* + * Configuration for a task. + */ +export type TaskDefArgs = { + name: string; fn: TaskFn; - // command: cliffy_cmd.Command; + desc?: string; + dependsOn?: string[]; + workingDir?: string | dax.PathRef; + envVars?: Record; + allowedPortDeps?: AllowedPortDep[]; + installs?: InstallConfigFat[]; + envBase?: string | boolean; }; -// TODO tasks config -const tasks = {} as Record; +class GhjkfileBuilder { + #installSets = new Map(); + #tasks = {} as Record< + string, + Omit & { + installs: string[]; + allowedPortDeps: Record; + } + >; + #bb = new Map(); + #seenEnvs: Record = {}; -const bb = new Map(); + addInstall( + setId: string, + configUnclean: InstallConfigFat, + ) { + const config = unwrapParseRes( + portsValidators.installConfigFat.safeParse(configUnclean), + { + config: configUnclean, + }, + `error parsing InstallConfig`, + ); -// FIXME: ses.lockdown to freeze primoridials -// freeze the object to prevent malicious tampering of the secureConfig -export const ghjk = Object.freeze({ - getConfig: Object.freeze(getConfig), - execTask: Object.freeze(execTask), -}); + const set = this.#getSet(setId); + set.installs.push(this.#registerInstall(config)); + logger().debug("install added", config); + } -export { $, logger }; + setAllowedPortDeps( + setId: string, + deps: AllowedPortDep[], + ) { + const set = this.#getSet(setId); + set.allowedDeps = Object.fromEntries( + deps.map(( + dep, + ) => [dep.manifest.name, this.#registerAllowedPortDep(dep)]), + ); + } -export function install(...configs: InstallConfigFat[]) { - const cx = portsConfig; - for (const config of configs) { - addInstall(cx, config); + addTask( + args: TaskDefArgs, + ) { + const allowedPortDeps = Object.fromEntries( + [ + ...(args.allowedPortDeps ?? (args.installs ? stdDeps() : [])), + ] + .map(( + dep, + ) => [dep.manifest.name, this.#registerAllowedPortDep(dep)]), + ); + + const installs = (args.installs ?? []).map((fat) => + this.#registerInstall(fat) + ); + + // NOTE: we make sure the env base declared here exists + // this call is necessary to make sure that a `task` can + // be declared before the `env` but still depend on it. + // Order-indepency like this makes the `ghjk.ts` way less + // brittle. + if (typeof args.envBase == "string") { + this.addEnv({ name: args.envBase }); + } + + this.#tasks[args.name] = { + ...args, + name, + installs, + allowedPortDeps, + }; + return args.name; } -} -function registerInstall(config: InstallConfigFat) { - // jsonHash.digest is async - const hash = objectHash(jsonHash.canonicalize(config as jsonHash.Tree)); + addEnv( + args: EnvDefArgs, + ) { + let env = this.#seenEnvs[args.name]?.[0]; + if (!env) { + let finalizer: EnvFinalizer; + env = new EnvBuilder(this, (fin) => finalizer = fin, args.name); + this.#seenEnvs[args.name] = [env, finalizer!]; + } + if (args.envBase) { + env.base(args.envBase); + } + if (args.installs) { + env.install(...args.installs); + } + if (args.allowedPortDeps) { + env.allowedPortDeps(args.allowedPortDeps); + } + return env; + } + + async execTask( + { name, workingDir, envVars, argv }: ExecTaskArgs, + ) { + const task = this.#tasks[name]; + if (!task) { + throw new Error(`no task defined under "${name}"`); + } + const custom$ = $.build$({ + commandBuilder: defaultCommandBuilder().env(envVars).cwd(workingDir), + }); + await task.fn({ argv, env: envVars, $: custom$ }); + } + + toConfig(secureConfig: PortsModuleSecureConfig | undefined) { + try { + const envsConfig = this.#processEnvs(); + + const tasksConfig: TasksModuleConfig = { + envs: {}, + tasks: {}, + }; + for ( + const [name, args] of Object + .entries( + this.#tasks, + ) + ) { + const { workingDir, desc, dependsOn, envBase } = args; + const envBaseResolved = typeof envBase === "string" + ? envBase + : envBase + ? DEFAULT_ENV_NAME + : null; + + const envBaseRecipe = envBaseResolved + ? envsConfig.envs[envBaseResolved] + : null; + + const taskEnvRecipe: EnvRecipe = { + provides: [], + }; + + const taskInstallSet: InstallSetHashed = { + installs: args.installs, + allowedDeps: args.allowedPortDeps, + }; + + const mergedEnvVars = args.envVars ?? {}; + if (envBaseRecipe) { + for ( + const prov of envBaseRecipe + .provides as ( + | WellKnownProvision + | InstallSetRefProvision + )[] + ) { + if (prov.ty == "envVar") { + if (!mergedEnvVars[prov.key]) { + mergedEnvVars[prov.key] = prov.val; + } + } else if (prov.ty == "ghjkPortsInstallSetRef") { + const baseSet = this.#installSets.get(prov.setId)!; + const mergedInstallsSet = new Set([ + ...taskInstallSet.installs, + ...baseSet.installs, + ]); + taskInstallSet.installs = [...mergedInstallsSet.values()]; + for ( + const [key, val] of Object.entries(baseSet.allowedDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the base + if (!taskInstallSet.allowedDeps[key]) { + taskInstallSet.allowedDeps[key] = val; + } + } + } else { + taskEnvRecipe.provides.push(prov); + } + } + } + if (taskInstallSet.installs.length > 0) { + const setId = `${name}_${crypto.randomUUID()}`; + this.#installSets.set(setId, taskInstallSet); + const prov: InstallSetRefProvision = { + ty: "ghjkPortsInstallSetRef", + setId, + }; + taskEnvRecipe.provides.push(prov); + } - if (!bb.has(hash)) { - bb.set(hash, config); + taskEnvRecipe.provides.push( + ...Object.entries(mergedEnvVars).map(( + [key, val], + ) => { + const prov: WellKnownProvision = { ty: "envVar", key, val }; + return prov; + }), + ); + + const envHash = objectHash( + jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), + ); + tasksConfig.envs[envHash] = taskEnvRecipe; + + tasksConfig.tasks[name] = { + name, + workingDir: typeof workingDir == "object" + ? workingDir.toString() + : workingDir, + desc, + dependsOn, + envHash, + }; + } + for (const [name, { dependsOn }] of Object.entries(tasksConfig.tasks)) { + for (const depName of dependsOn ?? []) { + if (!tasksConfig.tasks[depName]) { + throw new Error( + `task "${name}" depend on non-existent task "${depName}"`, + ); + } + } + } + + const masterPortDepAllowList = Object.fromEntries([ + ...(secureConfig?.masterPortDepAllowList ?? stdDeps()) + .map((dep) => + [ + dep.manifest.name, + this.#registerAllowedPortDep( + portsValidators.allowedPortDep.parse(dep), + ), + ] as const + ), + ]); + + const fullPortsConfig: PortsModuleConfigHashed = { + sets: {}, + }; + for ( + const [setId, set] of this.#installSets.entries() + ) { + for (const [portName, _] of Object.entries(set.allowedDeps)) { + if (!masterPortDepAllowList[portName]) { + throw new Error( + `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, + ); + } + } + fullPortsConfig.sets[setId] = set; + } + + const config: SerializedConfig = { + modules: [{ + id: std_modules.ports, + config: fullPortsConfig, + }, { + id: std_modules.tasks, + config: tasksConfig, + }], + blackboard: Object.fromEntries(this.#bb.entries()), + }; + return config; + } catch (cause) { + throw new Error(`error constructing config for serialization`, { cause }); + } + } + + #getSet(setId: string) { + let set = this.#installSets.get(setId); + if (!set) { + set = { installs: [], allowedDeps: {} }; + this.#installSets.set(setId, set); + } + return set; + } + + #registerInstall(config: InstallConfigFat) { + // jsonHash.digest is async + const hash = objectHash(jsonHash.canonicalize(config as jsonHash.Tree)); + + if (!this.#bb.has(hash)) { + this.#bb.set(hash, config); + } + return hash; + } + + #registerAllowedPortDep(dep: AllowedPortDep) { + const hash = objectHash(jsonHash.canonicalize(dep as jsonHash.Tree)); + if (!this.#bb.has(hash)) { + this.#bb.set(hash, dep); + } + return hash; + } + + // this processes the defined envs, normalizing dependency (i.e. "envBase") + // relationships to produce the standard EnvsModuleConfig + #processEnvs() { + const all = {} as Record< + string, + ReturnType & { envBaseResolved: null | string } + >; + const indie = [] as string[]; + const revDeps = new Map(); + for ( + const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) + ) { + const final = finalizer(); + const { name, envBase } = final; + const envBaseResolved = typeof envBase === "string" + ? envBase + : envBase + ? DEFAULT_ENV_NAME + : null; + all[name] = { ...final, envBaseResolved }; + if (envBaseResolved) { + let parentRevDeps = revDeps.get(envBaseResolved); + if (!parentRevDeps) { + parentRevDeps = []; + revDeps.set(envBaseResolved, parentRevDeps); + } + parentRevDeps.push(final.name); + } else { + indie.push(name); + } + } + const processed = {} as Record; + const out: EnvsModuleConfig = { envs: {} }; + const workingSet = indie; + while (workingSet.length > 0) { + const item = workingSet.pop()!; + const final = all[item]; + const base = final.envBaseResolved + ? processed[final.envBaseResolved] + : null; + let processedInstallSetId: string | undefined; + { + const installSet = this.#installSets.get(final.installSetId); + if (installSet) { + // if base also has an install set + if (base?.installSetId) { + // merge the parent's installs into this one + const baseSet = this.#installSets.get( + base.installSetId, + )!; + const mergedInstallsSet = new Set([ + ...installSet.installs, + ...baseSet.installs, + ]); + installSet.installs = [...mergedInstallsSet.values()]; + for ( + const [key, val] of Object.entries(baseSet.allowedDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the parent + if (!installSet.allowedDeps[key]) { + installSet.allowedDeps[key] = val; + } + } + } + processedInstallSetId = final.installSetId; + } // if there's no install set found under the id + else { + // implies that the env has not ports explicitly configured + if (base) { + processedInstallSetId = base.installSetId; + } + } + } + processed[final.name] = { installSetId: processedInstallSetId }; + out.envs[final.name] = { + provides: [ + ...Object.entries(final.vars).map(( + [key, val], + ) => { + const prov: WellKnownProvision = { ty: "envVar", key, val }; + return prov; + }), + ], + }; + if (processedInstallSetId) { + const prov: InstallSetRefProvision = { + ty: "ghjkPortsInstallSetRef", + setId: processedInstallSetId, + }; + out.envs[final.name].provides.push(prov); + } + } + return out; } - return hash; } -function registerAllowedPortDep(dep: AllowedPortDep) { - const hash = objectHash(jsonHash.canonicalize(dep as jsonHash.Tree)); - if (!bb.has(hash)) { - bb.set(hash, dep); +type EnvFinalizer = () => { + name: string; + installSetId: string; + envBase: string | boolean; + vars: Record; +}; + +// this class will be exposed to users and thus features +// a contrived implementation of the `build`/`finalize` method +// all to avoid exposing the function in the public api +class EnvBuilder { + #installSetId: string; + #file: GhjkfileBuilder; + #base: string | boolean = true; + #vars: Record = {}; + + constructor( + file: GhjkfileBuilder, + setFinalizer: (fin: EnvFinalizer) => void, + public name: string, + ) { + this.#file = file; + this.#installSetId = `${name}_${crypto.randomUUID()}`; + setFinalizer(() => ({ + name: this.name, + installSetId: this.#installSetId, + envBase: this.#base, + vars: this.#vars, + })); + } + + base(base: string | boolean) { + this.#base = base; + } + + /* + * Provision a port install in the environment. + */ + install(...configs: InstallConfigFat[]) { + for (const config of configs) { + this.#file.addInstall(this.#installSetId, config); + } + return this; + } + + /* + * This is treated as a single set and will replace previously any configured set. + */ + allowedPortDeps(deps: AllowedPortDep[]) { + this.#file.setAllowedPortDeps(this.#installSetId, deps); + } + + var(key: string, val: string) { + this.vars({ [key]: val }); + } + + vars(envVars: Record) { + Object.assign(this.#vars, envVars); } - return hash; } +const file = new GhjkfileBuilder(); +const mainEnv = file.addEnv({ + name: DEFAULT_ENV_NAME, + envBase: false, + allowedPortDeps: stdDeps(), +}); + +export { $, logger }; + +// FIXME: ses.lockdown to freeze primoridials +// freeze the object to prevent malicious tampering of the secureConfig +export const ghjk = Object.freeze({ + getConfig: Object.freeze( + (secureConfig: PortsModuleSecureConfig | undefined) => + file.toConfig(secureConfig), + ), + execTask: Object.freeze( + (args: ExecTaskArgs) => file.execTask(args), + ), +}); + /* - * A nicer form of TaskFnDef for better ergonomics in the ghjkfile + * Provision a port install in the `main` environment. */ -export type TaskDefNice = - & Omit - & Partial> - & Partial> - & { allowedPortDeps?: AllowedPortDep[]; installs?: InstallConfigFat[] }; -export function task(name: string, config: TaskDefNice) { - const allowedPortDeps = Object.fromEntries( - [ - ...(config.allowedPortDeps ?? (config.installs ? stdDeps() : [])), - ] - .map((dep) => [dep.manifest.name, registerAllowedPortDep(dep)]), - ); - - // TODO validate installs? - const installs = (config.installs ?? []).map(registerInstall); - - tasks[name] = { - name, - fn: config.fn, - desc: config.desc, - dependsOn: config.dependsOn ?? [], - env: { - installs, - env: config.env ?? {}, - allowedPortDeps, - }, - }; - return name; +export function install(...configs: InstallConfigFat[]) { + mainEnv.install(...configs); } -function addInstall( - cx: PortsModuleConfigHashed, - configUnclean: InstallConfigFat, -) { - const res = portsValidators.installConfigFat.safeParse(configUnclean); - if (!res.success) { - throw new Error(`error parsing InstallConfig`, { - cause: { - config: configUnclean, - zodErr: res.error, - }, - }); +export function task(args: TaskDefArgs): string; +export function task(name: string, args: Omit): string; +export function task(name: string, fn: TaskFn): string; +export function task( + nameOrArgs: string | TaskDefArgs, + argsOrFn?: Omit | TaskFn, +): string { + let args: TaskDefArgs; + if (typeof nameOrArgs == "object") { + args = nameOrArgs; + } else if (typeof argsOrFn == "object") { + args = { ...argsOrFn, name: nameOrArgs }; + } else if (argsOrFn) { + args = { + name: nameOrArgs, + fn: argsOrFn, + }; + } else { + throw new Error("no function provided when defining task"); } - const config = res.data; - logger().debug("install added", config); - cx.installs.push(registerInstall(config)); + return file.addTask(args); +} + +export function env(args: EnvDefArgs): EnvBuilder; +export function env(name: string, args?: Omit): EnvBuilder; +export function env( + nameOrArgs: string | EnvDefArgs, + argsMaybe?: Omit, +): EnvBuilder { + const args = typeof nameOrArgs == "object" + ? nameOrArgs + : { ...argsMaybe, name: nameOrArgs }; + return file.addEnv(args); } export function secureConfig( @@ -158,68 +617,3 @@ export function stdDeps(args = { enableRuntimes: false }) { } return out; } - -async function execTask( - name: string, - argv: string[], - envVars: Record, -) { - const task = tasks[name]; - if (!task) { - throw new Error(`no task defined under "${name}"`); - } - const custom$ = $.build$({ - commandBuilder: defaultCommandBuilder().env(envVars), - }); - await task.fn({ argv, env: envVars, $: custom$ }); -} - -async function getConfig(secureConfig: PortsModuleSecureConfig | undefined) { - try { - const allowedDeps = Object.fromEntries([ - ...(secureConfig?.allowedPortDeps ?? stdDeps()) - .map((dep) => - [ - dep.manifest.name, - registerAllowedPortDep(portsValidators.allowedPortDep.parse(dep)), - ] as const - ), - ]); - const fullPortsConfig: PortsModuleConfigHashed = { - installs: portsConfig.installs, - allowedDeps: allowedDeps, - }; - - // const cmdJsons = await Promise.all( - // Object.entries(tasks.comands).map( - // async ([name, cmd]) => [name, await zcli_json.zcliJson(tasksCli, cmd)], - // ), - // ); - const cmdJsons2 = await Promise.all( - Object.entries(tasks).map( - ([name, task]) => [name, { - ...task, - }], - ), - ); - const tasksConfig: TasksModuleConfigHashed = { - tasks: Object.fromEntries( - cmdJsons2, - ), - }; - - const config: SerializedConfig = { - modules: [{ - id: std_modules.ports, - config: fullPortsConfig, - }, { - id: std_modules.tasks, - config: tasksConfig, - }], - blackboard: Object.fromEntries(bb.entries()), - }; - return config; - } catch (cause) { - throw new Error(`error constructing config for serialization`, { cause }); - } -} diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 01556ade..fd7c2484 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -1,7 +1,7 @@ import { std_fs, std_path } from "../../deps/cli.ts"; import type { EnvRecipeX, - ProvisionReducer, + Provision, WellKnownEnvRecipeX, WellKnownProvision, } from "./types.ts"; @@ -10,42 +10,39 @@ import { wellKnownProvisionTypes } from "./types.ts"; import getLogger from "../../utils/logger.ts"; import { $, PathRef } from "../../utils/mod.ts"; import type { GhjkCtx } from "../types.ts"; +import { getProvisionReducerStore } from "./reducer.ts"; const logger = getLogger(import.meta); -export type ProvisionReducerStore = Map; -export function getProvisionReducerStore( - gcx: GhjkCtx, -) { - const id = "provisionReducerStore"; - let store = gcx.blackboard.get(id) as - | ProvisionReducerStore - | undefined; - if (!store) { - store = new Map(); - gcx.blackboard.set(id, store); - } - return store; -} - export async function reduceStrangeProvisions( gcx: GhjkCtx, env: EnvRecipeX, ) { const reducerStore = getProvisionReducerStore(gcx); - const reducedSet = [] as WellKnownProvision[]; + const bins = {} as Record; for (const item of env.provides) { - if (wellKnownProvisionTypes.includes(item.ty as any)) { - reducedSet.push(validators.wellKnownProvision.parse(item)); + let bin = bins[item.ty]; + if (!bin) { + bin = []; + bins[item.ty] = bin; + } + bin.push(item); + } + const reducedSet = [] as WellKnownProvision[]; + for (const [ty, items] of Object.entries(bins)) { + if (wellKnownProvisionTypes.includes(ty as any)) { + reducedSet.push( + ...items.map((item) => validators.wellKnownProvision.parse(item)), + ); continue; } - const reducer = reducerStore.get(item.ty); + const reducer = reducerStore.get(ty); if (!reducer) { - throw new Error(`no provider reducer found for ty: ${item.ty}`, { - cause: item, + throw new Error(`no provider reducer found for ty: ${ty}`, { + cause: items, }); } - const reduced = await reducer(item); + const reduced = await reducer(items); reducedSet.push(validators.wellKnownProvision.parse(reduced)); } const out: WellKnownEnvRecipeX = { @@ -75,6 +72,7 @@ export async function cookUnixEnv( const binPaths = [] as string[]; const libPaths = [] as string[]; const includePaths = [] as string[]; + const vars = {} as Record; // FIXME: detect shim conflicts // FIXME: better support for multi installs @@ -89,6 +87,16 @@ export async function cookUnixEnv( case "headerFile": includePaths.push(item.absolutePath); break; + case "envVar": + if (vars[item.key]) { + throw new Error( + `env var conflict cooking unix env: key "${item.key}" has entries "${ + vars[item.key] + }" and "${item.val}"`, + ); + } + vars[item.key] = vars[item.val]; + break; default: throw Error(`unsupported provision type: ${(item as any).provision}`); } @@ -109,7 +117,7 @@ export async function cookUnixEnv( includeShimDir, ); // write loader for the env vars mandated by the installs - logger.debug("adding vars to loader", env.vars); + logger.debug("adding vars to loader", vars); // FIXME: prevent malicious env manipulations let LD_LIBRARY_ENV: string; switch (Deno.build.os) { @@ -132,13 +140,13 @@ export async function cookUnixEnv( if (createShellLoaders) { await writeLoader( envDir, - env.vars, + vars, pathVars, ); } return { env: { - ...env.vars, + ...vars, ...pathVars, }, }; diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts new file mode 100644 index 00000000..3860de33 --- /dev/null +++ b/modules/envs/reducer.ts @@ -0,0 +1,17 @@ +import type { GhjkCtx } from "../types.ts"; +import type { Provision, ProvisionReducer } from "./types.ts"; + +export type ProvisionReducerStore = Map>; +export function getProvisionReducerStore( + gcx: GhjkCtx, +) { + const id = "provisionReducerStore"; + let store = gcx.blackboard.get(id) as + | ProvisionReducerStore + | undefined; + if (!store) { + store = new Map(); + gcx.blackboard.set(id, store); + } + return store; +} diff --git a/modules/envs/source.ts b/modules/envs/source.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/envs/types.ts b/modules/envs/types.ts index 1fac39c7..49fb6fa0 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -2,31 +2,36 @@ import { std_path, zod } from "../../deps/common.ts"; const absolutePath = zod.string().refine((path) => std_path.isAbsolute(path)); -const envVars = zod.record(zod.string(), zod.string()); - const provision = zod.object({ ty: zod.string() }).passthrough(); -export const wellKnownProvisionTypes = [ +const posixFileProvisionTypes = [ "posixExec", "posixSharedLib", "headerFile", ] as const; +// we separate the posix file types in a separate +// array in the interest of type inference +export const wellKnownProvisionTypes = [ + "envVar", + ...posixFileProvisionTypes, +] as const; + const wellKnownProvision = zod.discriminatedUnion( "ty", [ - // the types require that the discrim union array is not - // empty so we move the first item out of the `map` statement - // to appease typescript - zod.object({ ty: zod.literal(wellKnownProvisionTypes[0]), absolutePath }), - ...wellKnownProvisionTypes.slice(1).map((ty) => + zod.object({ + ty: zod.literal(wellKnownProvisionTypes[0]), + key: zod.string(), + val: zod.string(), + }), + ...posixFileProvisionTypes.map((ty) => zod.object({ ty: zod.literal(ty), absolutePath }) ), ], ); const envRecipe = zod.object({ - vars: envVars, provides: zod.array(provision), }); @@ -65,6 +70,6 @@ export type WellKnownEnvRecipeX = zod.infer< typeof validators.wellKnownEnvRecipe >; -export type ProvisionReducer = ( - provision: Provision, -) => Promise>; +export type ProvisionReducer

= ( + provision: P[], +) => Promise; diff --git a/modules/mod.ts b/modules/mod.ts index fca0db6e..2d2241d6 100644 --- a/modules/mod.ts +++ b/modules/mod.ts @@ -4,6 +4,9 @@ import type { Json } from "../utils/mod.ts"; import type { GhjkCtx, ModuleManifest } from "./types.ts"; export abstract class ModuleBase { + /* init( + _gcx: GhjkCtx, + ): Promise | void {} */ abstract processManifest( ctx: GhjkCtx, manifest: ModuleManifest, diff --git a/modules/ports/db.ts b/modules/ports/db.ts index d626830e..54934e68 100644 --- a/modules/ports/db.ts +++ b/modules/ports/db.ts @@ -1,21 +1,22 @@ // Deno.Kv api is unstable /// -import type { - DownloadArtifacts, - InstallArtifacts, - InstallConfigLite, - PortManifestX, -} from "./types.ts"; - -export type InstallRow = { - installId: string; - conf: InstallConfigLite; - manifest: PortManifestX; - installArts?: InstallArtifacts; - downloadArts: DownloadArtifacts; - progress: "downloaded" | "installed"; -}; +import { zod } from "../../deps/common.ts"; +import validators from "./types.ts"; + +// const logger = getLogger(import.meta); + +// NOTE: make sure any changes to here are backwards compatible +const installRowValidator = zod.object({ + installId: zod.string(), + conf: validators.installConfigLite, + manifest: validators.portManifest, + installArts: validators.installArtifacts.nullish(), + downloadArts: validators.downloadArtifacts, + progress: zod.enum(["downloaded", "installed"]), +}).passthrough(); + +export type InstallRow = zod.infer; export abstract class InstallsDb { abstract all(): Promise; @@ -61,3 +62,42 @@ class DenoKvInstallsDb extends InstallsDb { } } } + +// TODO: implement me +/* +class InlineInstallsDb extends InstallsDb { + #map = new Map(); + #dbDir: PathRef; + constructor( + dbDir: string, + ) { + super(); + this.#dbDir = $.path(dbDir); + } + all(): Promise { + throw new Error("Method not implemented."); + } + async get(id: string): Promise { + let row = this.#map.get(id); + if (!row) { + const res = installRowValidator.safeParse( + await this.#dbDir.join(`${id}.meta`).readMaybeJson(), + ); + if (!res.success) { + logger.warn() + } + } + return row; + } + set(id: string, row: InstallRow): Promise { + this.#map.set(id, row); + throw new Error("Method not implemented."); + } + delete(id: string): Promise { + this.#map.delete(id); + throw new Error("Method not implemented."); + } + [Symbol.dispose](): void { + throw new Error("Method not implemented."); + } +}*/ diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 73b11e27..74b531f1 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -1,10 +1,13 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { $, Json } from "../../utils/mod.ts"; +import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; import logger from "../../utils/logger.ts"; -import validators from "./types.ts"; -import type { PortsModuleConfigX } from "./types.ts"; +import validators, { + installSetProvisionTy, + installSetRefProvisionTy, +} from "./types.ts"; +import type { InstallSetX, PortsModuleConfigX } from "./types.ts"; import type { GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import { @@ -13,12 +16,18 @@ import { installFromGraphAndShimEnv, type InstallGraph, syncCtxFromGhjk, -} from "./sync.ts"; -import { Blackboard } from "../../host/types.ts"; +} from "./sync.ts"; // TODO: rename to install.ts +import type { Blackboard } from "../../host/types.ts"; +import { getProvisionReducerStore } from "../envs/reducer.ts"; +import { installSetReducer, installSetRefReducer } from "./reducers.ts"; +import type { Provision, ProvisionReducer } from "../envs/types.ts"; -type PortsCtx = { +export type PortsCtx = { config: PortsModuleConfigX; - installGraph: InstallGraph; + /* + * A map from a setId found in the `PortsModuleConfigX` to the `InstallGraph`. + */ + installGraphs: Map; }; const lockValidator = zod.object({ @@ -37,39 +46,62 @@ export class PortsModule extends ModuleBase { bb: Blackboard, _lockEnt: PortsLockEnt | undefined, ) { - function unwrapParseRes(res: zod.SafeParseReturnType) { - if (!res.success) { - throw new Error("error parsing module config", { - cause: { - zodErr: res.error, - id: manifest.id, - config: manifest.config, - bb, - }, - }); - } - return res.data; + function unwrapParseCurry(res: zod.SafeParseReturnType) { + return unwrapParseRes(res, { + id: manifest.id, + config: manifest.config, + bb, + }, "error parsing module config"); } - const hashed = unwrapParseRes( + + const hashedModConf = unwrapParseCurry( validators.portsModuleConfigHashed.safeParse(manifest.config), ); - const config: PortsModuleConfigX = { - installs: hashed.installs.map((hash) => - unwrapParseRes(validators.installConfigFat.safeParse(bb[hash])) - ), - allowedDeps: Object.fromEntries( - Object.entries(hashed.allowedDeps).map(( - [key, value], - ) => [ - key, - unwrapParseRes(validators.allowedPortDep.safeParse(bb[value])), - ]), - ), + const pcx: PortsCtx = { + config: { + sets: {}, + }, + installGraphs: new Map(), }; + // pre-process the install sets found in the config + { + // syncCx contains a reference counted db connection + // somewhere deep in there + // so we need to use `using` + await using syncCx = await syncCtxFromGhjk(gcx); + for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { + // install sets in the config use hash references to dedupe InstallConfigs + // reify the references from the blackboard from continuing + const set: InstallSetX = { + installs: hashedSet.installs.map((hash) => + unwrapParseCurry(validators.installConfigFat.safeParse(bb[hash])) + ), + allowedDeps: Object.fromEntries( + Object.entries(hashedSet.allowedDeps).map(( + [key, value], + ) => [ + key, + unwrapParseCurry(validators.allowedPortDep.safeParse(bb[value])), + ]), + ), + }; + pcx.config.sets[id] = set; + pcx.installGraphs.set(id, await buildInstallGraph(syncCx, set)); + } + } - await using syncCx = await syncCtxFromGhjk(gcx); - const installGraph = await buildInstallGraph(syncCx, config); - return { config, installGraph }; + // register envrionment reducers for any + // environemnts making use of install sets + const reducerStore = getProvisionReducerStore(gcx); + reducerStore.set( + installSetRefProvisionTy, + installSetRefReducer(gcx, pcx) as ProvisionReducer, + ); + reducerStore.set( + installSetProvisionTy, + installSetReducer(gcx) as ProvisionReducer, + ); + return pcx; } command( @@ -91,7 +123,8 @@ export class PortsModule extends ModuleBase { void await installFromGraphAndShimEnv( syncCx, $.path(gcx.ghjkDir).join("envs", "default").toString(), - pcx.installGraph, + // FIXME: + pcx.installGraphs.values().next().value, ); }), ) diff --git a/modules/ports/reducers.ts b/modules/ports/reducers.ts new file mode 100644 index 00000000..e275c639 --- /dev/null +++ b/modules/ports/reducers.ts @@ -0,0 +1,140 @@ +//! Integration between Ports and Envs module + +import { expandGlobsAndAbsolutize, unwrapParseRes } from "../../utils/mod.ts"; +import type { WellKnownProvision } from "../envs/types.ts"; +import { GhjkCtx } from "../types.ts"; +// NOTE: mod.ts must always be a type import +import type { PortsCtx } from "./mod.ts"; +import { + buildInstallGraph, + installFromGraph, + type InstallGraph, + syncCtxFromGhjk, +} from "./sync.ts"; +import type { + InstallArtifacts, + InstallSetProvision, + InstallSetRefProvision, +} from "./types.ts"; +import validators from "./types.ts"; + +export function installSetReducer(gcx: GhjkCtx) { + return async (provisions: InstallSetProvision[]) => { + if (provisions.length > 1) { + throw new Error( + 'only one "ghjkPorts" provision per environment is supported', + ); + } + const { set } = unwrapParseRes( + validators.installSetProvision.safeParse(provisions[0]), + {}, + "error parsing env provision", + ); + await using scx = await syncCtxFromGhjk(gcx); + const installGraph = await buildInstallGraph(scx, set); + const installArts = await installFromGraph(scx, installGraph); + + const out = await reduceInstArts(installGraph, installArts); + return out; + }; +} + +export function installSetRefReducer(gcx: GhjkCtx, pcx: PortsCtx) { + return async (provisions: InstallSetRefProvision[]) => { + if (provisions.length > 1) { + throw new Error( + 'only one "ghjkPorts" provision per environment is supported', + ); + } + const { setId } = unwrapParseRes( + validators.installSetRefProvision.safeParse(provisions[0]), + {}, + "error parsing env provision", + ); + const installGraph = pcx.installGraphs.get(setId); + if (!installGraph) { + throw new Error( + `provisioned install set under id "${setId}" not found`, + ); + } + await using scx = await syncCtxFromGhjk(gcx); + const installArts = await installFromGraph(scx, installGraph); + + const out = await reduceInstArts(installGraph, installArts); + return out; + }; +} + +async function reduceInstArts( + installGraph: InstallGraph, + installArts: Map, +) { + const out: WellKnownProvision[] = []; + + // use this to track seen env vars to report conflicts + const foundEnvVars: Record = {}; + // FIXME: detect shim conflicts + // FIXME: better support for multi installs + await Promise.all(installGraph.user.map(async (instId) => { + const { binPaths, libPaths, includePaths, installPath, env } = installArts + .get( + instId, + )!; + + for (const [key, val] of Object.entries(env)) { + const conflict = foundEnvVars[key]; + if (conflict) { + throw new Error( + `duplicate env var found ${key} from sources ${instId} & ${ + conflict[1] + }`, + { + cause: { + a: [instId, val], + b: conflict, + }, + }, + ); + } + foundEnvVars[key] = [val, instId]; + out.push({ + ty: "envVar", + key, + val, + }); + } + const expandCurry = (path: string) => + expandGlobsAndAbsolutize(path, installPath); + + const [binPathsNorm, libPathsNorm, includePathsNorm] = await Promise + .all( + [ + Promise.all(binPaths.map(expandCurry)), + Promise.all(libPaths.map(expandCurry)), + Promise.all(includePaths.map(expandCurry)), + ], + ); + out.push( + ...binPathsNorm.flatMap((paths) => + paths.map((absolutePath) => ({ + ty: "posixExec" as const, + absolutePath, + })) + ), + ...libPathsNorm.flatMap((paths) => + paths.map((absolutePath) => ({ + ty: "posixSharedLib" as const, + absolutePath, + })) + ), + ...includePathsNorm.flatMap((paths) => + paths.map((absolutePath) => ({ + ty: "headerFile" as const, + absolutePath, + })) + ), + ); + })); + + return out; +} diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 8bfc1ebe..ea62d373 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -9,10 +9,10 @@ import type { InstallArtifacts, InstallConfigLiteX, InstallConfigResolvedX, + InstallSetX, PortArgsBase, PortDep, PortManifestX, - PortsModuleConfigX, } from "./types.ts"; import { DenoWorkerPort } from "./worker.ts"; import { AmbientAccessPort } from "./ambient.ts"; @@ -380,7 +380,7 @@ export type InstallGraph = DePromisify>; // required for installation including the dependency graph export async function buildInstallGraph( scx: SyncCtx, - portsConfig: PortsModuleConfigX, + set: InstallSetX, ) { type GraphInstConf = { instId: string; @@ -430,7 +430,7 @@ export async function buildInstallGraph( const foundInstalls: GraphInstConf[] = []; // collect the user specified insts first - for (const inst of portsConfig.installs) { + for (const inst of set.installs) { const { port: manifest, ...instLiteBase } = inst; const portRef = addPort(manifest); const instLite = validators.installConfigLite.parse({ @@ -439,7 +439,7 @@ export async function buildInstallGraph( }); const resolvedConfig = await resolveConfig( scx, - portsConfig, + set, manifest, instLite, ); @@ -487,7 +487,7 @@ export async function buildInstallGraph( // this goes into graph.depEdges const deps: [string, string][] = []; for (const depId of manifest.deps) { - const { manifest: depPort } = portsConfig.allowedDeps[depId.name]; + const { manifest: depPort } = set.allowedDeps[depId.name]; if (!depPort) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, @@ -562,7 +562,7 @@ export async function buildInstallGraph( // It also resolves any dependencies that the config specifies async function resolveConfig( scx: SyncCtx, - portsConfig: PortsModuleConfigX, + set: InstallSetX, manifest: PortManifestX, config: InstallConfigLiteX, ) { @@ -579,7 +579,7 @@ async function resolveConfig( const resolvedResolutionDeps = [] as [string, string][]; for (const dep of manifest.resolutionDeps ?? []) { const { manifest: depMan, config: depConf } = getDepConfig( - portsConfig, + set, manifest, config, dep, @@ -589,7 +589,7 @@ async function resolveConfig( // get the version resolved config of the dependency const depInstId = await resolveAndInstall( scx, - portsConfig, + set, depMan, depConf, ); @@ -642,7 +642,7 @@ async function resolveConfig( const resolveDepConfigs = {} as Record; for (const dep of manifest.deps ?? []) { const { manifest: depMan, config: depConf } = getDepConfig( - portsConfig, + set, manifest, config, dep, @@ -650,7 +650,7 @@ async function resolveConfig( // get the version resolved config of the dependency const depInstall = await resolveConfig( scx, - portsConfig, + set, depMan, depConf, ); @@ -670,14 +670,14 @@ async function resolveConfig( // for the portsConfig.allowedDeps // No version resolution takes place function getDepConfig( - portsConfig: PortsModuleConfigX, + set: InstallSetX, manifest: PortManifestX, config: InstallConfigLiteX, depId: PortDep, resolutionDep = false, ) { const { manifest: depPort, defaultInst: defaultDepInstall } = - portsConfig.allowedDeps[depId.name]; + set.allowedDeps[depId.name]; if (!depPort) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, @@ -715,11 +715,11 @@ function getDepConfig( /// Consider introducing a memoization scheme async function resolveAndInstall( scx: SyncCtx, - portsConfig: PortsModuleConfigX, + set: InstallSetX, manifest: PortManifestX, configLite: InstallConfigLiteX, ) { - const config = await resolveConfig(scx, portsConfig, manifest, configLite); + const config = await resolveConfig(scx, set, manifest, configLite); const installId = await getInstallHash(config); const cached = await scx.db.val.get(installId); @@ -739,11 +739,11 @@ async function resolveAndInstall( await Promise.all( manifest.deps?.map( async (dep) => { - const depConfig = getDepConfig(portsConfig, manifest, config, dep); + const depConfig = getDepConfig(set, manifest, config, dep); // we not only resolve but install the dep here const { installId } = await resolveAndInstall( scx, - portsConfig, + set, depConfig.manifest, depConfig.config, ); diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 5f09c706..f8a52bc1 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -145,10 +145,10 @@ const allowedPortDep = zod.object({ }); const portsModuleSecureConfig = zod.object({ - allowedPortDeps: zod.array(allowedPortDep).nullish(), + masterPortDepAllowList: zod.array(allowedPortDep).nullish(), }); -const portsModuleConfigHashed = zod.object({ +const installSetHashed = zod.object({ installs: zod.array(zod.string()), allowedDeps: zod.record( zod.string(), @@ -156,7 +156,7 @@ const portsModuleConfigHashed = zod.object({ ), }); -const portsModuleConfig = zod.object({ +const installSet = zod.object({ installs: zod.array(installConfigFat), allowedDeps: zod.record( zod.string(), @@ -164,9 +164,39 @@ const portsModuleConfig = zod.object({ ), }); -const installEnvProvision = zod.object({ - ty: zod.literal("ghjkInstall"), - confHash: zod.string(), +const portsModuleConfigHashed = zod.object({ + sets: zod.record(zod.string(), installSetHashed), +}); + +const portsModuleConfig = zod.object({ + sets: zod.record(zod.string(), installSet), +}); + +export const installSetProvisionTy = "ghjkPortsInstallSet"; +const installSetProvision = zod.object({ + ty: zod.literal(installSetProvisionTy), + set: installSet, +}); + +export const installSetRefProvisionTy = "ghjkPortsInstallSetRef"; +const installSetRefProvision = zod.object({ + ty: zod.literal(installSetRefProvisionTy), + setId: zod.string(), +}); + +const downloadArtifacts = zod.object({ + installVersion: zod.string(), + downloadPath: zod.string(), +}); + +const installArtifacts = zod.object({ + env: zod.record(zod.string(), zod.string()), + installVersion: zod.string(), + binPaths: zod.string().array(), + libPaths: zod.string().array(), + includePaths: zod.string().array(), + installPath: zod.string(), + downloadPath: zod.string(), }); const validators = { @@ -193,8 +223,13 @@ const validators = { portsModuleConfig, portsModuleConfigHashed, allowedPortDep, - installEnvProvision, + installSetProvision, + installSetRefProvision, + installSet, + installSetHashed, string: zod.string(), + downloadArtifacts, + installArtifacts, stringArray: zod.string().min(1).array(), }; export default validators; @@ -261,8 +296,24 @@ export type InstallConfigResolvedX = zod.infer< typeof validators.installConfigResolved >; -export type InstallEnvProvision = zod.infer< - typeof validators.installEnvProvision +/* + * Provisions an [`InstallSet`]. + */ +export type InstallSetProvision = zod.input< + typeof validators.installSetProvision +>; +export type InstallSetProvisionX = zod.infer< + typeof validators.installSetProvision +>; + +/* + * Provisions an [`InstallSet`] that's been pre-defined in the [`PortsModuleConfigX`]. + */ +export type InstallSetRefProvision = zod.input< + typeof validators.installSetRefProvision +>; +export type InstallSetRefProvisionX = zod.infer< + typeof validators.installSetRefProvision >; export type AllowedPortDep = zod.input; @@ -278,6 +329,16 @@ export type PortsModuleSecureConfigX = zod.input< typeof validators.portsModuleSecureConfig >; +export type InstallSet = zod.input; +export type InstallSetX = zod.infer< + typeof validators.installSet +>; + +export type InstallSetHashed = zod.input; +export type InstallSetHashedX = zod.infer< + typeof validators.installSetHashed +>; + export type PortsModuleConfig = zod.input; export type PortsModuleConfigX = zod.infer< typeof validators.portsModuleConfig @@ -350,17 +411,5 @@ export interface InstallArgs extends PortArgsBase { tmpDirPath: string; } -export type DownloadArtifacts = { - installVersion: string; - downloadPath: string; -}; - -export type InstallArtifacts = { - env: Record; - installVersion: string; - binPaths: string[]; - libPaths: string[]; - includePaths: string[]; - installPath: string; - downloadPath: string; -}; +export type DownloadArtifacts = zod.infer; +export type InstallArtifacts = zod.infer; diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index a4d219d1..8a828804 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -21,10 +21,8 @@ function initWorker() { export type DriverRequests = { ty: "exec"; - name: string; uri: string; - args: string[]; - envVars: Record; + args: ExecTaskArgs; }; export type DriverResponse = { @@ -32,6 +30,13 @@ export type DriverResponse = { payload: boolean; }; +export type ExecTaskArgs = { + name: string; + argv: string[]; + workingDir: string; + envVars: Record; +}; + async function onMsg(msg: MessageEvent) { const req = msg.data; if (!req.ty) { @@ -42,7 +47,7 @@ async function onMsg(msg: MessageEvent) { if (req.ty == "exec") { res = { ty: req.ty, - payload: await importAndExec(req.uri, req.name, req.args, req.envVars), + payload: await importAndExec(req.uri, req.args), }; } else { logger().error(`invalid DriverRequest type`, req); @@ -53,12 +58,10 @@ async function onMsg(msg: MessageEvent) { async function importAndExec( uri: string, - name: string, - args: string[], - envVars: Record, + args: ExecTaskArgs, ) { const mod = await import(uri); - await mod.ghjk.execTask(name, args, envVars); + await mod.ghjk.execTask(args); return true; } @@ -104,16 +107,12 @@ async function rpc(moduleUri: string, req: DriverRequests) { export async function execTaskDeno( configUri: string, - name: string, - args: string[], - envVars: Record, + args: ExecTaskArgs, ) { const resp = await rpc(configUri, { ty: "exec", uri: configUri, - name, args, - envVars, }); if (resp.ty != "exec") { throw new Error(`invalid response type: ${resp.ty}`); diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index 1042429c..1a31f1f5 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -1,36 +1,17 @@ import { std_path } from "../../deps/cli.ts"; import { $, DePromisify } from "../../utils/mod.ts"; -import type { TaskDefX, TasksModuleConfigX } from "./types.ts"; +import type { TaskDefHashedX, TasksModuleConfigX } from "./types.ts"; import type { GhjkCtx } from "../types.ts"; import logger from "../../utils/logger.ts"; import { execTaskDeno } from "./deno.ts"; -import { - buildInstallGraph, - installFromGraphAndShimEnv, - syncCtxFromGhjk, -} from "../ports/sync.ts"; - -export type ExecCtx = DePromisify>; - -export async function execCtxFromGhjk( - gcx: GhjkCtx, -) { - const syncCx = await syncCtxFromGhjk(gcx); - return { - ghjkCx: gcx, - syncCx, - async [Symbol.asyncDispose]() { - await syncCx![Symbol.asyncDispose](); - }, - }; -} +import { cookUnixEnv, reduceStrangeProvisions } from "../envs/posix.ts"; export type TaskGraph = DePromisify>; -export async function buildTaskGraph( - ecx: ExecCtx, +export function buildTaskGraph( + _gcx: GhjkCtx, portsConfig: TasksModuleConfigX, // env: Blackboard, ) { @@ -40,32 +21,21 @@ export async function buildTaskGraph( revDepEdges: {} as Record, // edges from dependent to dependency depEdges: {} as Record, - // the install graphs for the ports declared by the tasks - portInstallGraphs: Object.fromEntries( - await Promise.all( - Object.entries(portsConfig.tasks) - .map(async ([name, task]) => [ - name, - await buildInstallGraph( - ecx.syncCx, - { - installs: task.env.installs, - allowedDeps: task.env.allowedPortDeps, - }, - ), - ]), - ), - ), }; for (const [name, task] of Object.entries(portsConfig.tasks)) { - if (task.dependsOn.length == 0) { + if (portsConfig.envs[task.envHash]) { + throw new Error( + `unable to find env referenced by task "${name}" under hash "${task.envHash}"`, + ); + } + if (!task.dependsOn || task.dependsOn.length == 0) { graph.indie.push(name); } else { for (const depTaskName of task.dependsOn) { const testCycle = ( name: string, depName: string, - ): TaskDefX | undefined => { + ): TaskDefHashedX | undefined => { const depTask = portsConfig.tasks[depName]; if (!depTask) { throw new Error(`specified dependency task doesn't exist`, { @@ -109,7 +79,7 @@ export async function buildTaskGraph( } export async function execTask( - ecx: ExecCtx, + gcx: GhjkCtx, tasksConfig: TasksModuleConfigX, taskGraph: TaskGraph, targetName: string, @@ -123,8 +93,8 @@ export async function execTask( while (stack.length > 0) { const taskName = stack.pop()!; const taskDef = tasksConfig.tasks[taskName]; - stack.push(...taskDef.dependsOn); - workSet = new Set([...workSet.keys(), ...taskDef.dependsOn]); + stack.push(...taskDef.dependsOn ?? []); + workSet = new Set([...workSet.keys(), ...taskDef.dependsOn ?? []]); } } const pendingDepEdges = new Map( @@ -136,35 +106,36 @@ export async function execTask( } while (pendingTasks.length > 0) { const taskName = pendingTasks.pop()!; - const taskEnv = tasksConfig.tasks[taskName]; + const taskDef = tasksConfig.tasks[taskName]; - const installGraph = taskGraph.portInstallGraphs[taskName]; const taskEnvDir = await Deno.makeTempDir({ prefix: `ghjkTaskEnv_${taskName}_`, }); - const { env: installEnvs } = await installFromGraphAndShimEnv( - ecx.syncCx, - taskEnvDir, - installGraph, + const reducedEnv = await reduceStrangeProvisions( + gcx, + tasksConfig.envs[taskDef.envHash], ); + const { env: installEnvs } = await cookUnixEnv(reducedEnv, taskEnvDir); logger().info("executing", taskName, args); await execTaskDeno( - std_path.toFileUrl(ecx.ghjkCx.ghjkfilePath).href, - taskName, - args, + std_path.toFileUrl(gcx.ghjkfilePath).href, { - ...Deno.env.toObject(), - ...Object.fromEntries( - Object.entries(installEnvs).map( - ( - [key, val], - ) => [ - key, - key.match(/PATH/i) ? `${val}:${Deno.env.get(key) ?? ""}` : val, - ], + name: taskName, + argv: args, + envVars: { + ...Deno.env.toObject(), + ...Object.fromEntries( + Object.entries(installEnvs).map( + ( + [key, val], + ) => [ + key, + key.match(/PATH/i) ? `${val}:${Deno.env.get(key) ?? ""}` : val, + ], + ), ), - ), - ...taskEnv.env.env, + }, + workingDir: std_path.dirname(gcx.ghjkfilePath), }, ); $.removeIfExists(taskEnvDir); diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index b8c464c7..e895f148 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -1,20 +1,14 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { Json } from "../../utils/mod.ts"; +import { Json, unwrapParseRes } from "../../utils/mod.ts"; import validators from "./types.ts"; -import portValidators from "../ports/types.ts"; import type { TasksModuleConfigX } from "./types.ts"; import type { GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; -import { - buildTaskGraph, - execCtxFromGhjk, - execTask, - type TaskGraph, -} from "./exec.ts"; +import { buildTaskGraph, execTask, type TaskGraph } from "./exec.ts"; import { Blackboard } from "../../host/types.ts"; export type TasksCtx = { @@ -27,57 +21,25 @@ const lockValidator = zod.object({ type TasksLockEnt = zod.infer; export class TasksModule extends ModuleBase { - async processManifest( - ctx: GhjkCtx, + processManifest( + gcx: GhjkCtx, manifest: ModuleManifest, bb: Blackboard, _lockEnt: TasksLockEnt | undefined, ) { - function unwrapParseRes(res: zod.SafeParseReturnType) { - if (!res.success) { - throw new Error("error parsing module config", { - cause: { - zodErr: res.error, - id: manifest.id, - config: manifest.config, - bb, - }, - }); - } - return res.data; + function unwrapParseCurry(res: zod.SafeParseReturnType) { + return unwrapParseRes(res, { + id: manifest.id, + config: manifest.config, + bb, + }, "error parsing module config"); } - const hashed = unwrapParseRes( - validators.tasksModuleConfigHashed.safeParse(manifest.config), + + const config = unwrapParseCurry( + validators.tasksModuleConfig.safeParse(manifest.config), ); - const config: TasksModuleConfigX = { - tasks: Object.fromEntries( - Object.entries(hashed.tasks).map( - ([name, task]) => [name, { - ...task, - env: { - ...task.env, - installs: task.env.installs.map((hash) => - unwrapParseRes( - portValidators.installConfigFat.safeParse(bb[hash]), - ) - ), - allowedPortDeps: Object.fromEntries( - Object.entries(task.env.allowedPortDeps) - .map(([key, hash]) => [ - key, - unwrapParseRes( - portValidators.allowedPortDep.safeParse(bb[hash]), - ), - ]), - ), - }, - }], - ), - ), - }; - await using execCx = await execCtxFromGhjk(ctx); - const taskGraph = await buildTaskGraph(execCx, config); + const taskGraph = buildTaskGraph(gcx, config); return { config, taskGraph, @@ -94,9 +56,8 @@ export class TasksModule extends ModuleBase { .name(name) .useRawArgs() .action(async (_, ...args) => { - await using execCx = await execCtxFromGhjk(gcx); await execTask( - execCx, + gcx, tcx.config, tcx.taskGraph, name, diff --git a/modules/tasks/types.ts b/modules/tasks/types.ts index 403af989..914e8bc6 100644 --- a/modules/tasks/types.ts +++ b/modules/tasks/types.ts @@ -1,65 +1,37 @@ //! NOTE: type FooX is a version of Foo after zod processing/transformation import { zod } from "../../deps/common.ts"; -import portsValidator from "../ports/types.ts"; +import envsValidators from "../envs/types.ts"; const taskName = zod.string().regex(/[^\s]/); -const taskEnvBase = zod.object({ - env: zod.record(zod.string(), zod.string()), -}); - -const taskEnvHashed = taskEnvBase.merge(zod.object({ - installs: zod.string().array(), - allowedPortDeps: zod.record(zod.string(), zod.string()), -})); - -const taskEnv = taskEnvBase.merge(zod.object({ - installs: portsValidator.installConfigFat.array(), - allowedPortDeps: zod.record( - zod.string(), - portsValidator.allowedPortDep, - ), -})); - const taskDefBase = zod.object({ name: zod.string(), - dependsOn: taskName.array(), + dependsOn: taskName.array().nullish(), desc: zod.string().nullish(), + workingDir: zod.string().nullish(), }); const taskDef = taskDefBase.merge(zod.object({ - env: taskEnv, + env: envsValidators.envRecipe, })); const taskDefHashed = taskDefBase.merge(zod.object({ - env: taskEnvHashed, + envHash: zod.string(), })); const tasksModuleConfig = zod.object({ - tasks: zod.record(taskName, taskDef), -}); - -const tasksModuleConfigHashed = zod.object({ + envs: zod.record(zod.string(), envsValidators.envRecipe), tasks: zod.record(taskName, taskDefHashed), }); const validators = { - taskEnv, - taskEnvHashed, taskDef, taskDefHashed, tasksModuleConfig, - tasksModuleConfigHashed, }; export default validators; -export type TaskEnv = zod.input; -export type TaskEnvX = zod.infer; - -export type TaskEnvHashed = zod.input; -export type TaskEnvHashedX = zod.infer; - export type TaskDef = zod.input; export type TaskDefX = zod.infer; @@ -68,10 +40,3 @@ export type TaskDefHashedX = zod.infer; export type TasksModuleConfig = zod.input; export type TasksModuleConfigX = zod.infer; - -export type TasksModuleConfigHashed = zod.input< - typeof validators.tasksModuleConfigHashed ->; -export type TasksModuleConfigHashedX = zod.infer< - typeof tasksModuleConfigHashed ->; diff --git a/tests/ports.ts b/tests/ports.ts index 34489a9c..d893483b 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -100,7 +100,7 @@ const cases: CustomE2eTestCase[] = [ installConf: ports.npmi({ packageName: "node-gyp" }), ePoint: `node-gyp --version`, secureConf: secureConfig({ - allowedPortDeps: stdDeps({ enableRuntimes: true }), + masterPortDepAllowList: stdDeps({ enableRuntimes: true }), }), }, // node + more megs @@ -109,7 +109,7 @@ const cases: CustomE2eTestCase[] = [ installConf: ports.npmi({ packageName: "@bytecodealliance/jco" }), ePoint: `jco --version`, secureConf: secureConfig({ - allowedPortDeps: stdDeps({ enableRuntimes: true }), + masterPortDepAllowList: stdDeps({ enableRuntimes: true }), }), }, // 42 megs @@ -161,7 +161,7 @@ const cases: CustomE2eTestCase[] = [ installConf: ports.pipi({ packageName: "poetry" }), ePoint: `poetry --version`, secureConf: secureConfig({ - allowedPortDeps: stdDeps({ enableRuntimes: true }), + masterPortDepAllowList: stdDeps({ enableRuntimes: true }), }), }, // rustup + 600 megs diff --git a/tests/tasks.ts b/tests/tasks.ts index d77941c7..20f6a487 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -3,7 +3,7 @@ import { dockerE2eTest, E2eTestCase, localE2eTest, - type TaskDefTest, + type TaskDefArgs, tsGhjkFileFromInstalls, } from "./utils.ts"; import * as ghjk from "../mod.ts"; @@ -12,7 +12,7 @@ import * as ports from "../ports/mod.ts"; type CustomE2eTestCase = Omit & { ePoint: string; stdin: string; - tasks: TaskDefTest[]; + tasks: TaskDefArgs[]; }; const cases: CustomE2eTestCase[] = [ { @@ -32,7 +32,7 @@ test (ghjk x greet world) = 'Hello world!'`, name: "env_vars", tasks: [{ name: "greet", - env: { + envVars: { NAME: "moon", }, fn: async ({ $ }) => { diff --git a/tests/utils.ts b/tests/utils.ts index 7f08d123..2c7985ff 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -5,7 +5,8 @@ import type { InstallConfigFat, PortsModuleSecureConfig, } from "../modules/ports/types.ts"; -import type { TaskDefNice } from "../mod.ts"; +import type { TaskDefArgs } from "../mod.ts"; +export type { TaskDefArgs } from "../mod.ts"; export type E2eTestCase = { name: string; @@ -150,12 +151,11 @@ export async function localE2eTest(testCase: E2eTestCase) { await tmpDir.remove({ recursive: true }); } -export type TaskDefTest = TaskDefNice & { name: string }; export function tsGhjkFileFromInstalls( { installConf, secureConf, taskDefs }: { installConf: InstallConfigFat | InstallConfigFat[]; secureConf?: PortsModuleSecureConfig; - taskDefs: TaskDefTest[]; + taskDefs: TaskDefArgs[]; }, ) { const installConfArray = Array.isArray(installConf) diff --git a/utils/mod.ts b/utils/mod.ts index c4a70c8e..233029f6 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -478,3 +478,29 @@ const _b = match("hey", { hi: 3 as const, holla: 4, }); + +export async function expandGlobsAndAbsolutize(path: string, wd: string) { + if (std_path.isGlob(path)) { + const glob = std_path.isAbsolute(path) + ? path + : std_path.joinGlobs([wd, path], { extended: true }); + return (await Array.fromAsync(std_fs.expandGlob(glob))) + .map((entry) => std_path.resolve(wd, entry.path)); + } + return [std_path.resolve(wd, path)]; +} +export function unwrapParseRes( + res: zod.SafeParseReturnType, + cause: object = {}, + errMessage = "error parsing object", +) { + if (!res.success) { + throw new Error(errMessage, { + cause: { + zodErr: res.error, + ...cause, + }, + }); + } + return res.data; +} From efe769fca92c01188f19de89c6afc91d2113d901 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 31 Mar 2024 00:18:52 +0000 Subject: [PATCH 03/21] wip: minor fixup --- .ghjk/lock.json | 915 ++++++++++++++++++++--------------------- deno.lock | 11 - install.ts | 4 +- main.ts | 4 +- mod.ts | 366 +++++++++-------- modules/envs/types.ts | 5 +- modules/ports/mod.ts | 34 +- modules/ports/types.ts | 39 +- modules/std.ts | 6 +- modules/tasks/exec.ts | 8 +- ports/asdf.ts | 14 + 11 files changed, 714 insertions(+), 692 deletions(-) diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 09ac08a0..3874fc10 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -6,18 +6,18 @@ "version": "0", "configResolutions": { "95dbc2b8c604a5996b88c5b1b4fb0c10b3e0d9cac68f57eb915b012c44288e93": { - "version": "v0.2.60", + "version": "v0.2.61", "depConfigs": {}, "portRef": "act_ghrel@0.1.0" }, "076a5b8ee3bdc68ebf20a696378458465042bb7dc1e49ac2dc98e5fa0dab3e25": { - "version": "3.6.2", + "version": "3.7.0", "depConfigs": { "cpy_bs_ghrel": { "version": "3.12.1", "depConfigs": { "tar_aa": { - "version": "1.35", + "version": "1.34", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -37,7 +37,7 @@ "version": "3.12.1", "depConfigs": { "tar_aa": { - "version": "1.35", + "version": "1.34", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -50,7 +50,7 @@ "portRef": "cpy_bs_ghrel@0.1.0" }, "9e3fa7742c431c34ae7ba8d1e907e50c937ccfb631fb4dcfb7a1773742abe267": { - "version": "1.35", + "version": "1.34", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -63,7 +63,7 @@ "version": "3.12.0", "depConfigs": { "tar_aa": { - "version": "1.35", + "version": "1.34", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -77,7 +77,7 @@ "releaseTag": "20231002" }, "a79698808eea53aedd8e83387b2f44e90a1a48d76193c5ccf0fc6efe29bd70f6": { - "version": "v25.3", + "version": "v26.1", "depConfigs": {}, "portRef": "protoc_ghrel@0.1.0" } @@ -85,6 +85,9 @@ }, "tasks": { "version": "0" + }, + "envs": { + "version": "0" } }, "config": { @@ -92,96 +95,74 @@ { "id": "ports", "config": { - "installs": [ - "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", - "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44", - "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" - ], - "allowedDeps": { - "tar_aa": "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2", - "git_aa": "9d26d0d90f6ecdd69d0705a042b01a344aa626ee", - "curl_aa": "3c447f912abf18883bd05314f946740975ee0dd3", - "unzip_aa": "dfb0f5e74666817e6ab8cbceca0c9da271142bca", - "zstd_aa": "d9122eff1fe3ef56872e53dae725ff3ccb37472e", - "rustup_rustlang": "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d", - "rust_rustup": "9fc8f32a0f79253defdb8845e2d6a4df69b526b9", - "cargo_binstall_ghrel": "45999e7561d7f6a661191f58ee35e67755d375e0", - "pnpm_ghrel": "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7", - "asdf_plugin_git": "a36b37f4eda81bf51a50d00362637690c7fea473", - "node_org": "5843605c861f0b7307c0192a1628c3823fe28ed9", - "cpy_bs_ghrel": "7a33163826283c47b52964a23b87a4762662c746" + "sets": { + "ghjkEnvProvInstSet___main": { + "installs": [ + "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", + "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44", + "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" + ], + "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" + }, + "ghjkTaskInstSet___ha": { + "installs": [ + "f48ddfcfec810fcfcfc155fef7281a8c139c26fa" + ], + "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" + } } } }, { "id": "tasks", "config": { + "envs": { + "287dbbf1428b17704321d061b7b99f6906906546": { + "provides": [] + }, + "5b8568ad275f743bca20b3988d17dc60cccc5dc6": { + "provides": [ + { + "ty": "ghjkPortsInstallSetRef", + "setId": "ghjkTaskInstSet___ha" + }, + { + "ty": "envVar", + "key": "STUFF", + "val": "stuffier" + } + ] + } + }, "tasks": { "greet": { "name": "greet", - "dependsOn": [], - "env": { - "installs": [], - "env": {}, - "allowedPortDeps": [] - } + "envHash": "287dbbf1428b17704321d061b7b99f6906906546" }, "ha": { "name": "ha", - "dependsOn": [], - "env": { - "installs": [ - "f48ddfcfec810fcfcfc155fef7281a8c139c26fa" - ], - "env": { - "STUFF": "stuffier" - }, - "allowedPortDeps": [ - "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2", - "9d26d0d90f6ecdd69d0705a042b01a344aa626ee", - "3c447f912abf18883bd05314f946740975ee0dd3", - "dfb0f5e74666817e6ab8cbceca0c9da271142bca", - "d9122eff1fe3ef56872e53dae725ff3ccb37472e", - "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d", - "9fc8f32a0f79253defdb8845e2d6a4df69b526b9", - "45999e7561d7f6a661191f58ee35e67755d375e0", - "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7", - "a36b37f4eda81bf51a50d00362637690c7fea473" - ] - } + "envHash": "5b8568ad275f743bca20b3988d17dc60cccc5dc6" }, "ho": { "name": "ho", "dependsOn": [ "ha" ], - "env": { - "installs": [], - "env": {}, - "allowedPortDeps": [] - } + "envHash": "287dbbf1428b17704321d061b7b99f6906906546" }, "hum": { "name": "hum", "dependsOn": [ "ho" ], - "env": { - "installs": [], - "env": {}, - "allowedPortDeps": [] - } + "envHash": "287dbbf1428b17704321d061b7b99f6906906546" }, "hii": { "name": "hii", "dependsOn": [ "hum" ], - "env": { - "installs": [], - "env": {}, - "allowedPortDeps": [] - } + "envHash": "287dbbf1428b17704321d061b7b99f6906906546" }, "hey": { "name": "hey", @@ -189,421 +170,433 @@ "hii", "ho" ], - "env": { - "installs": [], - "env": {}, - "allowedPortDeps": [] - } + "envHash": "287dbbf1428b17704321d061b7b99f6906906546" } } } + }, + { + "id": "envs", + "config": { + "envs": {} + } } ], - "globalEnv": { - "installs": { - "f48ddfcfec810fcfcfc155fef7281a8c139c26fa": { - "port": { - "ty": "denoWorker@v1", - "name": "protoc_ghrel", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ], - "version": "0.1.0", - "moduleSpecifier": "file:///data/home/ghjk/ports/protoc.ts" - } + "blackboard": { + "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f": { + "port": { + "ty": "denoWorker@v1", + "name": "act_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "moduleSpecifier": "file:///ports/act.ts" + } + }, + "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44": { + "port": { + "ty": "denoWorker@v1", + "name": "pipi_pypi", + "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", + "deps": [ + { + "name": "cpy_bs_ghrel" + } + ], + "moduleSpecifier": "file:///ports/pipi.ts" }, - "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f": { - "port": { - "ty": "denoWorker@v1", - "name": "act_ghrel", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" - ], - "version": "0.1.0", - "moduleSpecifier": "file:///ports/act.ts" - } + "packageName": "pre-commit" + }, + "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c": { + "port": { + "ty": "denoWorker@v1", + "name": "cpy_bs_ghrel", + "platforms": [ + "x86_64-linux", + "aarch64-linux", + "x86_64-darwin", + "aarch64-darwin", + "x86_64-windows", + "aarch64-windows" + ], + "version": "0.1.0", + "deps": [ + { + "name": "tar_aa" + }, + { + "name": "zstd_aa" + } + ], + "moduleSpecifier": "file:///ports/cpy_bs.ts" }, - "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44": { - "port": { - "ty": "denoWorker@v1", - "name": "pipi_pypi", - "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", - "deps": [ - { - "name": "cpy_bs_ghrel" - } - ], - "moduleSpecifier": "file:///ports/pipi.ts" - }, - "packageName": "pre-commit" + "releaseTag": "20231002" + }, + "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2": { + "manifest": { + "ty": "ambientAccess@v1", + "name": "tar_aa", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin" + ], + "version": "0.1.0", + "execName": "tar", + "versionExtractFlag": "--version", + "versionExtractRegex": "(\\d+\\.\\d+)", + "versionExtractRegexFlags": "" }, - "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c": { - "port": { - "ty": "denoWorker@v1", - "name": "cpy_bs_ghrel", - "platforms": [ - "x86_64-linux", - "aarch64-linux", - "x86_64-darwin", - "aarch64-darwin", - "x86_64-windows", - "aarch64-windows" - ], - "version": "0.1.0", - "deps": [ - { - "name": "tar_aa" - }, - { - "name": "zstd_aa" - } - ], - "moduleSpecifier": "file:///ports/cpy_bs.ts" - }, - "releaseTag": "20231002" + "defaultInst": { + "portRef": "tar_aa@0.1.0" } }, - "allowedPortDeps": { - "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2": { - "manifest": { - "ty": "ambientAccess@v1", - "name": "tar_aa", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ], - "version": "0.1.0", - "execName": "tar", - "versionExtractFlag": "--version", - "versionExtractRegex": "(\\d+\\.\\d+)", - "versionExtractRegexFlags": "" - }, - "defaultInst": { - "portRef": "tar_aa@0.1.0" - } + "9d26d0d90f6ecdd69d0705a042b01a344aa626ee": { + "manifest": { + "ty": "ambientAccess@v1", + "name": "git_aa", + "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", + "execName": "git", + "versionExtractFlag": "--version", + "versionExtractRegex": "(\\d+\\.\\d+\\.\\d+)", + "versionExtractRegexFlags": "" }, - "9d26d0d90f6ecdd69d0705a042b01a344aa626ee": { - "manifest": { - "ty": "ambientAccess@v1", - "name": "git_aa", - "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", - "execName": "git", - "versionExtractFlag": "--version", - "versionExtractRegex": "(\\d+\\.\\d+\\.\\d+)", - "versionExtractRegexFlags": "" - }, - "defaultInst": { - "portRef": "git_aa@0.1.0" - } + "defaultInst": { + "portRef": "git_aa@0.1.0" + } + }, + "3c447f912abf18883bd05314f946740975ee0dd3": { + "manifest": { + "ty": "ambientAccess@v1", + "name": "curl_aa", + "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", + "execName": "curl", + "versionExtractFlag": "--version", + "versionExtractRegex": "(\\d+\\.\\d+\\.\\d+)", + "versionExtractRegexFlags": "" }, - "3c447f912abf18883bd05314f946740975ee0dd3": { - "manifest": { - "ty": "ambientAccess@v1", - "name": "curl_aa", - "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", - "execName": "curl", - "versionExtractFlag": "--version", - "versionExtractRegex": "(\\d+\\.\\d+\\.\\d+)", - "versionExtractRegexFlags": "" - }, - "defaultInst": { - "portRef": "curl_aa@0.1.0" - } + "defaultInst": { + "portRef": "curl_aa@0.1.0" + } + }, + "dfb0f5e74666817e6ab8cbceca0c9da271142bca": { + "manifest": { + "ty": "ambientAccess@v1", + "name": "unzip_aa", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "execName": "unzip", + "versionExtractFlag": "-v", + "versionExtractRegex": "(\\d+\\.\\d+)", + "versionExtractRegexFlags": "" }, - "dfb0f5e74666817e6ab8cbceca0c9da271142bca": { - "manifest": { - "ty": "ambientAccess@v1", - "name": "unzip_aa", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" - ], - "version": "0.1.0", - "execName": "unzip", - "versionExtractFlag": "-v", - "versionExtractRegex": "(\\d+\\.\\d+)", - "versionExtractRegexFlags": "" - }, - "defaultInst": { - "portRef": "unzip_aa@0.1.0" - } + "defaultInst": { + "portRef": "unzip_aa@0.1.0" + } + }, + "d9122eff1fe3ef56872e53dae725ff3ccb37472e": { + "manifest": { + "ty": "ambientAccess@v1", + "name": "zstd_aa", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin" + ], + "version": "0.1.0", + "execName": "zstd", + "versionExtractFlag": "--version", + "versionExtractRegex": "v(\\d+\\.\\d+\\.\\d+),", + "versionExtractRegexFlags": "" }, - "d9122eff1fe3ef56872e53dae725ff3ccb37472e": { - "manifest": { - "ty": "ambientAccess@v1", - "name": "zstd_aa", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ], - "version": "0.1.0", - "execName": "zstd", - "versionExtractFlag": "--version", - "versionExtractRegex": "v(\\d+\\.\\d+\\.\\d+),", - "versionExtractRegexFlags": "" - }, - "defaultInst": { - "portRef": "zstd_aa@0.1.0" - } + "defaultInst": { + "portRef": "zstd_aa@0.1.0" + } + }, + "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d": { + "manifest": { + "ty": "denoWorker@v1", + "name": "rustup_rustlang", + "platforms": [ + "x86_64-darwin", + "aarch64-darwin", + "x86_64-linux", + "aarch64-linux", + "x86_64-windows", + "x86_64-illumos", + "x86_64-freebsd", + "x86_64-netbsd" + ], + "version": "0.1.0", + "deps": [ + { + "name": "git_aa" + } + ], + "resolutionDeps": [ + { + "name": "git_aa" + } + ], + "moduleSpecifier": "file:///ports/rustup.ts" }, - "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d": { - "manifest": { - "ty": "denoWorker@v1", - "name": "rustup_rustlang", - "platforms": [ - "x86_64-darwin", - "aarch64-darwin", - "x86_64-linux", - "aarch64-linux", - "x86_64-windows", - "x86_64-illumos", - "x86_64-freebsd", - "x86_64-netbsd" - ], - "version": "0.1.0", - "deps": [ - { - "name": "git_aa" - } - ], - "resolutionDeps": [ - { - "name": "git_aa" - } - ], - "moduleSpecifier": "file:///ports/rustup.ts" - }, - "defaultInst": { - "portRef": "rustup_rustlang@0.1.0" - } + "defaultInst": { + "portRef": "rustup_rustlang@0.1.0" + } + }, + "9fc8f32a0f79253defdb8845e2d6a4df69b526b9": { + "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", + "deps": [ + { + "name": "rustup_rustlang" + } + ], + "moduleSpecifier": "file:///ports/rust.ts" }, - "9fc8f32a0f79253defdb8845e2d6a4df69b526b9": { - "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", - "deps": [ - { - "name": "rustup_rustlang" - } - ], - "moduleSpecifier": "file:///ports/rust.ts" - }, - "defaultInst": { - "portRef": "rust_rustup@0.1.0" - } + "defaultInst": { + "portRef": "rust_rustup@0.1.0" + } + }, + "45999e7561d7f6a661191f58ee35e67755d375e0": { + "manifest": { + "ty": "denoWorker@v1", + "name": "cargo_binstall_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin" + ], + "version": "0.1.0", + "moduleSpecifier": "file:///ports/cargo-binstall.ts" }, - "45999e7561d7f6a661191f58ee35e67755d375e0": { - "manifest": { - "ty": "denoWorker@v1", - "name": "cargo_binstall_ghrel", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ], - "version": "0.1.0", - "moduleSpecifier": "file:///ports/cargo-binstall.ts" - }, - "defaultInst": { - "portRef": "cargo_binstall_ghrel@0.1.0" - } + "defaultInst": { + "portRef": "cargo_binstall_ghrel@0.1.0" + } + }, + "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7": { + "manifest": { + "ty": "denoWorker@v1", + "name": "pnpm_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "moduleSpecifier": "file:///ports/pnpm.ts" }, - "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7": { - "manifest": { - "ty": "denoWorker@v1", - "name": "pnpm_ghrel", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" - ], - "version": "0.1.0", - "moduleSpecifier": "file:///ports/pnpm.ts" - }, - "defaultInst": { - "portRef": "pnpm_ghrel@0.1.0" - } + "defaultInst": { + "portRef": "pnpm_ghrel@0.1.0" + } + }, + "a36b37f4eda81bf51a50d00362637690c7fea473": { + "manifest": { + "ty": "denoWorker@v1", + "name": "asdf_plugin_git", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "deps": [ + { + "name": "git_aa" + } + ], + "resolutionDeps": [ + { + "name": "git_aa" + } + ], + "moduleSpecifier": "file:///ports/asdf_plugin_git.ts" }, - "a36b37f4eda81bf51a50d00362637690c7fea473": { - "manifest": { - "ty": "denoWorker@v1", - "name": "asdf_plugin_git", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" - ], - "version": "0.1.0", - "deps": [ - { - "name": "git_aa" - } - ], - "resolutionDeps": [ - { - "name": "git_aa" - } - ], - "moduleSpecifier": "file:///ports/asdf_plugin_git.ts" - }, - "defaultInst": { - "portRef": "asdf_plugin_git@0.1.0" - } + "defaultInst": { + "portRef": "asdf_plugin_git@0.1.0" + } + }, + "5843605c861f0b7307c0192a1628c3823fe28ed9": { + "manifest": { + "ty": "denoWorker@v1", + "name": "node_org", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "deps": [ + { + "name": "tar_aa" + } + ], + "moduleSpecifier": "file:///ports/node.ts" }, - "5843605c861f0b7307c0192a1628c3823fe28ed9": { - "manifest": { - "ty": "denoWorker@v1", - "name": "node_org", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin", - "aarch64-windows", - "x86_64-windows" - ], - "version": "0.1.0", - "deps": [ - { - "name": "tar_aa" - } - ], - "moduleSpecifier": "file:///ports/node.ts" - }, - "defaultInst": { - "portRef": "node_org@0.1.0" - } + "defaultInst": { + "portRef": "node_org@0.1.0" + } + }, + "7a33163826283c47b52964a23b87a4762662c746": { + "manifest": { + "ty": "denoWorker@v1", + "name": "cpy_bs_ghrel", + "platforms": [ + "x86_64-linux", + "aarch64-linux", + "x86_64-darwin", + "aarch64-darwin", + "x86_64-windows", + "aarch64-windows" + ], + "version": "0.1.0", + "deps": [ + { + "name": "tar_aa" + }, + { + "name": "zstd_aa" + } + ], + "moduleSpecifier": "file:///ports/cpy_bs.ts" }, - "7a33163826283c47b52964a23b87a4762662c746": { - "manifest": { - "ty": "denoWorker@v1", - "name": "cpy_bs_ghrel", - "platforms": [ - "x86_64-linux", - "aarch64-linux", - "x86_64-darwin", - "aarch64-darwin", - "x86_64-windows", - "aarch64-windows" - ], - "version": "0.1.0", - "deps": [ - { - "name": "tar_aa" - }, - { - "name": "zstd_aa" - } - ], - "moduleSpecifier": "file:///ports/cpy_bs.ts" - }, - "defaultInst": { - "portRef": "cpy_bs_ghrel@0.1.0" - } + "defaultInst": { + "portRef": "cpy_bs_ghrel@0.1.0" + } + }, + "48a429761f3837562b097b47afe07601ba4ffca5": { + "tar_aa": "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2", + "git_aa": "9d26d0d90f6ecdd69d0705a042b01a344aa626ee", + "curl_aa": "3c447f912abf18883bd05314f946740975ee0dd3", + "unzip_aa": "dfb0f5e74666817e6ab8cbceca0c9da271142bca", + "zstd_aa": "d9122eff1fe3ef56872e53dae725ff3ccb37472e", + "rustup_rustlang": "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d", + "rust_rustup": "9fc8f32a0f79253defdb8845e2d6a4df69b526b9", + "cargo_binstall_ghrel": "45999e7561d7f6a661191f58ee35e67755d375e0", + "pnpm_ghrel": "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7", + "asdf_plugin_git": "a36b37f4eda81bf51a50d00362637690c7fea473", + "node_org": "5843605c861f0b7307c0192a1628c3823fe28ed9", + "cpy_bs_ghrel": "7a33163826283c47b52964a23b87a4762662c746" + }, + "f48ddfcfec810fcfcfc155fef7281a8c139c26fa": { + "port": { + "ty": "denoWorker@v1", + "name": "protoc_ghrel", + "version": "0.1.0", + "moduleSpecifier": "file:///data/home/ghjk/ports/protoc.ts", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin" + ] } } } diff --git a/deno.lock b/deno.lock index 8e56fd10..b2acfc4a 100644 --- a/deno.lock +++ b/deno.lock @@ -1,16 +1,5 @@ { "version": "3", - "packages": { - "specifiers": { - "npm:@types/node": "npm:@types/node@18.16.19" - }, - "npm": { - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", - "dependencies": {} - } - } - }, "remote": { "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", diff --git a/install.ts b/install.ts index 330a2924..16db7830 100755 --- a/install.ts +++ b/install.ts @@ -34,6 +34,8 @@ if (import.meta.main) { }); } else { throw new Error( - "unexpected ctx: if you want to access the ghjk installer, import `install` from ./install/mod.ts", + `unexpected context: this module is an entrypoint. If you want to programmatically invoke the ghjk installer, import \`install\` from ${ + import.meta.resolve("./install/mod.ts") + }`, ); } diff --git a/main.ts b/main.ts index 6859e698..74b2c54d 100755 --- a/main.ts +++ b/main.ts @@ -22,6 +22,8 @@ if (import.meta.main) { }); } else { throw new Error( - "unexpected ctx: if you want to run the ghjk cli, import `main` from ./host/mod.ts", + `unexpected context: this module is an entrypoint. If you want to programmatically invoke the ghjk cli, import \`main\` from ${ + import.meta.resolve("./host/mod.ts") + }`, ); } diff --git a/mod.ts b/mod.ts index a833d929..a1a045b3 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,9 @@ //! This module is intended to be re-exported by `ghjk.ts` config scripts. Please //! avoid importing elsewhere at it has side-effects. +// NOTE: avoid adding sources of randomness +// here to make the resulting config reasonably stable +// across serializaiton. No random identifiers. // TODO: harden most of the items in here import "./setup_logger.ts"; @@ -10,7 +13,7 @@ import portsValidators from "./modules/ports/types.ts"; import type { AllowedPortDep, InstallConfigFat, - InstallSetHashed, + InstallSet, InstallSetRefProvision, PortsModuleConfigHashed, PortsModuleSecureConfig, @@ -80,14 +83,11 @@ export type TaskDefArgs = { }; class GhjkfileBuilder { - #installSets = new Map(); - #tasks = {} as Record< + #installSets = new Map< string, - Omit & { - installs: string[]; - allowedPortDeps: Record; - } - >; + InstallSet + >(); + #tasks = {} as Record; #bb = new Map(); #seenEnvs: Record = {}; @@ -104,7 +104,7 @@ class GhjkfileBuilder { ); const set = this.#getSet(setId); - set.installs.push(this.#registerInstall(config)); + set.installs.push(config); logger().debug("install added", config); } @@ -116,26 +116,13 @@ class GhjkfileBuilder { set.allowedDeps = Object.fromEntries( deps.map(( dep, - ) => [dep.manifest.name, this.#registerAllowedPortDep(dep)]), + ) => [dep.manifest.name, dep]), ); } addTask( args: TaskDefArgs, ) { - const allowedPortDeps = Object.fromEntries( - [ - ...(args.allowedPortDeps ?? (args.installs ? stdDeps() : [])), - ] - .map(( - dep, - ) => [dep.manifest.name, this.#registerAllowedPortDep(dep)]), - ); - - const installs = (args.installs ?? []).map((fat) => - this.#registerInstall(fat) - ); - // NOTE: we make sure the env base declared here exists // this call is necessary to make sure that a `task` can // be declared before the `env` but still depend on it. @@ -148,8 +135,6 @@ class GhjkfileBuilder { this.#tasks[args.name] = { ...args, name, - installs, - allowedPortDeps, }; return args.name; } @@ -191,153 +176,28 @@ class GhjkfileBuilder { toConfig(secureConfig: PortsModuleSecureConfig | undefined) { try { const envsConfig = this.#processEnvs(); - - const tasksConfig: TasksModuleConfig = { - envs: {}, - tasks: {}, - }; - for ( - const [name, args] of Object - .entries( - this.#tasks, - ) - ) { - const { workingDir, desc, dependsOn, envBase } = args; - const envBaseResolved = typeof envBase === "string" - ? envBase - : envBase - ? DEFAULT_ENV_NAME - : null; - - const envBaseRecipe = envBaseResolved - ? envsConfig.envs[envBaseResolved] - : null; - - const taskEnvRecipe: EnvRecipe = { - provides: [], - }; - - const taskInstallSet: InstallSetHashed = { - installs: args.installs, - allowedDeps: args.allowedPortDeps, - }; - - const mergedEnvVars = args.envVars ?? {}; - if (envBaseRecipe) { - for ( - const prov of envBaseRecipe - .provides as ( - | WellKnownProvision - | InstallSetRefProvision - )[] - ) { - if (prov.ty == "envVar") { - if (!mergedEnvVars[prov.key]) { - mergedEnvVars[prov.key] = prov.val; - } - } else if (prov.ty == "ghjkPortsInstallSetRef") { - const baseSet = this.#installSets.get(prov.setId)!; - const mergedInstallsSet = new Set([ - ...taskInstallSet.installs, - ...baseSet.installs, - ]); - taskInstallSet.installs = [...mergedInstallsSet.values()]; - for ( - const [key, val] of Object.entries(baseSet.allowedDeps) - ) { - // prefer the port dep config of the child over any - // similar deps in the base - if (!taskInstallSet.allowedDeps[key]) { - taskInstallSet.allowedDeps[key] = val; - } - } - } else { - taskEnvRecipe.provides.push(prov); - } - } - } - if (taskInstallSet.installs.length > 0) { - const setId = `${name}_${crypto.randomUUID()}`; - this.#installSets.set(setId, taskInstallSet); - const prov: InstallSetRefProvision = { - ty: "ghjkPortsInstallSetRef", - setId, - }; - taskEnvRecipe.provides.push(prov); - } - - taskEnvRecipe.provides.push( - ...Object.entries(mergedEnvVars).map(( - [key, val], - ) => { - const prov: WellKnownProvision = { ty: "envVar", key, val }; - return prov; - }), - ); - - const envHash = objectHash( - jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), - ); - tasksConfig.envs[envHash] = taskEnvRecipe; - - tasksConfig.tasks[name] = { - name, - workingDir: typeof workingDir == "object" - ? workingDir.toString() - : workingDir, - desc, - dependsOn, - envHash, - }; - } - for (const [name, { dependsOn }] of Object.entries(tasksConfig.tasks)) { - for (const depName of dependsOn ?? []) { - if (!tasksConfig.tasks[depName]) { - throw new Error( - `task "${name}" depend on non-existent task "${depName}"`, - ); - } - } - } - - const masterPortDepAllowList = Object.fromEntries([ - ...(secureConfig?.masterPortDepAllowList ?? stdDeps()) - .map((dep) => - [ - dep.manifest.name, - this.#registerAllowedPortDep( - portsValidators.allowedPortDep.parse(dep), - ), - ] as const - ), - ]); - - const fullPortsConfig: PortsModuleConfigHashed = { - sets: {}, - }; - for ( - const [setId, set] of this.#installSets.entries() - ) { - for (const [portName, _] of Object.entries(set.allowedDeps)) { - if (!masterPortDepAllowList[portName]) { - throw new Error( - `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, - ); - } - } - fullPortsConfig.sets[setId] = set; - } + const tasksConfig = this.#processTasks(envsConfig); + const portsConfig = this.#processInstalls( + secureConfig?.masterPortDepAllowList ?? stdDeps(), + ); const config: SerializedConfig = { modules: [{ id: std_modules.ports, - config: fullPortsConfig, + config: portsConfig, }, { id: std_modules.tasks, config: tasksConfig, + }, { + id: std_modules.envs, + config: envsConfig, }], blackboard: Object.fromEntries(this.#bb.entries()), }; + console.log(Deno.inspect(config, { + depth: 10, + colors: true, + })); return config; } catch (cause) { throw new Error(`error constructing config for serialization`, { cause }); @@ -353,20 +213,12 @@ class GhjkfileBuilder { return set; } - #registerInstall(config: InstallConfigFat) { + #addToBlackboard(inp: unknown) { // jsonHash.digest is async - const hash = objectHash(jsonHash.canonicalize(config as jsonHash.Tree)); + const hash = objectHash(jsonHash.canonicalize(inp as jsonHash.Tree)); if (!this.#bb.has(hash)) { - this.#bb.set(hash, config); - } - return hash; - } - - #registerAllowedPortDep(dep: AllowedPortDep) { - const hash = objectHash(jsonHash.canonicalize(dep as jsonHash.Tree)); - if (!this.#bb.has(hash)) { - this.#bb.set(hash, dep); + this.#bb.set(hash, inp); } return hash; } @@ -466,6 +318,172 @@ class GhjkfileBuilder { } return out; } + + #processTasks(envsConfig: EnvsModuleConfig) { + const out: TasksModuleConfig = { + envs: {}, + tasks: {}, + }; + for ( + const [name, args] of Object + .entries( + this.#tasks, + ) + ) { + const { workingDir, desc, dependsOn, envBase } = args; + const envBaseResolved = typeof envBase === "string" + ? envBase + : envBase + ? DEFAULT_ENV_NAME + : null; + + const envBaseRecipe = envBaseResolved + ? envsConfig.envs[envBaseResolved] + : null; + + const taskEnvRecipe: EnvRecipe = { + provides: [], + }; + + const taskInstallSet: InstallSet = { + installs: args.installs ?? [], + allowedDeps: Object.fromEntries( + (args.allowedPortDeps ?? []).map((dep) => [dep.manifest.name, dep]), + ), + }; + + const mergedEnvVars = args.envVars ?? {}; + if (envBaseRecipe) { + for ( + const prov of envBaseRecipe + .provides as ( + | WellKnownProvision + | InstallSetRefProvision + )[] + ) { + if (prov.ty == "envVar") { + if (!mergedEnvVars[prov.key]) { + mergedEnvVars[prov.key] = prov.val; + } + } else if (prov.ty == "ghjkPortsInstallSetRef") { + const baseSet = this.#installSets.get(prov.setId)!; + const mergedInstallsSet = new Set([ + ...taskInstallSet.installs, + ...baseSet.installs, + ]); + taskInstallSet.installs = [...mergedInstallsSet.values()]; + for ( + const [key, val] of Object.entries(baseSet.allowedDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the base + if (!taskInstallSet.allowedDeps[key]) { + taskInstallSet.allowedDeps[key] = val; + } + } + } else { + taskEnvRecipe.provides.push(prov); + } + } + } + if (taskInstallSet.installs.length > 0) { + const setId = `ghjkTaskInstSet___${name}`; + this.#installSets.set(setId, taskInstallSet); + const prov: InstallSetRefProvision = { + ty: "ghjkPortsInstallSetRef", + setId, + }; + taskEnvRecipe.provides.push(prov); + } + + taskEnvRecipe.provides.push( + ...Object.entries(mergedEnvVars).map(( + [key, val], + ) => { + const prov: WellKnownProvision = { ty: "envVar", key, val }; + return prov; + }), + ); + + const envHash = objectHash( + jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), + ); + out.envs[envHash] = taskEnvRecipe; + + out.tasks[name] = { + name, + workingDir: typeof workingDir == "object" + ? workingDir.toString() + : workingDir, + desc, + dependsOn, + envHash, + }; + } + for (const [name, { dependsOn }] of Object.entries(out.tasks)) { + for (const depName of dependsOn ?? []) { + if (!out.tasks[depName]) { + throw new Error( + `task "${name}" depend on non-existent task "${depName}"`, + ); + } + } + } + + return out; + } + + #processInstalls(masterAllowList: AllowedPortDep[]) { + const out: PortsModuleConfigHashed = { + sets: {}, + }; + /* const masterPortDepAllowList = Object.fromEntries([ + ...masterAllowList + .map((dep) => + [ + dep.manifest.name, + this.#registerAllowedPortDep( + portsValidators.allowedPortDep.parse(dep), + ), + ] as const + ), + ]); + */ + const masterPortDepAllowList = Object.fromEntries( + masterAllowList + .map((dep) => + [ + dep.manifest.name, + dep, + ] as const + ), + ); + for ( + const [setId, set] of this.#installSets.entries() + ) { + for (const [portName, _] of Object.entries(set.allowedDeps)) { + if (!masterPortDepAllowList[portName]) { + throw new Error( + `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, + ); + } + } + for (const [name, hash] of Object.entries(masterPortDepAllowList)) { + if (!set.allowedDeps[name]) { + set.allowedDeps[name] = hash; + } + } + out.sets[setId] = { + installs: set.installs.map((inst) => this.#addToBlackboard(inst)), + allowedDeps: this.#addToBlackboard(Object.fromEntries( + Object.entries(set.allowedDeps).map(( + [key, dep], + ) => [key, this.#addToBlackboard(dep)]), + )), + }; + } + return out; + } } type EnvFinalizer = () => { @@ -490,7 +508,7 @@ class EnvBuilder { public name: string, ) { this.#file = file; - this.#installSetId = `${name}_${crypto.randomUUID()}`; + this.#installSetId = `ghjkEnvProvInstSet___${name}`; setFinalizer(() => ({ name: this.name, installSetId: this.#installSetId, diff --git a/modules/envs/types.ts b/modules/envs/types.ts index 49fb6fa0..f0b347a3 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -70,6 +70,9 @@ export type WellKnownEnvRecipeX = zod.infer< typeof validators.wellKnownEnvRecipe >; +/* + * A function that batch convert strange provisions of a certain kind to well known ones. + */ export type ProvisionReducer

= ( - provision: P[], + provisions: P[], ) => Promise; diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 74b531f1..506556c8 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -70,20 +70,28 @@ export class PortsModule extends ModuleBase { // so we need to use `using` await using syncCx = await syncCtxFromGhjk(gcx); for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { - // install sets in the config use hash references to dedupe InstallConfigs - // reify the references from the blackboard from continuing - const set: InstallSetX = { - installs: hashedSet.installs.map((hash) => - unwrapParseCurry(validators.installConfigFat.safeParse(bb[hash])) - ), - allowedDeps: Object.fromEntries( - Object.entries(hashedSet.allowedDeps).map(( - [key, value], - ) => [ - key, - unwrapParseCurry(validators.allowedPortDep.safeParse(bb[value])), - ]), + // install sets in the config use hash references to dedupe InstallConfigs, + // AllowedDepSets and AllowedDeps + // reify the references from the blackboard before continuing + const installs = hashedSet.installs.map((hash) => + unwrapParseCurry(validators.installConfigFat.safeParse(bb[hash])) + ); + const allowedDepSetHashed = unwrapParseCurry( + validators.allowDepSetHashed.safeParse( + bb[hashedSet.allowedDeps], ), + ); + const allowedDeps = Object.fromEntries( + Object.entries(allowedDepSetHashed).map(( + [key, hash], + ) => [ + key, + unwrapParseCurry(validators.allowedPortDep.safeParse(bb[hash])), + ]), + ); + const set: InstallSetX = { + installs, + allowedDeps, }; pcx.config.sets[id] = set; pcx.installGraphs.set(id, await buildInstallGraph(syncCx, set)); diff --git a/modules/ports/types.ts b/modules/ports/types.ts index f8a52bc1..94942858 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -148,20 +148,18 @@ const portsModuleSecureConfig = zod.object({ masterPortDepAllowList: zod.array(allowedPortDep).nullish(), }); +const allowDepSet = zod.record(zod.string(), allowedPortDep); + +const allowDepSetHashed = zod.record(zod.string(), zod.string()); + const installSetHashed = zod.object({ installs: zod.array(zod.string()), - allowedDeps: zod.record( - zod.string(), - zod.string(), - ), + allowedDeps: zod.string(), }); const installSet = zod.object({ installs: zod.array(installConfigFat), - allowedDeps: zod.record( - zod.string(), - allowedPortDep, - ), + allowedDeps: allowDepSet, }); const portsModuleConfigHashed = zod.object({ @@ -223,6 +221,8 @@ const validators = { portsModuleConfig, portsModuleConfigHashed, allowedPortDep, + allowDepSet, + allowDepSetHashed, installSetProvision, installSetRefProvision, installSet, @@ -319,9 +319,11 @@ export type InstallSetRefProvisionX = zod.infer< export type AllowedPortDep = zod.input; export type AllowedPortDepX = zod.infer; -/// This is a secure sections of the config intended to be direct exports -/// from the config script instead of the global variable approach the -/// main [`GhjkConfig`] can take. +/* + * This is a secure sections of the config intended to be direct exports + * from the config script instead of the global variable approach the + * main [`GhjkConfig`] can take. + */ export type PortsModuleSecureConfig = zod.input< typeof validators.portsModuleSecureConfig >; @@ -351,21 +353,6 @@ export type PortsModuleConfigLiteHashedX = zod.infer< typeof validators.portsModuleConfigHashed >; -/* -interface ASDF_CONFIG_EXAMPLE { - ASDF_INSTALL_TYPE: "version" | "ref"; - ASDF_INSTALL_VERSION: string; // full version number or Git Ref depending on ASDF_INSTALL_TYPE - ASDF_INSTALL_PATH: string; // the path to where the tool should, or has been installed - ASDF_CONCURRENCY: number; // the number of cores to use when compiling the source code. Useful for setting make -j - ASDF_DOWNLOAD_PATH: string; // the path to where the source code or binary was downloaded to by bin/download - ASDF_PLUGIN_PATH: string; // the path the plugin was installed - ASDF_PLUGIN_SOURCE_URL: string; // the source URL of the plugin - ASDF_PLUGIN_PREV_REF: string; // prevous git-ref of the plugin repo - ASDF_PLUGIN_POST_REF: string; // updated git-ref of the plugin repo - ASDF_CMD_FILE: string; // resolves to the full path of the file being sourced -} -*/ - export type DepArt = { execs: Record; libs: Record; diff --git a/modules/std.ts b/modules/std.ts index 917fd610..31a2e73f 100644 --- a/modules/std.ts +++ b/modules/std.ts @@ -1,9 +1,10 @@ +import { EnvsModule } from "./envs/mod.ts"; import { PortsModule } from "./ports/mod.ts"; import { TasksModule } from "./tasks/mod.ts"; export const ports = "ports"; - export const tasks = "tasks"; +export const envs = "envs"; export const map = { [ports as string]: { @@ -12,4 +13,7 @@ export const map = { [tasks as string]: { ctor: TasksModule, }, + [envs as string]: { + ctor: EnvsModule, + }, }; diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index 1a31f1f5..ed503348 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -3,9 +3,11 @@ import { $, DePromisify } from "../../utils/mod.ts"; import type { TaskDefHashedX, TasksModuleConfigX } from "./types.ts"; import type { GhjkCtx } from "../types.ts"; -import logger from "../../utils/logger.ts"; +import getLogger from "../../utils/logger.ts"; import { execTaskDeno } from "./deno.ts"; +const logger = getLogger(import.meta); + import { cookUnixEnv, reduceStrangeProvisions } from "../envs/posix.ts"; export type TaskGraph = DePromisify>; @@ -23,7 +25,7 @@ export function buildTaskGraph( depEdges: {} as Record, }; for (const [name, task] of Object.entries(portsConfig.tasks)) { - if (portsConfig.envs[task.envHash]) { + if (!portsConfig.envs[task.envHash]) { throw new Error( `unable to find env referenced by task "${name}" under hash "${task.envHash}"`, ); @@ -116,7 +118,7 @@ export async function execTask( tasksConfig.envs[taskDef.envHash], ); const { env: installEnvs } = await cookUnixEnv(reducedEnv, taskEnvDir); - logger().info("executing", taskName, args); + logger.info("executing", taskName, args); await execTaskDeno( std_path.toFileUrl(gcx.ghjkfilePath).href, { diff --git a/ports/asdf.ts b/ports/asdf.ts index d2427db1..55c62755 100644 --- a/ports/asdf.ts +++ b/ports/asdf.ts @@ -150,3 +150,17 @@ export class Port extends PortBase { }); } } +/* +interface ASDF_CONFIG_EXAMPLE { + ASDF_INSTALL_TYPE: "version" | "ref"; + ASDF_INSTALL_VERSION: string; // full version number or Git Ref depending on ASDF_INSTALL_TYPE + ASDF_INSTALL_PATH: string; // the path to where the tool should, or has been installed + ASDF_CONCURRENCY: number; // the number of cores to use when compiling the source code. Useful for setting make -j + ASDF_DOWNLOAD_PATH: string; // the path to where the source code or binary was downloaded to by bin/download + ASDF_PLUGIN_PATH: string; // the path the plugin was installed + ASDF_PLUGIN_SOURCE_URL: string; // the source URL of the plugin + ASDF_PLUGIN_PREV_REF: string; // prevous git-ref of the plugin repo + ASDF_PLUGIN_POST_REF: string; // updated git-ref of the plugin repo + ASDF_CMD_FILE: string; // resolves to the full path of the file being sourced +} +*/ From bef28ad23a02fadcb7e46e3a7a42af8e321dc0eb Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:35:17 +0300 Subject: [PATCH 04/21] fix: improve uninited dir experience --- .ghjk/lock.json | 79 +---------------------- docs/architecture.md | 78 +++++++++++++--------- examples/tasks/ghjk.ts | 41 ++++++++++++ ghjk.ts | 62 ++---------------- host/mod.ts | 143 +++++++++++++++++++++-------------------- host/worker.ts | 2 + main.ts | 8 +-- mod.ts | 73 +++++++-------------- modules/envs/mod.ts | 4 +- 9 files changed, 202 insertions(+), 288 deletions(-) create mode 100644 examples/tasks/ghjk.ts diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 3874fc10..5f546e72 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -103,12 +103,6 @@ "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" ], "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" - }, - "ghjkTaskInstSet___ha": { - "installs": [ - "f48ddfcfec810fcfcfc155fef7281a8c139c26fa" - ], - "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" } } } @@ -116,63 +110,8 @@ { "id": "tasks", "config": { - "envs": { - "287dbbf1428b17704321d061b7b99f6906906546": { - "provides": [] - }, - "5b8568ad275f743bca20b3988d17dc60cccc5dc6": { - "provides": [ - { - "ty": "ghjkPortsInstallSetRef", - "setId": "ghjkTaskInstSet___ha" - }, - { - "ty": "envVar", - "key": "STUFF", - "val": "stuffier" - } - ] - } - }, - "tasks": { - "greet": { - "name": "greet", - "envHash": "287dbbf1428b17704321d061b7b99f6906906546" - }, - "ha": { - "name": "ha", - "envHash": "5b8568ad275f743bca20b3988d17dc60cccc5dc6" - }, - "ho": { - "name": "ho", - "dependsOn": [ - "ha" - ], - "envHash": "287dbbf1428b17704321d061b7b99f6906906546" - }, - "hum": { - "name": "hum", - "dependsOn": [ - "ho" - ], - "envHash": "287dbbf1428b17704321d061b7b99f6906906546" - }, - "hii": { - "name": "hii", - "dependsOn": [ - "hum" - ], - "envHash": "287dbbf1428b17704321d061b7b99f6906906546" - }, - "hey": { - "name": "hey", - "dependsOn": [ - "hii", - "ho" - ], - "envHash": "287dbbf1428b17704321d061b7b99f6906906546" - } - } + "envs": {}, + "tasks": {} } }, { @@ -584,20 +523,6 @@ "asdf_plugin_git": "a36b37f4eda81bf51a50d00362637690c7fea473", "node_org": "5843605c861f0b7307c0192a1628c3823fe28ed9", "cpy_bs_ghrel": "7a33163826283c47b52964a23b87a4762662c746" - }, - "f48ddfcfec810fcfcfc155fef7281a8c139c26fa": { - "port": { - "ty": "denoWorker@v1", - "name": "protoc_ghrel", - "version": "0.1.0", - "moduleSpecifier": "file:///data/home/ghjk/ports/protoc.ts", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ] - } } } } diff --git a/docs/architecture.md b/docs/architecture.md index 8b0fecce..81519ee9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -11,17 +11,21 @@ > [here](https://www.tldraw.com/s/v2_c_MewHuw1lKwZzwv3XG8-Y6?viewport=-3756%2C-1126%2C10279%2C6280&page=page%3Apage). > Be sure to update the [backup](./architecture.tldr) if you update that. -Ghjk is made up of a set of modules that each implement and encapsulate a set of related features. -The program is primarily consumed through the provided CLI. -It takes as an argument a path to a ghjkfile (through `$GHJKFILE`) and if no such argument is provided, it'll look for a file named `ghjk.ts` in the current or any of the parent directories and treat it as the config file. -It then loads the config file in a `WebWorker` to obtain a config object which is expected to contain configuration for any of the modules it's interested in. -The modules then process their configuration and, based on it, outline the cli commands and flags to expose through the CLI. -The modules are also allowed to export entries to the lockfile which is treated as a _memo_ of the processed config file. -As of January 1, 2024 the following modules are implemented/planned: +Ghjk is made up of a set of modules that each implement and encapsulate a set of +related features. The program is primarily consumed through the provided CLI. It +takes as an argument a path to a ghjkfile (through `$GHJKFILE`) and if no such +argument is provided, it'll look for a file named `ghjk.ts` in the current or +any of the parent directories and treat it as the config file. It then loads the +config file in a `WebWorker` to obtain a config object which is expected to +contain configuration for any of the modules it's interested in. The modules +then process their configuration and, based on it, outline the cli commands and +flags to expose through the CLI. The modules are also allowed to export entries +to the lockfile which is treated as a _memo_ of the processed config file. As of +January 1, 2024 the following modules are implemented/planned: - Ports: download and install executables and libraries -- Envs (TBD): make CLI shell environments that have access to specific programs - and variables +- Envs: make CLI shell environments that have access to specific programs and + variables - Tasks: run commands in custom shell environments ## Run down @@ -34,9 +38,14 @@ Ghjk is composed of two distinct spheres: - The host - loads and processes config files -Ghjkfiles are the primary entry point for interacting with `ghjk` and provide the vector of programmability for end users. As of today, only `ghjk.ts` config files are supported but the `ghjk` is designed to support alternatives. -You'll observe that this kind of modularity and extendability is a core motif of the design, providing constraints, guidance and tension that's informed a lot of the current design. -A lot of decisions and abstractions will thus appear YAGNI (you ain't going to need it) at this early stage but programmability is the name of the game in ghjk is programmability so we prefer to err on the side of modularity. +Ghjkfiles are the primary entry point for interacting with `ghjk` and provide +the vector of programmability for end users. As of today, only `ghjk.ts` config +files are supported but the `ghjk` is designed to support alternatives. You'll +observe that this kind of modularity and extendability is a core motif of the +design, providing constraints, guidance and tension that's informed a lot of the +current design. A lot of decisions and abstractions will thus appear YAGNI (you +ain't going to need it) at this early stage but programmability is the name of +the game in ghjk so we prefer to err on the side of modularity. ### Ghjkfiles @@ -52,17 +61,20 @@ A lot of decisions and abstractions will thus appear YAGNI (you ain't going to n - If `ghjk.ts` exposes an item named `secureConfig`, it's passed as the first argument to `getConfig`. - `ghjk/mod.ts` exposes a bunch of helpers for authoring conventional `ghjk.ts` - but as far as the host is concerned, it's only aware of the `getConfig` - interface. + but as far as the host is concerned, it's only aware of the + `getConfig(secureConfig?): SerializedConfig` interface. ### Ghjkdir - Contains files specific to a certain ghjkfile - Expected and placed in `.ghjk/` within the same dir of the ghjkfile. - Contents: - - `lock.json`: lockfile generated from the ghjkfile. Intended to be version control. - - `deno.lock`: lockfile for any modules used by ghjk when working with that specific lockfile. Intended to be version controlled. - - `hash.json`: serves as a store for hashes used to determine weather re-serialization is necessary. Don't put in version control. + - `lock.json`: lockfile generated from the ghjkfile. Intended to be version + control. + - `deno.lock`: lockfile for any modules used by ghjk when working with that + specific lockfile. Intended to be version controlled. + - `hash.json`: serves as a store for hashes used to determine weather + re-serialization is necessary. Don't put in version control. - `envs`: the shims and loaders of the different environments ### Host @@ -76,14 +88,19 @@ The host is the section of the program expected to: ### Modules -Ghjk is made up of a set of interacting modules implementing specific functionality. -Listed below are the modules that we think will make ghjk a complete runtime manager but note that we don't currently plan on implementing all of them. -Getting each module to become competitive with equivalent tools let alone achieving feature parity is beyond the resources available to the authors today and their design is only considered here to provide a holistic framework for development of ghjk. -It is, after all, a _programmable runtime manager_ and we intend to make the core of ghjk (i.e. the host) modular enough that: +Ghjk is made up of a set of interacting modules implementing specific +functionality. Listed below are the modules that we think will make ghjk a +complete runtime manager but note that we don't currently plan on implementing +all of them. Getting each module to become competitive with equivalent tools let +alone achieving feature parity is beyond the resources available to the authors +today and their design is only considered here to provide a holistic perspective +for the design of ghjk. It is, after all, a _programmable runtime manager_ and +we intend to make the core of ghjk (i.e. the host) modular enough that: - Future implementations shouldn't require large refactors - Easy integration of external tools as modules -- Easy to swap implementation of modules without requiring lot of changes in other dependent modules +- Easy to swap implementation of modules without requiring lot of changes in + other dependent modules #### Ports module @@ -112,14 +129,13 @@ Equivalent tools: program that will handle it's installation. - A `PortManifest` can optionally specify a list of other ports, under `buildDeps`, that the `Port` requires during build time. - - A separate list of dependencies, `resolutionDeps`, is used for - routines used for version resolution like `listAll` and - `latestStable`. + - A separate list of dependencies, `resolutionDeps`, is used for version + resolution like `listAll` and `latestStable`. - Any dependencies used by ports must be declared in the top level `allowedPortDeps` list. - I.e. non standard dependencies will have to be manually declared there by users. - - `InstallConfig` can optionally contain a `version`. + - `InstallConfig` can optionally contain a `version` field. - If found, the `version` is sanity checked against the list of versions returned by `listAll`. - [ ] Fuzzy matching can optionally take place. @@ -135,7 +151,8 @@ Equivalent tools: - A Port is described through the `PortManifest` object. - The implementation and execution of ports depends on the `ty` of the port but - they're all roughly expose the following stages modeled after `asdf` plugins: + they're all, roughly, expected to expose the following stages modeled after + `asdf` plugins: - `listAll`: return a list of all the versions that the port - `latestStable`: the version to install when no version is specified by the user. @@ -174,8 +191,9 @@ Equivalent tools: #### Envs module -Reproducible CLI shell environments that can access specific tools and variables. -Including support to auto-load an environment when a specific shell `cd`'s to the ghjk root. +Reproducible CLI shell environments that can access specific tools and +variables. Including support to auto-load an environment when a specific shell +`cd`'s to the ghjk root. Prior art: @@ -206,7 +224,7 @@ Aspirations: #### Containers module -Create OCI compatible containers from based on the results of the Envs and Build +Create OCI compatible containers from on the outputs of the Envs and Build module. Not planned. Looking at: diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts new file mode 100644 index 00000000..4869b9d4 --- /dev/null +++ b/examples/tasks/ghjk.ts @@ -0,0 +1,41 @@ +export { ghjk } from "../../mod.ts"; +import { logger, task } from "../../mod.ts"; +import * as ports from "../../ports/mod.ts"; + +task("greet", async ({ $, argv: [name] }) => { + await $`echo Hello ${name}!`; +}); + +const ha = task({ + name: "ha", + installs: [ + ports.protoc(), + ], + envVars: { STUFF: "stuffier" }, + async fn({ $ }) { + await $`echo $STUFF; + protoc --version; + `; + }, +}); + +task("ho", { + dependsOn: [ha], + fn: () => logger().info(`ho`), +}); + +task("hii", { + // task `dependsOn` declaration is order-independent + dependsOn: ["hum"], + fn: () => logger().info(`haii`), +}); + +task("hum", { + dependsOn: ["ho"], + fn: () => logger().info(`hum`), +}); + +task("hey", { + dependsOn: ["hii", "ho"], + fn: () => logger().info(`hey`), +}); diff --git a/ghjk.ts b/ghjk.ts index bb6433f8..64d4c2d2 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,69 +1,15 @@ export { ghjk } from "./mod.ts"; -import * as ghjk from "./mod.ts"; +import { install, stdSecureConfig } from "./mod.ts"; import * as ports from "./ports/mod.ts"; -ghjk - .task("greet", { - fn: async ({ $, argv: [name] }) => { - await $`echo Hello ${name}!`; - }, - }); - -const ha = ghjk - .task("ha", { - installs: [ - ports.protoc(), - ], - envVars: { STUFF: "stuffier" }, - async fn({ $ }) { - await $`echo $STUFF; - protoc --version; - `; - }, - }); - -const ho = ghjk - .task("ho", { - dependsOn: [ha], - async fn({ $ }) { - await $`echo ho`; - }, - }); - -const hum = ghjk - .task("hum", { - dependsOn: [ho], - async fn({ $ }) { - await $`echo hum`; - }, - }); - -const hii = ghjk - .task("hii", { - dependsOn: [hum], - async fn({ $ }) { - await $`echo haii`; - }, - }); - -ghjk - .task("hey", { - dependsOn: [hii, ho], - async fn({ $ }) { - await $`echo hey`; - }, - }); - // these are just for quick testing -ghjk.install(); +install(); // these are used for developing ghjk -ghjk.install( +install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], ports.cpy_bs({ releaseTag: "20231002" }), ); -export const secureConfig = ghjk.secureConfig({ - masterPortDepAllowList: ghjk.stdDeps({ enableRuntimes: true }), -}); +export const secureConfig = stdSecureConfig({ enableRuntimes: true }); diff --git a/host/mod.ts b/host/mod.ts index cd204008..62a4b5e7 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -18,7 +18,7 @@ import { serializePlatform } from "../modules/ports/types/platform.ts"; export interface CliArgs { ghjkShareDir: string; - ghjkfilePath: string; + ghjkfilePath?: string; } type HostCtx = { @@ -26,18 +26,81 @@ type HostCtx = { }; export async function cli(args: CliArgs) { - const ghjkfilePath = $.path(args.ghjkfilePath).resolve().normalize() - .toString(); const ghjkShareDir = $.path(args.ghjkShareDir).resolve().normalize() .toString(); - const ghjkDir = $.path(ghjkfilePath).parentOrThrow().join(".ghjk").toString(); - logger().debug({ ghjkfilePath, ghjkDir }); + const subcmds = { + print: new cliffy_cmd.Command() + .description("Emit different discovered and built values to stdout.") + .action(function () { + this.showHelp(); + }) + .command( + "share-dir-path", + new cliffy_cmd.Command() + .description("Print the path where ghjk is installed in.") + .action(function () { + console.log(ghjkShareDir); + }), + ), + deno: new cliffy_cmd.Command() + .description("Access the deno cli used by ghjk.") + .useRawArgs() + .action(async function (_, ...args) { + logger().debug(args); + await $.raw`${Deno.execPath()} ${args}` + .env("DENO_EXEC_PATH", Deno.execPath()); + }), + }; + + if (args.ghjkfilePath) { + const ghjkfilePath = $.path(args.ghjkfilePath).resolve().normalize() + .toString(); + const ghjkDir = $.path(ghjkfilePath).parentOrThrow().join(".ghjk") + .toString(); + logger().debug({ ghjkfilePath, ghjkDir }); + + const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, blackboard: new Map() }; + const hcx = { fileHashMemoStore: new Map() }; - const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, blackboard: new Map() }; - const hcx = { fileHashMemoStore: new Map() }; + const { subCommands: configCommands, serializedConfig } = await readConfig( + gcx, + hcx, + ); - const { subCommands, serializedConfig } = await readConfig(gcx, hcx); + Object.assign(subcmds, configCommands); + + subcmds.print = subcmds.print + .command( + "ghjk-dir-path", + new cliffy_cmd.Command() + .description("Print the path where ghjk is installed in.") + .action(function () { + console.log(ghjkDir); + }), + ) + .command( + "ghjkfile-path", + new cliffy_cmd.Command() + .description("Print the path of the ghjk.ts used") + .action(function () { + console.log(ghjkfilePath); + }), + ) + .command( + "config", + new cliffy_cmd.Command() + .description( + "Print the extracted ans serialized config from the ghjkfile", + ) + .action(function () { + console.log(Deno.inspect(serializedConfig, { + depth: 10, + colors: isColorfulTty(), + })); + }), + ); + } let cmd: cliffy_cmd.Command = new cliffy_cmd.Command() .name("ghjk") @@ -45,65 +108,8 @@ export async function cli(args: CliArgs) { .description("Programmable runtime manager.") .action(function () { this.showHelp(); - }) - .command( - "print", - new cliffy_cmd.Command() - .description("Emit different discovored and built values to stdout.") - .action(function () { - this.showHelp(); - }) - .command( - "ghjk-dir-path", - new cliffy_cmd.Command() - .description("Print the path where ghjk is installed in.") - .action(function () { - console.log(ghjkDir); - }), - ) - .command( - "share-dir-path", - new cliffy_cmd.Command() - .description("Print the path where ghjk is installed in.") - .action(function () { - console.log(ghjkShareDir); - }), - ) - .command( - "ghjkfile-path", - new cliffy_cmd.Command() - .description("Print the path of the ghjk.ts used") - .action(function () { - console.log(ghjkfilePath); - }), - ) - .command( - "config", - new cliffy_cmd.Command() - .description( - "Print the extracted ans serialized config from the ghjkfile", - ) - .action(function () { - console.log(Deno.inspect(serializedConfig, { - depth: 10, - colors: isColorfulTty(), - })); - }), - ), - ) - .command( - "deno", - new cliffy_cmd.Command() - .description("Access the deno cli used by ghjk.") - .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(subCommands)) { + }); + for (const [name, subcmd] of Object.entries(subcmds)) { cmd = cmd.command(name, subcmd); } await cmd @@ -168,8 +174,7 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { } const platformMatch = () => - foundLockObj.platform[0] == Deno.build.os && - foundLockObj.platform[1] == Deno.build.arch; + serializePlatform(Deno.build) == foundLockObj.platform; const envHashesMatch = async () => { const oldHashes = foundHashObj!.envVarHashes; diff --git a/host/worker.ts b/host/worker.ts index 3b36fbd2..55c24066 100644 --- a/host/worker.ts +++ b/host/worker.ts @@ -72,6 +72,8 @@ function denoFsReadShim() { throw new Error("Deno.watchFs API is disabled"); }] as const, ...[ + // TODO: systemize a way to make sure this + // tracks deno APIs Deno.readFile, Deno.readTextFileSync, Deno.readTextFile, diff --git a/main.ts b/main.ts index 74b2c54d..81571da7 100755 --- a/main.ts +++ b/main.ts @@ -10,19 +10,19 @@ if (import.meta.main) { const ghjkfile = Deno.env.get("GHJKFILE") ?? await findConfig(Deno.cwd()); if (!ghjkfile) { - logger().error( + logger().warn( "ghjk could not find any ghjkfiles, try creating a `ghjk.ts` script.", ); - Deno.exit(2); + // Deno.exit(2); } await cli({ ghjkShareDir: Deno.env.get("GHJK_SHARE_DIR") ?? std_path.resolve(dirs().shareDir, "ghjk"), - ghjkfilePath: std_path.resolve(Deno.cwd(), ghjkfile), + ghjkfilePath: ghjkfile ? std_path.resolve(Deno.cwd(), ghjkfile) : undefined, }); } else { throw new Error( - `unexpected context: this module is an entrypoint. If you want to programmatically invoke the ghjk cli, import \`main\` from ${ + `unexpected context: this module is an entrypoint. If you want to programmatically invoke the ghjk cli, import \`cli\` from ${ import.meta.resolve("./host/mod.ts") }`, ); diff --git a/mod.ts b/mod.ts index a1a045b3..811e6ca7 100644 --- a/mod.ts +++ b/mod.ts @@ -65,7 +65,7 @@ export type TaskFnArgs = { env: Record; }; -export type TaskFn = (args: TaskFnArgs) => Promise; +export type TaskFn = (args: TaskFnArgs) => Promise | any; /* * Configuration for a task. @@ -83,18 +83,12 @@ export type TaskDefArgs = { }; class GhjkfileBuilder { - #installSets = new Map< - string, - InstallSet - >(); + #installSets = new Map(); #tasks = {} as Record; #bb = new Map(); #seenEnvs: Record = {}; - addInstall( - setId: string, - configUnclean: InstallConfigFat, - ) { + addInstall(setId: string, configUnclean: InstallConfigFat) { const config = unwrapParseRes( portsValidators.installConfigFat.safeParse(configUnclean), { @@ -108,10 +102,7 @@ class GhjkfileBuilder { logger().debug("install added", config); } - setAllowedPortDeps( - setId: string, - deps: AllowedPortDep[], - ) { + setAllowedPortDeps(setId: string, deps: AllowedPortDep[]) { const set = this.#getSet(setId); set.allowedDeps = Object.fromEntries( deps.map(( @@ -120,9 +111,7 @@ class GhjkfileBuilder { ); } - addTask( - args: TaskDefArgs, - ) { + addTask(args: TaskDefArgs) { // NOTE: we make sure the env base declared here exists // this call is necessary to make sure that a `task` can // be declared before the `env` but still depend on it. @@ -139,9 +128,7 @@ class GhjkfileBuilder { return args.name; } - addEnv( - args: EnvDefArgs, - ) { + addEnv(args: EnvDefArgs) { let env = this.#seenEnvs[args.name]?.[0]; if (!env) { let finalizer: EnvFinalizer; @@ -194,10 +181,6 @@ class GhjkfileBuilder { }], blackboard: Object.fromEntries(this.#bb.entries()), }; - console.log(Deno.inspect(config, { - depth: 10, - colors: true, - })); return config; } catch (cause) { throw new Error(`error constructing config for serialization`, { cause }); @@ -437,26 +420,8 @@ class GhjkfileBuilder { const out: PortsModuleConfigHashed = { sets: {}, }; - /* const masterPortDepAllowList = Object.fromEntries([ - ...masterAllowList - .map((dep) => - [ - dep.manifest.name, - this.#registerAllowedPortDep( - portsValidators.allowedPortDep.parse(dep), - ), - ] as const - ), - ]); - */ const masterPortDepAllowList = Object.fromEntries( - masterAllowList - .map((dep) => - [ - dep.manifest.name, - dep, - ] as const - ), + masterAllowList.map((dep) => [dep.manifest.name, dep] as const), ); for ( const [setId, set] of this.#installSets.entries() @@ -476,9 +441,9 @@ class GhjkfileBuilder { out.sets[setId] = { installs: set.installs.map((inst) => this.#addToBlackboard(inst)), allowedDeps: this.#addToBlackboard(Object.fromEntries( - Object.entries(set.allowedDeps).map(( - [key, dep], - ) => [key, this.#addToBlackboard(dep)]), + Object.entries(set.allowedDeps).map( + ([key, dep]) => [key, this.#addToBlackboard(dep)], + ), )), }; } @@ -610,10 +575,20 @@ export function env( return file.addEnv(args); } -export function secureConfig( - config: PortsModuleSecureConfig, -) { - return config; +export function stdSecureConfig( + args: { + additionalAllowedPorts?: PortsModuleSecureConfig["masterPortDepAllowList"]; + enableRuntimes?: boolean; + }, +): PortsModuleSecureConfig { + const { additionalAllowedPorts, enableRuntimes = false } = args; + const out: PortsModuleSecureConfig = { + masterPortDepAllowList: [ + ...stdDeps({ enableRuntimes }), + ...additionalAllowedPorts ?? [], + ], + }; + return out; } export function stdDeps(args = { enableRuntimes: false }) { diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index aac41511..8304b15a 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -1,6 +1,8 @@ /* Design: - - `$ ghjk env foo` to switch to environment + - `$ ghjk env activate` to switch to default environment + - `$ ghjk env list` + - `$ ghjk env info` - By default, all things go to the `main` environment */ From ff5aadfa1cc7f3054aac7eda109e738bc31fa1fb Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Mon, 1 Apr 2024 01:42:38 +0300 Subject: [PATCH 05/21] wip: `$ ghjk envs sync` --- .ghjk/lock.json | 51 ++++++++++++++++++--- ghjk.ts | 10 ++++- mod.ts | 53 ++++++++++++++-------- modules/envs/mod.ts | 56 ++++++++++++++++------- modules/envs/posix.ts | 93 ++++++++++----------------------------- modules/envs/reducer.ts | 58 +++++++++++++++++++++++- modules/envs/types.ts | 11 +++-- modules/ports/mod.ts | 21 ++------- modules/ports/reducers.ts | 8 ++-- modules/ports/types.ts | 6 ++- modules/tasks/exec.ts | 5 ++- tests/ports.ts | 16 +++---- tests/test.Dockerfile | 2 +- utils/mod.ts | 30 ++++++++----- 14 files changed, 260 insertions(+), 160 deletions(-) diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 5f546e72..6e0b86c9 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -17,7 +17,7 @@ "version": "3.12.1", "depConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -37,7 +37,7 @@ "version": "3.12.1", "depConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -50,7 +50,7 @@ "portRef": "cpy_bs_ghrel@0.1.0" }, "9e3fa7742c431c34ae7ba8d1e907e50c937ccfb631fb4dcfb7a1773742abe267": { - "version": "1.34", + "version": "1.35", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -63,7 +63,7 @@ "version": "3.12.0", "depConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "depConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -103,6 +103,15 @@ "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" ], "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" + }, + "ghjkEnvProvInstSet___test": { + "installs": [ + "aa103d26454710ca5d7f43358123341380389864", + "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", + "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44", + "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" + ], + "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" } } } @@ -117,7 +126,25 @@ { "id": "envs", "config": { - "envs": {} + "envs": { + "main": { + "provides": [ + { + "ty": "ghjk.ports.InstallSetRef", + "setId": "ghjkEnvProvInstSet___main" + } + ] + }, + "test": { + "provides": [ + { + "ty": "ghjk.ports.InstallSetRef", + "setId": "ghjkEnvProvInstSet___test" + } + ] + } + }, + "defaultEnv": "main" } } ], @@ -523,6 +550,20 @@ "asdf_plugin_git": "a36b37f4eda81bf51a50d00362637690c7fea473", "node_org": "5843605c861f0b7307c0192a1628c3823fe28ed9", "cpy_bs_ghrel": "7a33163826283c47b52964a23b87a4762662c746" + }, + "aa103d26454710ca5d7f43358123341380389864": { + "port": { + "ty": "denoWorker@v1", + "name": "protoc_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin" + ], + "version": "0.1.0", + "moduleSpecifier": "file:///ports/protoc.ts" + } } } } diff --git a/ghjk.ts b/ghjk.ts index 64d4c2d2..c841dd78 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,5 +1,5 @@ export { ghjk } from "./mod.ts"; -import { install, stdSecureConfig } from "./mod.ts"; +import { env, install, stdSecureConfig } from "./mod.ts"; import * as ports from "./ports/mod.ts"; // these are just for quick testing @@ -12,4 +12,10 @@ install( ports.cpy_bs({ releaseTag: "20231002" }), ); -export const secureConfig = stdSecureConfig({ enableRuntimes: true }); +env("test") + .install(ports.protoc()); + +export const secureConfig = stdSecureConfig({ + enableRuntimes: true, + defaultBaseEnv: "test", +}); diff --git a/mod.ts b/mod.ts index 811e6ca7..fd2c986d 100644 --- a/mod.ts +++ b/mod.ts @@ -44,7 +44,7 @@ import { WellKnownProvision, } from "./modules/envs/types.ts"; -const DEFAULT_ENV_NAME = "main"; +const DEFAULT_BASE_ENV_NAME = "main"; export type EnvDefArgs = { name: string; @@ -135,7 +135,7 @@ class GhjkfileBuilder { env = new EnvBuilder(this, (fin) => finalizer = fin, args.name); this.#seenEnvs[args.name] = [env, finalizer!]; } - if (args.envBase) { + if (args.envBase !== undefined) { env.base(args.envBase); } if (args.installs) { @@ -162,8 +162,14 @@ class GhjkfileBuilder { toConfig(secureConfig: PortsModuleSecureConfig | undefined) { try { - const envsConfig = this.#processEnvs(); - const tasksConfig = this.#processTasks(envsConfig); + const defaultEnv = secureConfig?.defaultEnv ?? DEFAULT_BASE_ENV_NAME; + const defaultBaseEnv = secureConfig?.defaultBaseEnv ?? + DEFAULT_BASE_ENV_NAME; + const envsConfig = this.#processEnvs( + defaultEnv, + defaultBaseEnv, + ); + const tasksConfig = this.#processTasks(envsConfig, defaultBaseEnv); const portsConfig = this.#processInstalls( secureConfig?.masterPortDepAllowList ?? stdDeps(), ); @@ -208,7 +214,10 @@ class GhjkfileBuilder { // this processes the defined envs, normalizing dependency (i.e. "envBase") // relationships to produce the standard EnvsModuleConfig - #processEnvs() { + #processEnvs( + defaultEnv: string, + defaultBaseEnv: string, + ) { const all = {} as Record< string, ReturnType & { envBaseResolved: null | string } @@ -223,7 +232,7 @@ class GhjkfileBuilder { const envBaseResolved = typeof envBase === "string" ? envBase : envBase - ? DEFAULT_ENV_NAME + ? defaultBaseEnv : null; all[name] = { ...final, envBaseResolved }; if (envBaseResolved) { @@ -238,14 +247,16 @@ class GhjkfileBuilder { } } const processed = {} as Record; - const out: EnvsModuleConfig = { envs: {} }; - const workingSet = indie; + const out: EnvsModuleConfig = { envs: {}, defaultEnv }; + const workingSet = [...indie]; while (workingSet.length > 0) { const item = workingSet.pop()!; const final = all[item]; + const base = final.envBaseResolved ? processed[final.envBaseResolved] : null; + let processedInstallSetId: string | undefined; { const installSet = this.#installSets.get(final.installSetId); @@ -286,23 +297,29 @@ class GhjkfileBuilder { ...Object.entries(final.vars).map(( [key, val], ) => { - const prov: WellKnownProvision = { ty: "envVar", key, val }; + const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; return prov; }), ], }; if (processedInstallSetId) { const prov: InstallSetRefProvision = { - ty: "ghjkPortsInstallSetRef", + ty: "ghjk.ports.InstallSetRef", setId: processedInstallSetId, }; out.envs[final.name].provides.push(prov); } + + const curRevDeps = revDeps.get(final.name); + if (curRevDeps) { + workingSet.push(...curRevDeps); + revDeps.delete(final.name); + } } return out; } - #processTasks(envsConfig: EnvsModuleConfig) { + #processTasks(envsConfig: EnvsModuleConfig, defaultBaseEnv: string) { const out: TasksModuleConfig = { envs: {}, tasks: {}, @@ -317,7 +334,7 @@ class GhjkfileBuilder { const envBaseResolved = typeof envBase === "string" ? envBase : envBase - ? DEFAULT_ENV_NAME + ? defaultBaseEnv : null; const envBaseRecipe = envBaseResolved @@ -344,11 +361,11 @@ class GhjkfileBuilder { | InstallSetRefProvision )[] ) { - if (prov.ty == "envVar") { + if (prov.ty == "posix.envVar") { if (!mergedEnvVars[prov.key]) { mergedEnvVars[prov.key] = prov.val; } - } else if (prov.ty == "ghjkPortsInstallSetRef") { + } else if (prov.ty == "ghjk.ports.InstallSetRef") { const baseSet = this.#installSets.get(prov.setId)!; const mergedInstallsSet = new Set([ ...taskInstallSet.installs, @@ -373,7 +390,7 @@ class GhjkfileBuilder { const setId = `ghjkTaskInstSet___${name}`; this.#installSets.set(setId, taskInstallSet); const prov: InstallSetRefProvision = { - ty: "ghjkPortsInstallSetRef", + ty: "ghjk.ports.InstallSetRef", setId, }; taskEnvRecipe.provides.push(prov); @@ -383,7 +400,7 @@ class GhjkfileBuilder { ...Object.entries(mergedEnvVars).map(( [key, val], ) => { - const prov: WellKnownProvision = { ty: "envVar", key, val }; + const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; return prov; }), ); @@ -514,7 +531,7 @@ class EnvBuilder { const file = new GhjkfileBuilder(); const mainEnv = file.addEnv({ - name: DEFAULT_ENV_NAME, + name: DEFAULT_BASE_ENV_NAME, envBase: false, allowedPortDeps: stdDeps(), }); @@ -579,7 +596,7 @@ export function stdSecureConfig( args: { additionalAllowedPorts?: PortsModuleSecureConfig["masterPortDepAllowList"]; enableRuntimes?: boolean; - }, + } & Pick, ): PortsModuleSecureConfig { const { additionalAllowedPorts, enableRuntimes = false } = args; const out: PortsModuleSecureConfig = { diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 8304b15a..5b785217 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -9,7 +9,7 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { Json } from "../../utils/mod.ts"; +import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; import validators from "./types.ts"; import type { EnvsModuleConfigX } from "./types.ts"; @@ -17,48 +17,72 @@ import type { GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import { Blackboard } from "../../host/types.ts"; +import { reduceStrangeProvisions } from "./reducer.ts"; +import { cookPosixEnv } from "./posix.ts"; + +export type EnvsCtx = { + activeEnv: string; + config: EnvsModuleConfigX; +}; -export type EnvsCtx = {}; const lockValidator = zod.object({ version: zod.string(), }); + type EnvsLockEnt = zod.infer; export class EnvsModule extends ModuleBase { processManifest( _ctx: GhjkCtx, manifest: ModuleManifest, - _bb: Blackboard, + bb: Blackboard, _lockEnt: EnvsLockEnt | undefined, ) { - const res = validators.envsModuleConfig.safeParse(manifest.config); - if (!res.success) { - throw new Error("error parsing module config", { - cause: { - config: manifest.config, - zodErr: res.error, - }, - }); + function unwrapParseCurry(res: zod.SafeParseReturnType) { + return unwrapParseRes(res, { + id: manifest.id, + config: manifest.config, + bb, + }, "error parsing module config"); } - const config: EnvsModuleConfigX = { - ...res.data, - }; + const config = unwrapParseCurry( + validators.envsModuleConfig.safeParse(manifest.config), + ); + + const activeEnv = config.defaultEnv; return Promise.resolve({ + activeEnv, config, }); } command( - _gcx: GhjkCtx, - _ecx: EnvsCtx, + gcx: GhjkCtx, + ecx: EnvsCtx, ) { const root: cliffy_cmd.Command = new cliffy_cmd .Command() + .description("Envs module, the cornerstone") .alias("e") + .alias("env") .action(function () { this.showHelp(); }) + .command( + "sync", + new cliffy_cmd.Command().description("Syncs the environment.") + .action(async () => { + const envName = ecx.activeEnv; + + const env = ecx.config.envs[envName]; + // TODO: diff env and ask confirmation from user + const reducedEnv = await reduceStrangeProvisions(gcx, env); + const envDir = $.path(gcx.ghjkDir).join("envs", envName).toString(); + + await cookPosixEnv(reducedEnv, envDir, true); + }), + ) .description("Envs module."); return root; } diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index fd7c2484..8a2b2f5a 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -1,61 +1,14 @@ import { std_fs, std_path } from "../../deps/cli.ts"; -import type { - EnvRecipeX, - Provision, - WellKnownEnvRecipeX, - WellKnownProvision, -} from "./types.ts"; -import validators from "./types.ts"; -import { wellKnownProvisionTypes } from "./types.ts"; +import type { WellKnownEnvRecipeX } from "./types.ts"; import getLogger from "../../utils/logger.ts"; import { $, PathRef } from "../../utils/mod.ts"; -import type { GhjkCtx } from "../types.ts"; -import { getProvisionReducerStore } from "./reducer.ts"; const logger = getLogger(import.meta); -export async function reduceStrangeProvisions( - gcx: GhjkCtx, - env: EnvRecipeX, -) { - const reducerStore = getProvisionReducerStore(gcx); - const bins = {} as Record; - for (const item of env.provides) { - let bin = bins[item.ty]; - if (!bin) { - bin = []; - bins[item.ty] = bin; - } - bin.push(item); - } - const reducedSet = [] as WellKnownProvision[]; - for (const [ty, items] of Object.entries(bins)) { - if (wellKnownProvisionTypes.includes(ty as any)) { - reducedSet.push( - ...items.map((item) => validators.wellKnownProvision.parse(item)), - ); - continue; - } - const reducer = reducerStore.get(ty); - if (!reducer) { - throw new Error(`no provider reducer found for ty: ${ty}`, { - cause: items, - }); - } - const reduced = await reducer(items); - reducedSet.push(validators.wellKnownProvision.parse(reduced)); - } - const out: WellKnownEnvRecipeX = { - ...env, - provides: reducedSet, - }; - return out; -} - -export async function cookUnixEnv( +export async function cookPosixEnv( env: WellKnownEnvRecipeX, envDir: string, - createShellLoaders = true, + createShellLoaders = false, ) { // create the shims for the user's environment const shimDir = $.path(envDir).join("shims"); @@ -78,16 +31,16 @@ export async function cookUnixEnv( await Promise.all(env.provides.map((item) => { switch (item.ty) { - case "posixExec": + case "posix.exec": binPaths.push(item.absolutePath); break; - case "posixSharedLib": + case "posix.sharedLib": libPaths.push(item.absolutePath); break; - case "headerFile": + case "posix.headerFile": includePaths.push(item.absolutePath); break; - case "envVar": + case "posix.envVar": if (vars[item.key]) { throw new Error( `env var conflict cooking unix env: key "${item.key}" has entries "${ @@ -101,21 +54,23 @@ export async function cookUnixEnv( throw Error(`unsupported provision type: ${(item as any).provision}`); } })); - // bin shims - void await shimLinkPaths( - binPaths, - binShimDir, - ); - // lib shims - void await shimLinkPaths( - libPaths, - libShimDir, - ); - // include shims - void await shimLinkPaths( - includePaths, - includeShimDir, - ); + void await Promise.all([ + // bin shims + await shimLinkPaths( + binPaths, + binShimDir, + ), + // lib shims + await shimLinkPaths( + libPaths, + libShimDir, + ), + // include shims + await shimLinkPaths( + includePaths, + includeShimDir, + ), + ]); // write loader for the env vars mandated by the installs logger.debug("adding vars to loader", vars); // FIXME: prevent malicious env manipulations diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts index 3860de33..28357be8 100644 --- a/modules/envs/reducer.ts +++ b/modules/envs/reducer.ts @@ -1,5 +1,14 @@ +import { unwrapParseRes } from "../../port.ts"; import type { GhjkCtx } from "../types.ts"; -import type { Provision, ProvisionReducer } from "./types.ts"; +import type { + EnvRecipeX, + Provision, + ProvisionReducer, + WellKnownEnvRecipeX, + WellKnownProvision, +} from "./types.ts"; +import { wellKnownProvisionTypes } from "./types.ts"; +import validators from "./types.ts"; export type ProvisionReducerStore = Map>; export function getProvisionReducerStore( @@ -15,3 +24,50 @@ export function getProvisionReducerStore( } return store; } + +export async function reduceStrangeProvisions( + gcx: GhjkCtx, + env: EnvRecipeX, +) { + const reducerStore = getProvisionReducerStore(gcx); + // Replace by `Object.groupBy` once the types for it are fixed + const bins = {} as Record; + for (const item of env.provides) { + let bin = bins[item.ty]; + if (!bin) { + bin = []; + bins[item.ty] = bin; + } + bin.push(item); + } + const reducedSet = [] as WellKnownProvision[]; + for (const [ty, items] of Object.entries(bins)) { + if (wellKnownProvisionTypes.includes(ty as any)) { + reducedSet.push( + ...items.map((item) => validators.wellKnownProvision.parse(item)), + ); + continue; + } + const reducer = reducerStore.get(ty); + if (!reducer) { + throw new Error(`no provider reducer found for ty: ${ty}`, { + cause: items, + }); + } + const reduced = await reducer(items); + reducedSet.push( + ...reduced.map((prov) => + unwrapParseRes( + validators.wellKnownProvision.safeParse(prov), + { prov }, + `error parsing reduced provision`, + ) + ), + ); + } + const out: WellKnownEnvRecipeX = { + ...env, + provides: reducedSet, + }; + return out; +} diff --git a/modules/envs/types.ts b/modules/envs/types.ts index f0b347a3..00554972 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -5,15 +5,15 @@ const absolutePath = zod.string().refine((path) => std_path.isAbsolute(path)); const provision = zod.object({ ty: zod.string() }).passthrough(); const posixFileProvisionTypes = [ - "posixExec", - "posixSharedLib", - "headerFile", + "posix.exec", + "posix.sharedLib", + "posix.headerFile", ] as const; // we separate the posix file types in a separate // array in the interest of type inference export const wellKnownProvisionTypes = [ - "envVar", + "posix.envVar", ...posixFileProvisionTypes, ] as const; @@ -40,7 +40,10 @@ const wellKnownEnvRecipe = envRecipe.merge(zod.object({ })); const envsModuleConfig = zod.object({ + defaultEnv: zod.string(), envs: zod.record(zod.string(), envRecipe), +}).refine((conf) => conf.envs[conf.defaultEnv], { + message: `no env found under the provided "defaultEnv"`, }); const validators = { diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 506556c8..49e60da9 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -1,7 +1,7 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; +import { Json, unwrapParseRes } from "../../utils/mod.ts"; import logger from "../../utils/logger.ts"; import validators, { installSetProvisionTy, @@ -13,7 +13,6 @@ import { ModuleBase } from "../mod.ts"; import { buildInstallGraph, getResolutionMemo, - installFromGraphAndShimEnv, type InstallGraph, syncCtxFromGhjk, } from "./sync.ts"; // TODO: rename to install.ts @@ -113,8 +112,8 @@ export class PortsModule extends ModuleBase { } command( - gcx: GhjkCtx, - pcx: PortsCtx, + _gcx: GhjkCtx, + _pcx: PortsCtx, ) { return new cliffy_cmd.Command() .alias("p") @@ -122,20 +121,6 @@ export class PortsModule extends ModuleBase { this.showHelp(); }) .description("Ports module, install programs into your env.") - .command( - "sync", - new cliffy_cmd.Command().description("Syncs the environment.") - .action(async () => { - logger().debug("syncing ports"); - await using syncCx = await syncCtxFromGhjk(gcx); - void await installFromGraphAndShimEnv( - syncCx, - $.path(gcx.ghjkDir).join("envs", "default").toString(), - // FIXME: - pcx.installGraphs.values().next().value, - ); - }), - ) .command( "outdated", new cliffy_cmd.Command() diff --git a/modules/ports/reducers.ts b/modules/ports/reducers.ts index e275c639..9c843bbf 100644 --- a/modules/ports/reducers.ts +++ b/modules/ports/reducers.ts @@ -98,7 +98,7 @@ async function reduceInstArts( } foundEnvVars[key] = [val, instId]; out.push({ - ty: "envVar", + ty: "posix.envVar", key, val, }); @@ -117,19 +117,19 @@ async function reduceInstArts( out.push( ...binPathsNorm.flatMap((paths) => paths.map((absolutePath) => ({ - ty: "posixExec" as const, + ty: "posix.exec" as const, absolutePath, })) ), ...libPathsNorm.flatMap((paths) => paths.map((absolutePath) => ({ - ty: "posixSharedLib" as const, + ty: "posix.sharedLib" as const, absolutePath, })) ), ...includePathsNorm.flatMap((paths) => paths.map((absolutePath) => ({ - ty: "headerFile" as const, + ty: "posix.headerFile" as const, absolutePath, })) ), diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 94942858..199ac4f1 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -146,6 +146,8 @@ const allowedPortDep = zod.object({ const portsModuleSecureConfig = zod.object({ masterPortDepAllowList: zod.array(allowedPortDep).nullish(), + defaultEnv: zod.string().nullish(), + defaultBaseEnv: zod.string().nullish(), }); const allowDepSet = zod.record(zod.string(), allowedPortDep); @@ -170,13 +172,13 @@ const portsModuleConfig = zod.object({ sets: zod.record(zod.string(), installSet), }); -export const installSetProvisionTy = "ghjkPortsInstallSet"; +export const installSetProvisionTy = "ghjk.ports.InstallSet"; const installSetProvision = zod.object({ ty: zod.literal(installSetProvisionTy), set: installSet, }); -export const installSetRefProvisionTy = "ghjkPortsInstallSetRef"; +export const installSetRefProvisionTy = "ghjk.ports.InstallSetRef"; const installSetRefProvision = zod.object({ ty: zod.literal(installSetRefProvisionTy), setId: zod.string(), diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index ed503348..4caa3a82 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -8,7 +8,8 @@ import { execTaskDeno } from "./deno.ts"; const logger = getLogger(import.meta); -import { cookUnixEnv, reduceStrangeProvisions } from "../envs/posix.ts"; +import { cookPosixEnv } from "../envs/posix.ts"; +import { reduceStrangeProvisions } from "../envs/reducer.ts"; export type TaskGraph = DePromisify>; @@ -117,7 +118,7 @@ export async function execTask( gcx, tasksConfig.envs[taskDef.envHash], ); - const { env: installEnvs } = await cookUnixEnv(reducedEnv, taskEnvDir); + const { env: installEnvs } = await cookPosixEnv(reducedEnv, taskEnvDir); logger.info("executing", taskName, args); await execTaskDeno( std_path.toFileUrl(gcx.ghjkfilePath).href, diff --git a/tests/ports.ts b/tests/ports.ts index d893483b..e52e9b8e 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -1,6 +1,6 @@ import "../setup_logger.ts"; import { std_async } from "../deps/dev.ts"; -import { secureConfig, stdDeps } from "../mod.ts"; +import { stdSecureConfig } from "../mod.ts"; import { dockerE2eTest, E2eTestCase, @@ -99,8 +99,8 @@ const cases: CustomE2eTestCase[] = [ name: "npmi-node-gyp", installConf: ports.npmi({ packageName: "node-gyp" }), ePoint: `node-gyp --version`, - secureConf: secureConfig({ - masterPortDepAllowList: stdDeps({ enableRuntimes: true }), + secureConf: stdSecureConfig({ + enableRuntimes: true, }), }, // node + more megs @@ -108,8 +108,8 @@ const cases: CustomE2eTestCase[] = [ name: "npmi-jco", installConf: ports.npmi({ packageName: "@bytecodealliance/jco" }), ePoint: `jco --version`, - secureConf: secureConfig({ - masterPortDepAllowList: stdDeps({ enableRuntimes: true }), + secureConf: stdSecureConfig({ + enableRuntimes: true, }), }, // 42 megs @@ -160,8 +160,8 @@ const cases: CustomE2eTestCase[] = [ name: "pipi-poetry", installConf: ports.pipi({ packageName: "poetry" }), ePoint: `poetry --version`, - secureConf: secureConfig({ - masterPortDepAllowList: stdDeps({ enableRuntimes: true }), + secureConf: stdSecureConfig({ + enableRuntimes: true, }), }, // rustup + 600 megs @@ -228,7 +228,7 @@ function testMany( // FIXME: better tests for the `InstallDb` // installs db means this shouldn't take too long // as it's the second sync - { cmd: "env bash -c 'timeout 1 ghjk ports sync'" }, + { cmd: "env bash -c 'timeout 1 ghjk env sync'" }, ], envs: { ...defaultEnvs, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index b6b22279..9b526f2d 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -61,7 +61,7 @@ RUN < = T extends () => infer Inner ? Inner : T; + +export function switchMap< + K extends string | number | symbol, + All extends { + [Key in K]: All[K]; + }, >( val: K, branches: All, -): All[K] extends () => infer Inner ? Inner : All[K] { + // def?: D, +): K extends keyof All ? OrRetOf + : /* All[keyof All] | */ undefined { // return branches[val]; const branch = branches[val]; return typeof branch == "function" ? branch() : branch; } -const _b = match("hey", { - hey: () => 1, - hello: () => 2, - hi: 3 as const, - holla: 4, -}); +switchMap( + "holla" as string, + { + hey: () => 1, + hello: () => 2, + hi: 3, + holla: 4, + } as const, + // () =>5 +); export async function expandGlobsAndAbsolutize(path: string, wd: string) { if (std_path.isGlob(path)) { From 91738a464de95bfcaddcfc4ba3fcbf0f43ed6e23 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:11:34 +0300 Subject: [PATCH 06/21] refactor(envs,cli)!: envs first cli (#48) * refactor(envs): envs first CLI * wip: missing file * wip: wip * wip: wip 2 * feat(tests): basic tests * docs: some snippets for `README.md` * doc: typo * fix: `Deno.Command` troubles * fix: cross platform shell fn for getting ctime * fix: `!` as instId separator * fix(tests): missing flag * wip: wip * feat: `ghjk p resolve` * refactor: polish CLI code * wip: clearEnv fix * fix: vendor dax patch * fix: forgotten change * fix: use `@ghjk/dax` fork * fix: remove vendor dir from Dockerfile --- .ghjk/deno.lock | 114 ++++- .ghjk/lock.json | 105 ++--- .github/workflows/tests.yml | 2 + .pre-commit-config.yaml | 5 + README.md | 130 +++++- check.ts | 2 + deno.jsonc | 12 +- deno.lock | 287 +++++++----- deps/cli.ts | 2 +- deps/common.ts | 7 +- ghjk.ts | 7 +- host/deno.ts => ghjkfiles/deno/mod.ts | 2 +- {host => ghjkfiles/deno}/worker.ts | 6 +- ghjkfiles/mod.ts | 603 ++++++++++++++++++++++++++ host/mod.ts | 401 ++++++++++------- install.ts | 24 +- install/ghjk.sh | 5 +- install/hook.fish | 49 ++- install/hook.sh | 55 ++- install/mod.ts | 51 ++- mod.ts | 568 +----------------------- modules/envs/mod.ts | 300 +++++++++++-- modules/envs/posix.ts | 44 +- modules/envs/reducer.ts | 6 + modules/envs/types.ts | 1 + modules/mod.ts | 16 +- modules/ports/ambient.ts | 2 +- modules/ports/inter.ts | 49 +++ modules/ports/mod.ts | 136 +++--- modules/ports/reducers.ts | 41 +- modules/ports/sync.ts | 189 +------- modules/ports/types.ts | 47 +- modules/ports/worker.ts | 9 +- modules/tasks/exec.ts | 12 +- modules/tasks/mod.ts | 15 +- ports/asdf.ts | 8 +- ports/asdf_plugin_git.ts | 2 +- ports/cargobi.ts | 4 +- ports/cpy_bs.ts | 2 +- ports/dummy.ts | 24 +- ports/infisical.ts | 1 - ports/jq_ghrel.ts | 2 +- ports/meta_cli_ghrel.ts | 2 +- ports/mold.ts | 2 +- ports/node.ts | 2 +- ports/npmi.ts | 2 +- ports/pipi.ts | 2 +- ports/ruff.ts | 2 +- ports/rust.ts | 2 +- ports/rustup.ts | 2 +- ports/temporal_cli.ts | 1 - ports/terraform.ts | 1 - ports/wasmedge.ts | 2 +- tests/ambient.ts | 3 +- tests/envs.ts | 204 +++++++++ tests/hooks.ts | 8 +- tests/ports.ts | 28 +- tests/tasks.ts | 18 +- tests/test-alpine.Dockerfile | 4 +- tests/test.Dockerfile | 4 +- tests/utils.ts | 80 ++-- utils/logger.ts | 7 +- utils/mod.ts | 75 +++- utils/unarchive.ts | 12 +- 64 files changed, 2430 insertions(+), 1380 deletions(-) rename host/deno.ts => ghjkfiles/deno/mod.ts (97%) rename {host => ghjkfiles/deno}/worker.ts (97%) create mode 100644 ghjkfiles/mod.ts create mode 100644 modules/ports/inter.ts create mode 100644 tests/envs.ts diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 885e603d..93382d85 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -1,5 +1,22 @@ { "version": "3", + "packages": { + "specifiers": { + "npm:zod-validation-error": "npm:zod-validation-error@3.1.0_zod@3.22.4" + }, + "npm": { + "zod-validation-error@3.1.0_zod@3.22.4": { + "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", + "dependencies": { + "zod": "zod@3.22.4" + } + }, + "zod@3.22.4": { + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dependencies": {} + } + } + }, "remote": { "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", @@ -242,6 +259,14 @@ "https://deno.land/std@0.213.0/url/join.ts": "00c7e9088cafaa24963ce4081119e58b3afe2c58f033701383f359ea02620dd2", "https://deno.land/std@0.213.0/url/mod.ts": "e2621f6a0db6fdbe7fbbd240064095bb203014657e5e1ab81db1c44d80dce6c9", "https://deno.land/std@0.213.0/url/normalize.ts": "6328c75df0fab300f74bc4a1c255062a0db882240e15ab646606d0009e7e40d7", + "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", + "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", + "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", + "https://deno.land/std@0.221.0/console/_run_length.ts": "7da8642a0f4f41ac27c0adb1364e18886be856c1d08c5cce6c6b5c00543c8722", + "https://deno.land/std@0.221.0/console/unicode_width.ts": "d92f085c0ab9c7ab171e4e7862dfd9d3a36ffd369939be5d3e1140ec58bc820f", + "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", + "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", + "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", "https://deno.land/std@0.76.0/encoding/base64.ts": "b1d8f99b778981548457ec74bc6273ad785ffd6f61b2233bd5b30925345b565d", "https://deno.land/std@0.76.0/encoding/hex.ts": "07a03ba41c96060a4ed4ba272e50b9e23f3c5b3839f4b069cdebc24d57434386", "https://deno.land/std@0.76.0/hash/_wasm/hash.ts": "005f64c4d9343ecbc91e0da9ae5e800f146c20930ad829bbb872c5c06bd89c5f", @@ -306,6 +331,65 @@ "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_utils.ts": "fa0e88cc4215b18554a7308e8e2ae3a12be0fb91c54d1473c54c530dbd4adfcb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/command.ts": "83cbece11c1459d5bc5add32c3cad0bf49e92c4ddd3ef00f22f80efdae30994e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_zsh_completions_generator.ts": "9df79fbac17a32b9645d01628c41a2bfd295d7976b87b0ae235f50a9c8975fbc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deps.ts": "a58ea2fa4e2ed9b39bb8dd8c35dd0498c74f05392517ff230a9a4d04c4c766b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/_help_generator.ts": "98619da83ff25523280a6fdcad89af3f13a6fafefc81b71f8230f3344b5ff2c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/upgrade_command.ts": "27191f4b1ce93581b6d5ee2fff6003fe4fca437f476ecb98b6eae92f2b4d0716", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_utils.ts": "25e519ce1f35acc8b43c75d1ca1c4ab591e7dab08327b7b408705b591e27d8bd", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deps.ts": "bed26afff36eeb25509440edec9d5d141b3411e08cc7a90e38a370969b5166bb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_layout.ts": "73a9bcb8a87b3a6817c4c9d2a31a21b874a7dd690ade1c64c9a1f066d628d626", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_utils.ts": "13390db3f11977b7a4fc1202fa8386be14696b475a7f46a65178354f9a6640b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/cell.ts": "65e3ee699c3cebeb4d4d44e8f156e37a8532a0f317359d73178a95724d3f9267", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/consume_words.ts": "369d065dbf7f15c664ea8523e0ef750fb952aea6d88e146c375e64aec9503052", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/deps.ts": "cbb896e8d7a6b5e3c2b9dda7d16638c202d9b46eb738c2dae1fa9480d8091486", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", "https://deno.land/x/dax@0.38.0/mod.ts": "3a5d7e6ac12547feec5d3c0c96717f14276891a3802fbbc73e5901e4f20eb08d", "https://deno.land/x/dax@0.38.0/src/command.ts": "f20135ef7188a0fc9f773d50e88775dee8653044a7f536fb2fb885b293c26ec4", "https://deno.land/x/dax@0.38.0/src/command_handler.ts": "a9e40f0f1ec57318e62904b5785fede82dcdf1101922ebb3ebfad8f1c4d9c8df", @@ -344,6 +428,9 @@ "https://deno.land/x/dax@0.38.0/src/result.ts": "719a9b4bc6bafeec785106744381cd5f37927c973334fcba6a33b6418fb9e7be", "https://deno.land/x/dax@0.38.0/src/shell.ts": "9b59a63de62003a0575f9c3300b5fff83cd7e5487582eceaa5f071a684d75e0e", "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", + "https://deno.land/x/diff_kit@v2.0.4/mod.ts": "3d88f6b8132feabe4c0863a5c65fdad05d44d52488de91205fc76abcbfd2eadd", + "https://deno.land/x/diff_kit@v2.0.4/private/diff.ts": "bc270998702ba73c8d2b1810feb54d3973615ce56a33d2ec64432e698f2f2613", + "https://deno.land/x/diff_kit@v2.0.4/private/diff_handler.ts": "2f96831bde217d6a84691abfe7d4580057aee4e9fe1cf753101a2eb703cef9aa", "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts": "c350ea5f32938e6dcb694df3761615f316d730dafc57440e9afd5f36f8e309fd", "https://deno.land/x/foras@v2.1.4/src/deno/mods/mod.ts": "cc099bbce378f3cdaa94303e8aff2611e207442e5ac2d5161aba636bb4a95b46", "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.js": "06f8875b456918b9671d52133f64f3047f1c95540feda87fdd4a55ba3d30091d", @@ -373,6 +460,31 @@ "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", "https://esm.sh/jszip@3.7.1": "f3872a819b015715edb05f81d973b5cd05d3d213d8eb28293ca5471fe7a71773", - "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9" + "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9", + "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", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/host/mod.ts": "2bc9f273262e1c4fb434b1a0389f24464f8b986816ce9480e8e2d63d910e8253", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/host/types.ts": "22c06b190172d08092717ad788ed04b050af58af0cf3f8c78b1511984101e9e4", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/main.ts": "8d6985e59db0b5baf67c9dc330bf8b25ad556341b9ef6088038e8ebb37ed75e5", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/mod.ts": "6aa0b765ce5684842ea531e026926836ffde7d2513e62457bffe9cb4ec7eb0df", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/ambient.ts": "25623410c535e2bfaf51fca1e582e7325a00a7690d5b5e763a12be9407f619cf", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/db.ts": "3f4541d6874c434f2f869774a17fd41c3d86914ed190d412e2f63f564b58ce95", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/mod.ts": "e38ad2d3599b6a5522da436b52e5945bb85cabba2aca27f633eae43e465b5794", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/sync.ts": "46447c2c51c085193f567ddcd2451b14bb33ee2d761edeb91a6153e2ba642f42", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/types.ts": "b3967d9d75def187b3b55f2b0b1357c9cb69a70e475a9280fc66717193b8b43c", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/worker.ts": "25c01e3afddd97d48af89d9c97a9a5188e7db09fceb26a69eac4dabacd8ac4fc", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/std.ts": "ddb2c134c080bb0e762a78f2f2edd69536991cc4257bd29a6fc95944b2f105a9", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/deno.ts": "f988a4d1062364b99272087fa0c7d54e699944ead3790c5b83140577bda089de", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/exec.ts": "7a07f2cce79fe16e86f0b74df6d57f0160bac75a8c6d58a03f2883a5ecccddf0", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/mod.ts": "0edbe1ce953a44b6b0fd45aa9c9dd52c11b12053eef21307eac3b24b6db4745e", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/types.ts": "536495a17c7a917bdd1c316ecc98ce2947b4959a713f92a175d372196dcaafc0", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/types.ts": "b44609942d7ad66c925c24485057c5b4b2ffcad20c0a94e14dc6af34cf9e8241", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/logger.ts": "86fdf651123d00ea1081bf8001ed9039cd41a79940e6ebadb8484952ab390e73", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/mod.ts": "1ee68d9390259c065144c10663f6e360d29aec36db2af38d02647e304eeeaedc", + "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623" } } diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 6e0b86c9..509afa1c 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -7,23 +7,23 @@ "configResolutions": { "95dbc2b8c604a5996b88c5b1b4fb0c10b3e0d9cac68f57eb915b012c44288e93": { "version": "v0.2.61", - "depConfigs": {}, + "buildDepConfigs": {}, "portRef": "act_ghrel@0.1.0" }, "076a5b8ee3bdc68ebf20a696378458465042bb7dc1e49ac2dc98e5fa0dab3e25": { "version": "3.7.0", - "depConfigs": { + "buildDepConfigs": { "cpy_bs_ghrel": { - "version": "3.12.1", - "depConfigs": { + "version": "3.12.3", + "buildDepConfigs": { "tar_aa": { - "version": "1.35", - "depConfigs": {}, + "version": "1.34", + "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, "zstd_aa": { "version": "v1.5.5,", - "depConfigs": {}, + "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0" } }, @@ -34,51 +34,34 @@ "packageName": "pre-commit" }, "84ecde630296f01e7cb8443c58d1596d668c357a0d9837c0a678b8a541ed0a39": { - "version": "3.12.1", - "depConfigs": { + "version": "3.12.3", + "buildDepConfigs": { "tar_aa": { - "version": "1.35", - "depConfigs": {}, + "version": "1.34", + "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, "zstd_aa": { "version": "v1.5.5,", - "depConfigs": {}, + "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0" } }, "portRef": "cpy_bs_ghrel@0.1.0" }, "9e3fa7742c431c34ae7ba8d1e907e50c937ccfb631fb4dcfb7a1773742abe267": { - "version": "1.35", - "depConfigs": {}, + "version": "1.34", + "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, "4f16c72030e922711abf15474d30e3cb232b18144beb73322b297edecfcdb86f": { "version": "v1.5.5,", - "depConfigs": {}, + "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0" }, - "c58811a17645c26b5a55a2de02f930945e04adf37e408846bf6b5c72bf707146": { - "version": "3.12.0", - "depConfigs": { - "tar_aa": { - "version": "1.35", - "depConfigs": {}, - "portRef": "tar_aa@0.1.0" - }, - "zstd_aa": { - "version": "v1.5.5,", - "depConfigs": {}, - "portRef": "zstd_aa@0.1.0" - } - }, - "portRef": "cpy_bs_ghrel@0.1.0", - "releaseTag": "20231002" - }, "a79698808eea53aedd8e83387b2f44e90a1a48d76193c5ccf0fc6efe29bd70f6": { "version": "v26.1", - "depConfigs": {}, + "buildDepConfigs": {}, "portRef": "protoc_ghrel@0.1.0" } } @@ -99,19 +82,19 @@ "ghjkEnvProvInstSet___main": { "installs": [ "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", - "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44", - "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" + "2a0176fec803325cc31d4a9b15f77f4e07938cc4", + "b6c49b375643a285e20b6ec0f7a692214bd0f392" ], - "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" + "allowedDeps": "3c71ccb92f3785a685b27d7b897fef4b80ad6b24" }, "ghjkEnvProvInstSet___test": { "installs": [ "aa103d26454710ca5d7f43358123341380389864", "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", - "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44", - "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c" + "2a0176fec803325cc31d4a9b15f77f4e07938cc4", + "b6c49b375643a285e20b6ec0f7a692214bd0f392" ], - "allowedDeps": "48a429761f3837562b097b47afe07601ba4ffca5" + "allowedDeps": "3c71ccb92f3785a685b27d7b897fef4b80ad6b24" } } } @@ -128,6 +111,7 @@ "config": { "envs": { "main": { + "desc": "the default default environment.", "provides": [ { "ty": "ghjk.ports.InstallSetRef", @@ -165,7 +149,7 @@ "moduleSpecifier": "file:///ports/act.ts" } }, - "9283b97b5499e8da4dcfb7f14c1306c25e8e8a44": { + "2a0176fec803325cc31d4a9b15f77f4e07938cc4": { "port": { "ty": "denoWorker@v1", "name": "pipi_pypi", @@ -190,7 +174,7 @@ "aarch64-android" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "cpy_bs_ghrel" } @@ -199,7 +183,7 @@ }, "packageName": "pre-commit" }, - "7d7b0f4b9ec5375688fceab016687f3ac3fbc94c": { + "b6c49b375643a285e20b6ec0f7a692214bd0f392": { "port": { "ty": "denoWorker@v1", "name": "cpy_bs_ghrel", @@ -212,7 +196,7 @@ "aarch64-windows" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "tar_aa" }, @@ -221,8 +205,7 @@ } ], "moduleSpecifier": "file:///ports/cpy_bs.ts" - }, - "releaseTag": "20231002" + } }, "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2": { "manifest": { @@ -354,7 +337,7 @@ "portRef": "zstd_aa@0.1.0" } }, - "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d": { + "5314c90de340dfd1ef21421dcbdcba726b4d03b9": { "manifest": { "ty": "denoWorker@v1", "name": "rustup_rustlang", @@ -369,7 +352,7 @@ "x86_64-netbsd" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "git_aa" } @@ -385,7 +368,7 @@ "portRef": "rustup_rustlang@0.1.0" } }, - "9fc8f32a0f79253defdb8845e2d6a4df69b526b9": { + "ebba9b42698f7f065a359575f195153ca1adba7b": { "manifest": { "ty": "denoWorker@v1", "name": "rust_rustup", @@ -410,7 +393,7 @@ "aarch64-android" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "rustup_rustlang" } @@ -457,7 +440,7 @@ "portRef": "pnpm_ghrel@0.1.0" } }, - "a36b37f4eda81bf51a50d00362637690c7fea473": { + "16e0e281e0f961fcc805896fc146d2c011c8d694": { "manifest": { "ty": "denoWorker@v1", "name": "asdf_plugin_git", @@ -470,7 +453,7 @@ "x86_64-windows" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "git_aa" } @@ -486,7 +469,7 @@ "portRef": "asdf_plugin_git@0.1.0" } }, - "5843605c861f0b7307c0192a1628c3823fe28ed9": { + "65ca6fb1b829a92d6423b3ea701d9602d84cf6f8": { "manifest": { "ty": "denoWorker@v1", "name": "node_org", @@ -499,7 +482,7 @@ "x86_64-windows" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "tar_aa" } @@ -510,7 +493,7 @@ "portRef": "node_org@0.1.0" } }, - "7a33163826283c47b52964a23b87a4762662c746": { + "d82c92542f0ed9c49a0383922c1d968ba88f0c4b": { "manifest": { "ty": "denoWorker@v1", "name": "cpy_bs_ghrel", @@ -523,7 +506,7 @@ "aarch64-windows" ], "version": "0.1.0", - "deps": [ + "buildDeps": [ { "name": "tar_aa" }, @@ -537,19 +520,19 @@ "portRef": "cpy_bs_ghrel@0.1.0" } }, - "48a429761f3837562b097b47afe07601ba4ffca5": { + "3c71ccb92f3785a685b27d7b897fef4b80ad6b24": { "tar_aa": "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2", "git_aa": "9d26d0d90f6ecdd69d0705a042b01a344aa626ee", "curl_aa": "3c447f912abf18883bd05314f946740975ee0dd3", "unzip_aa": "dfb0f5e74666817e6ab8cbceca0c9da271142bca", "zstd_aa": "d9122eff1fe3ef56872e53dae725ff3ccb37472e", - "rustup_rustlang": "8f14cde4f25c276d5e54538d91a6ac6d3eec3e8d", - "rust_rustup": "9fc8f32a0f79253defdb8845e2d6a4df69b526b9", + "rustup_rustlang": "5314c90de340dfd1ef21421dcbdcba726b4d03b9", + "rust_rustup": "ebba9b42698f7f065a359575f195153ca1adba7b", "cargo_binstall_ghrel": "45999e7561d7f6a661191f58ee35e67755d375e0", "pnpm_ghrel": "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7", - "asdf_plugin_git": "a36b37f4eda81bf51a50d00362637690c7fea473", - "node_org": "5843605c861f0b7307c0192a1628c3823fe28ed9", - "cpy_bs_ghrel": "7a33163826283c47b52964a23b87a4762662c746" + "asdf_plugin_git": "16e0e281e0f961fcc805896fc146d2c011c8d694", + "node_org": "65ca6fb1b829a92d6423b3ea701d9602d84cf6f8", + "cpy_bs_ghrel": "d82c92542f0ed9c49a0383922c1d968ba88f0c4b" }, "aa103d26454710ca5d7f43358123341380389864": { "port": { diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ceab6301..fbeda562 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,6 +31,8 @@ jobs: with: deno-version: ${{ env.DENO_VERSION }} - uses: pre-commit/action@v3.0.1 + env: + SKIP: ghjk-resolve test-e2e: runs-on: "${{ matrix.os }}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9790a165..69817abc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,11 @@ repos: - commit-msg - repo: local hooks: + - id: ghjk-resolve + name: Ghjk resolve + language: system + entry: bash -c 'deno run --unstable -A main.ts p resolve' + pass_filenames: false - id: deno-fmt name: Deno format language: system diff --git a/README.md b/README.md index 3c75880a..c15ecf6e 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,139 @@ curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | In your project, create a configuration file `ghjk.ts`: ```ts +// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the ghjk object +// exported here. export { ghjk } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; -import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; +import { + install, + task, +} from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; import node from "https://raw.githubusercontent.com/metatypedev/ghjk/main/ports/node.ts"; -ghjk.install( +// install programs into your env +install( node({ version: "14.17.0" }), ); + +// write simple scripts and execute them through +// `$ ghjk x greet` +task("greet", async ({ $, argv: [name] }) => { + await $`echo Hello ${name}!`; +}); +``` + +Use the following command to then access your environment: + +```shell +$ ghjk sync +``` + +### Environments + +Ghjk is primarily configured through constructs called "environments" or "envs" +for short. They serve as recipes for making reproducable (mostly) posix shells. + +```ts +export { ghjk } from "https://raw.githubusercontent.com/metatypedev/ghjk/mod.ts"; +import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/mod.ts"; +import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/ports/mod.ts"; + +// top level `install`s go to the `main` env +ghjk.install(ports.protoc()); +ghjk.install(ports.rust()); + +// the previous block is equivalent to +ghjk.env("main", { + installs: [ + ports.protoc(), + ports.rust(), + ], +}); + +ghjk.env("dev", { + // by default, all envs are additively based on `main` + // pass false here to make env indiependent. + base: false, + // envs can specify standard env vars + vars: { CARGO_TARGET_DIR: "my_target" }, + installs: [ + ports.cargobi({ crateName: "cargo-insta" }), + ports.act(), + ], +}); + +ghjk.env({ + name: "docker", + desc: "for Dockerfile usage", + // NOTE: env references are order-independent + base: "ci", + installs: [ + ports.cargobi({ crateName: "cargo-chef" }), + ports.zstd(), + ], +}); + +// builder syntax is also availaible +ghjk.env("ci") + .var("CI", "1") + .install( + ports.opentofu_ghrel(), + ); + +// each task describes it's own env as well +ghjk.task({ + name: "run", + base: "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. + +### Tasks + +TBD: this feature is still in development. + +### Secure configs + +Certain options are configured through the `secureConfig` object. + +```ts +import { env, stdSecureConfig } from "https://.../ghjk/mod.ts"; +import * as ports from "https://.../ports/mod.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", + // 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, +}); ``` ## Development diff --git a/check.ts b/check.ts index e2341541..f82503a3 100755 --- a/check.ts +++ b/check.ts @@ -7,8 +7,10 @@ import { $ } from "./utils/mod.ts"; const files = (await Array.fromAsync( $.path(import.meta.url).parentOrThrow().expandGlob("**/*.ts", { exclude: [ + "play.ts", ".ghjk/**", ".deno-dir/**", + "vendor/**", ], }), )).map((ref) => ref.path.toString()); diff --git a/deno.jsonc b/deno.jsonc index 801f17cc..dafb6678 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,6 @@ { "tasks": { - "test": "GHJK_LOG=debug deno test --parallel --unstable-worker-options --unstable-kv -A tests/*", + "test": "GHJK_LOG=info deno test --parallel --unstable-worker-options --unstable-kv -A tests/*", "cache": "deno cache deps/*", "check": "deno run -A check.ts" }, @@ -8,16 +8,22 @@ "exclude": [ "**/*.md", ".ghjk/**", - ".deno-dir/**" + ".deno-dir/**", + "vendor/**" ] }, "lint": { "exclude": [ ".deno-dir/**", "ghjk.ts", - "play.ts" + "play.ts", + "vendor/**" ], "rules": { + "include": [ + "no-console", + "no-sync-fn-in-async-fn" + ], "exclude": [ "no-explicit-any" ] diff --git a/deno.lock b/deno.lock index b2acfc4a..60d8a39e 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,108 @@ { "version": "3", + "packages": { + "specifiers": { + "jsr:@david/dax@0.40.1": "jsr:@david/dax@0.40.1", + "jsr:@david/which@0.3": "jsr:@david/which@0.3.0", + "jsr:@ghjk/dax@0.40.2-alpha-ghjk": "jsr:@ghjk/dax@0.40.2-alpha-ghjk", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", + "npm:@types/node": "npm:@types/node@18.16.19", + "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" + }, + "jsr": { + "@david/dax@0.40.1": { + "integrity": "0c71d32a0484d3904f586417995f8ec26d45144f0eba95d3e5bb03b640b6df59", + "dependencies": [ + "jsr:@david/which@0.3", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@david/which@0.3.0": { + "integrity": "6bdb62c40ac90edcf328e854fa8103a8db21e7c326089cbe3c3a1cf7887d3204" + }, + "@ghjk/dax@0.40.2-alpha-ghjk": { + "integrity": "87bc93e9947779cb2f3922fe277e21ea8c716de804b2627f80ba9e7bc3d0d019", + "dependencies": [ + "jsr:@david/which@0.3", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/bytes@0.221.0": { + "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/io@0.221.0": { + "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/bytes@^0.221.0" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + }, + "@std/streams@0.221.0": { + "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", + "dependencies": [ + "jsr:@std/io@^0.221.0" + ] + } + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, + "zod-validation-error@3.1.0_zod@3.23.3": { + "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", + "dependencies": { + "zod": "zod@3.23.3" + } + }, + "zod-validation-error@3.2.0_zod@3.23.3": { + "integrity": "sha512-cYlPR6zuyrgmu2wRTdumEAJGuwI7eHVHGT+VyneAQxmRAKtGRL1/7pjz4wfLhz4J05f5qoSZc3rGacswgyTjjw==", + "dependencies": { + "zod": "zod@3.23.3" + } + }, + "zod@3.23.3": { + "integrity": "sha512-tPvq1B/2Yu/dh2uAIH2/BhUlUeLIUvAjr6dpL/75I0pCYefHgjhXk1o1Kob3kTU8C7yU1j396jFHlsVWFi9ogg==", + "dependencies": {} + } + } + }, "remote": { "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", @@ -19,12 +122,6 @@ "https://deno.land/std@0.120.0/crypto/mod.ts": "5760510eaa0b250f78cce81ce92d83cf8c40e9bb3c3efeedd4ef1a5bb0801ef4", "https://deno.land/std@0.120.0/encoding/ascii85.ts": "b42b041e9c668afa356dd07ccf69a6b3ee49b9ae080fdf3b03f0ac3981f4d1e6", "https://deno.land/std@0.120.0/encoding/base64.ts": "0b58bd6477214838bf711eef43eac21e47ba9e5c81b2ce185fe25d9ecab3ebb3", - "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", - "https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7", - "https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9", - "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", "https://deno.land/std@0.213.0/archive/_common.ts": "85edd5cdd4324833f613c1bc055f8e2f935cc9229c6b3044421268d9959997ef", "https://deno.land/std@0.213.0/archive/untar.ts": "7677c136f2188cd8c33363ccaaee6e77d4ca656cca3e2093d08de8f294d4353d", "https://deno.land/std@0.213.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", @@ -281,108 +378,79 @@ "https://deno.land/std@0.213.0/url/join.ts": "00c7e9088cafaa24963ce4081119e58b3afe2c58f033701383f359ea02620dd2", "https://deno.land/std@0.213.0/url/mod.ts": "e2621f6a0db6fdbe7fbbd240064095bb203014657e5e1ab81db1c44d80dce6c9", "https://deno.land/std@0.213.0/url/normalize.ts": "6328c75df0fab300f74bc4a1c255062a0db882240e15ab646606d0009e7e40d7", + "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", + "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", + "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", + "https://deno.land/std@0.221.0/console/_run_length.ts": "7da8642a0f4f41ac27c0adb1364e18886be856c1d08c5cce6c6b5c00543c8722", + "https://deno.land/std@0.221.0/console/unicode_width.ts": "d92f085c0ab9c7ab171e4e7862dfd9d3a36ffd369939be5d3e1140ec58bc820f", + "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", + "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", + "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", "https://deno.land/std@0.76.0/encoding/base64.ts": "b1d8f99b778981548457ec74bc6273ad785ffd6f61b2233bd5b30925345b565d", "https://deno.land/std@0.76.0/encoding/hex.ts": "07a03ba41c96060a4ed4ba272e50b9e23f3c5b3839f4b069cdebc24d57434386", "https://deno.land/std@0.76.0/hash/_wasm/hash.ts": "005f64c4d9343ecbc91e0da9ae5e800f146c20930ad829bbb872c5c06bd89c5f", "https://deno.land/std@0.76.0/hash/_wasm/wasm.js": "5ac48aa0c3931d7f31dba628be5ab0aa4e786354197eb4d7d0583f9b50be1397", "https://deno.land/std@0.76.0/hash/hasher.ts": "099c9e2a91b9f106b9f01379705e17e7d9de392ee1ea2b8684a2adfa82ac3bfc", "https://deno.land/std@0.76.0/hash/mod.ts": "e764a6a9ab2f5519a97f928e17cc13d984e3dd5c7f742ff9c1c8fb3114790f0c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_errors.ts": "12d513ff401020287a344e0830e1297ce1c80c077ecb91e0ac5db44d04a6019c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_utils.ts": "3c88ff4f36eba298beb07de08068fdce5e5cb7b9d82c8a319f09596d8279be64", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts": "ae690745759524082776b7f271f66d5b93933170b1b132f888bd4ac12e9fdd7d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_zsh_completions_generator.ts": "c74525feaf570fe8c14433c30d192622c25603f1fc64694ef69f2a218b41f230", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deps.ts": "7473ebd5625bf901becd7ff80afdde3b8a50ae5d1bbfa2f43805cfacf4559d5a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/_help_generator.ts": "532dd4a928baab8b45ce46bb6d20e2ebacfdf3da141ce9d12da796652b1de478", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_utils.ts": "fd48d1a524a42e72aa3ad2eec858a92f5a00728d306c7e8436fba6c34314fee6", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/cell.ts": "1ffabd43b6b7fddfac9625cb0d015532e144702a9bfed03b358b79375115d06b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/consume_words.ts": "456e75755fdf6966abdefb8b783df2855e2a8bad6ddbdf21bd748547c5fc1d4b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", - "https://deno.land/x/dax@0.38.0/mod.ts": "3a5d7e6ac12547feec5d3c0c96717f14276891a3802fbbc73e5901e4f20eb08d", - "https://deno.land/x/dax@0.38.0/src/command.ts": "f20135ef7188a0fc9f773d50e88775dee8653044a7f536fb2fb885b293c26ec4", - "https://deno.land/x/dax@0.38.0/src/command_handler.ts": "a9e40f0f1ec57318e62904b5785fede82dcdf1101922ebb3ebfad8f1c4d9c8df", - "https://deno.land/x/dax@0.38.0/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", - "https://deno.land/x/dax@0.38.0/src/commands/cat.ts": "a136e9fe729d6b89c9bab469e6367f557bcddf3a4a3240b2ac280ff6da540b88", - "https://deno.land/x/dax@0.38.0/src/commands/cd.ts": "3d70605c6f8606008072f52763dbf4a979fa501975d006cf7f50eed0576936ab", - "https://deno.land/x/dax@0.38.0/src/commands/cp_mv.ts": "d57102f05f8eb6fb8f705e532a0e01c0dc7ba960c1e0828d4a5bef7ff411215f", - "https://deno.land/x/dax@0.38.0/src/commands/echo.ts": "8ca19f63779f8fa9cf2a29e21bdb31cfd6a3a09a820e5a83d6244325dea5f360", - "https://deno.land/x/dax@0.38.0/src/commands/exit.ts": "ef83eefb99270872ac679e38cee9aec345da9a345a3873fe6660f05aa577f937", - "https://deno.land/x/dax@0.38.0/src/commands/export.ts": "c10d1dc6a45fd00e40afa6b19d7ecd29d09333f422b5b0fc75863baf13350969", - "https://deno.land/x/dax@0.38.0/src/commands/mkdir.ts": "828a2d356fcff05d022f0e5ef76ed4a899b5370485fa4144fe378040a0f05aef", - "https://deno.land/x/dax@0.38.0/src/commands/printenv.ts": "4fc09ecf88e35bc9d810e3f45d1d8e808613e73701466ca6e48fca8d1810a48a", - "https://deno.land/x/dax@0.38.0/src/commands/pwd.ts": "6507d70bf02026bde8f58da166c37cdc2f7e1fda807b003f096aab077b866ee5", - "https://deno.land/x/dax@0.38.0/src/commands/rm.ts": "43ef496c34b722d007b945232d51273fcc6d7315f6198f6a6291bb7151941426", - "https://deno.land/x/dax@0.38.0/src/commands/sleep.ts": "413bacfd3bebf2a1397cda223776baadef8596f40558d6c2686ffd9b6ad80e54", - "https://deno.land/x/dax@0.38.0/src/commands/test.ts": "b0f56b3d1d038b47fe826bb3dab056746aefe12df6222e29150e7e6f78a51d9c", - "https://deno.land/x/dax@0.38.0/src/commands/touch.ts": "40a0292e5e4f35c057ac50445a124703355d2955a25b53a223aebf0b3b016e4e", - "https://deno.land/x/dax@0.38.0/src/commands/unset.ts": "1ffec8b32bbac8ef7b90b2ba1fc4d9339d3563ef8e302b14d119f9c220564985", - "https://deno.land/x/dax@0.38.0/src/common.ts": "37449926d3bc874aac4e4ff4ea06d46251dc54ad0bbb5721c7eb4920e2d5b591", - "https://deno.land/x/dax@0.38.0/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", - "https://deno.land/x/dax@0.38.0/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", - "https://deno.land/x/dax@0.38.0/src/console/mod.ts": "de8af7d646f6cb222eee6560171993690247941b13ed9d757789d16f019d73ee", - "https://deno.land/x/dax@0.38.0/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", - "https://deno.land/x/dax@0.38.0/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", - "https://deno.land/x/dax@0.38.0/src/console/progress/interval.ts": "80188d980a27c2eb07c31324365118af549641442f0752fe7c3b0c91832e5046", - "https://deno.land/x/dax@0.38.0/src/console/progress/mod.ts": "dd9330c3edd1790d70808d043f417f0eaf80a4442a945545c38e47ce11e907b6", - "https://deno.land/x/dax@0.38.0/src/console/prompt.ts": "1ad65c8a5a27fb58ce6138f8ebefe2fca4cd12015fea550fbdc62f875d4b31f7", - "https://deno.land/x/dax@0.38.0/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", - "https://deno.land/x/dax@0.38.0/src/console/utils.ts": "24b840d4e55eba0d5b2f79337d2940d5f9456d4d6836f35316e6495b7cb827b4", - "https://deno.land/x/dax@0.38.0/src/deps.ts": "c1e16434a805285d27c30c70a825473f88117dfa7e1d308408db1b1ab4fe743f", - "https://deno.land/x/dax@0.38.0/src/lib/mod.ts": "c992db99c8259ae3bf2d35666585dfefda84cf7cf4e624e42ea2ac7367900fe0", - "https://deno.land/x/dax@0.38.0/src/lib/rs_lib.generated.js": "0a1a482c4387379106ef0da69534ebc5b0c2a1ec9f6dab76833fe84a7e6bbdf6", - "https://deno.land/x/dax@0.38.0/src/path.ts": "451589cc3ad49cab084c50ad0ec07f7e2492a20d2f0ee7cfd80ab36360e6aa55", - "https://deno.land/x/dax@0.38.0/src/pipes.ts": "bbfc7d6bf0f0bfc363daa2f4d3c5ebf17025d82c4114d5b0ea444cf69d805670", - "https://deno.land/x/dax@0.38.0/src/request.ts": "461e16f53367c73c0ec16091c2fd6cb97a219f6af07a3a2a10029139bf404879", - "https://deno.land/x/dax@0.38.0/src/result.ts": "719a9b4bc6bafeec785106744381cd5f37927c973334fcba6a33b6418fb9e7be", - "https://deno.land/x/dax@0.38.0/src/shell.ts": "9b59a63de62003a0575f9c3300b5fff83cd7e5487582eceaa5f071a684d75e0e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_utils.ts": "fa0e88cc4215b18554a7308e8e2ae3a12be0fb91c54d1473c54c530dbd4adfcb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/command.ts": "83cbece11c1459d5bc5add32c3cad0bf49e92c4ddd3ef00f22f80efdae30994e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_zsh_completions_generator.ts": "9df79fbac17a32b9645d01628c41a2bfd295d7976b87b0ae235f50a9c8975fbc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deps.ts": "a58ea2fa4e2ed9b39bb8dd8c35dd0498c74f05392517ff230a9a4d04c4c766b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/_help_generator.ts": "98619da83ff25523280a6fdcad89af3f13a6fafefc81b71f8230f3344b5ff2c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/upgrade_command.ts": "27191f4b1ce93581b6d5ee2fff6003fe4fca437f476ecb98b6eae92f2b4d0716", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_utils.ts": "25e519ce1f35acc8b43c75d1ca1c4ab591e7dab08327b7b408705b591e27d8bd", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deps.ts": "bed26afff36eeb25509440edec9d5d141b3411e08cc7a90e38a370969b5166bb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_layout.ts": "73a9bcb8a87b3a6817c4c9d2a31a21b874a7dd690ade1c64c9a1f066d628d626", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_utils.ts": "13390db3f11977b7a4fc1202fa8386be14696b475a7f46a65178354f9a6640b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/cell.ts": "65e3ee699c3cebeb4d4d44e8f156e37a8532a0f317359d73178a95724d3f9267", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/consume_words.ts": "369d065dbf7f15c664ea8523e0ef750fb952aea6d88e146c375e64aec9503052", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/deps.ts": "cbb896e8d7a6b5e3c2b9dda7d16638c202d9b46eb738c2dae1fa9480d8091486", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts": "c350ea5f32938e6dcb694df3761615f316d730dafc57440e9afd5f36f8e309fd", "https://deno.land/x/foras@v2.1.4/src/deno/mods/mod.ts": "cc099bbce378f3cdaa94303e8aff2611e207442e5ac2d5161aba636bb4a95b46", @@ -398,8 +466,6 @@ "https://deno.land/x/jszip@0.11.0/types.ts": "1528d1279fbb64dd118c371331c641a3a5eff2b594336fb38a7659cf4c53b2d1", "https://deno.land/x/object_hash@2.0.3/index.ts": "74b20a0065dc0066c60510174626db1d18e53ec966edb6f76fa33a67aa0c44e3", "https://deno.land/x/object_hash@2.0.3/mod.ts": "648559bcafb54b930d4b6a283cc2eef20afa54de471371a97c2ccf8116941148", - "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", - "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", @@ -413,6 +479,19 @@ "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", + "https://deno.land/x/zod@v3.23.5/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", + "https://deno.land/x/zod@v3.23.5/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.23.5/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.23.5/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.23.5/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.23.5/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", + "https://deno.land/x/zod@v3.23.5/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.23.5/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.23.5/helpers/util.ts": "3301a69867c9e589ac5b3bc4d7a518b5212858cd6a25e8b02d635c9c32ba331c", + "https://deno.land/x/zod@v3.23.5/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "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://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 ee7e2deb..01057a2b 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -2,4 +2,4 @@ export * from "./common.ts"; -export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; +export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; diff --git a/deps/common.ts b/deps/common.ts index ff55ede8..13627211 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -2,7 +2,8 @@ //! 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.22.4/mod.ts"; +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 * 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"; @@ -10,7 +11,9 @@ export * as std_fmt_colors from "https://deno.land/std@0.213.0/fmt/colors.ts"; export * as std_url from "https://deno.land/std@0.213.0/url/mod.ts"; export * as std_path from "https://deno.land/std@0.213.0/path/mod.ts"; export * as std_fs from "https://deno.land/std@0.213.0/fs/mod.ts"; -export * as dax from "https://deno.land/x/dax@0.38.0/mod.ts"; +// export * as dax from "jsr:@david/dax@0.40.1"; +export * as dax from "jsr:@ghjk/dax@0.40.2-alpha-ghjk"; + export * as jsonHash from "https://deno.land/x/json_hash@0.2.0/mod.ts"; export { default as objectHash } from "https://deno.land/x/object_hash@2.0.3/mod.ts"; export { default as deep_eql } from "https://deno.land/x/deep_eql@v5.0.1/index.js"; diff --git a/ghjk.ts b/ghjk.ts index c841dd78..cc7acabf 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -9,11 +9,12 @@ install(); install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], - ports.cpy_bs({ releaseTag: "20231002" }), + ports.cpy_bs({}), ); -env("test") - .install(ports.protoc()); +env("test", { + installs: [ports.protoc()], +}); export const secureConfig = stdSecureConfig({ enableRuntimes: true, diff --git a/host/deno.ts b/ghjkfiles/deno/mod.ts similarity index 97% rename from host/deno.ts rename to ghjkfiles/deno/mod.ts index 5d055528..4b566ceb 100644 --- a/host/deno.ts +++ b/ghjkfiles/deno/mod.ts @@ -1,6 +1,6 @@ //! this loads the ghjk.ts module and provides a program for it -import { std_url } from "../deps/common.ts"; +import { std_url } from "../../deps/common.ts"; export type DriverRequests = { ty: "serialize"; diff --git a/host/worker.ts b/ghjkfiles/deno/worker.ts similarity index 97% rename from host/worker.ts rename to ghjkfiles/deno/worker.ts index 55c24066..c53be849 100644 --- a/host/worker.ts +++ b/ghjkfiles/deno/worker.ts @@ -7,7 +7,7 @@ // modify the Deno namespace before anyone touches it // NOTE: only import types -import type { DriverRequests, DriverResponse } from "./deno.ts"; +import type { DriverRequests, DriverResponse } from "./mod.ts"; self.onmessage = onMsg; @@ -34,9 +34,7 @@ async function onMsg(msg: MessageEvent) { async function serializeConfig(uri: string, envVars: Record) { const shimHandle = shimDenoNamespace(envVars); - const { setup: setupLogger } = await import( - "../utils/logger.ts" - ); + const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(uri); const rawConfig = await mod.ghjk.getConfig(mod.secureConfig); diff --git a/ghjkfiles/mod.ts b/ghjkfiles/mod.ts new file mode 100644 index 00000000..1885cd3f --- /dev/null +++ b/ghjkfiles/mod.ts @@ -0,0 +1,603 @@ +// NOTE: avoid adding sources of randomness +// here to make the resulting config reasonably stable +// across serializaiton. No random identifiers. + +// ports specific imports +import portsValidators from "../modules/ports/types.ts"; +import type { + AllowedPortDep, + InstallConfigFat, + InstallSet, + InstallSetRefProvision, + PortsModuleConfigHashed, + PortsModuleSecureConfig, +} from "../modules/ports/types.ts"; +import logger from "../utils/logger.ts"; +import { + $, + defaultCommandBuilder, + Path, + thinInstallConfig, + unwrapParseRes, +} 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"; +// host +import type { SerializedConfig } from "../host/types.ts"; +import * as std_modules from "../modules/std.ts"; +// tasks +import { dax, jsonHash, objectHash } from "../deps/common.ts"; +// WARN: this module has side-effects and only ever import +// types from it +import type { ExecTaskArgs } from "../modules/tasks/deno.ts"; +import { TasksModuleConfig } from "../modules/tasks/types.ts"; +// envs +import { + EnvRecipe, + EnvsModuleConfig, + WellKnownProvision, +} from "../modules/envs/types.ts"; + +export type EnvDefArgs = { + name: string; + installs?: InstallConfigFat[]; + allowedPortDeps?: AllowedPortDep[]; + /* + * If true or not set, will base the task's env on top + * of the default env (usually `main`). If false, will build on + * top of a new env. If given a string, will use the identified env as a base + * for the task env. + */ + base?: string | boolean; + desc?: string; + vars?: Record; +}; + +export type TaskFnArgs = { + $: dax.$Type; + argv: string[]; + env: Record; +}; + +export type TaskFn = (args: TaskFnArgs) => Promise | any; + +/* + * Configuration for a task. + */ +export type TaskDefArgs = { + name: string; + fn: TaskFn; + desc?: string; + dependsOn?: string[]; + workingDir?: string | Path; + envVars?: Record; + allowedPortDeps?: AllowedPortDep[]; + installs?: InstallConfigFat[]; + base?: string | boolean; +}; + +export class GhjkfileBuilder { + #installSets = new Map(); + #tasks = {} as Record; + #bb = new Map(); + #seenEnvs: Record = {}; + + addInstall(setId: string, configUnclean: InstallConfigFat) { + const config = unwrapParseRes( + portsValidators.installConfigFat.safeParse(configUnclean), + { + config: configUnclean, + }, + `error parsing InstallConfig`, + ); + + const set = this.#getSet(setId); + set.installs.push(config); + logger().debug("install added", config); + } + + setAllowedPortDeps(setId: string, deps: AllowedPortDep[]) { + const set = this.#getSet(setId); + set.allowedDeps = Object.fromEntries( + deps.map(( + dep, + ) => [dep.manifest.name, dep]), + ); + } + + addTask(args: TaskDefArgs) { + // NOTE: we make sure the env base declared here exists + // this call is necessary to make sure that a `task` can + // 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 }); + } + + this.#tasks[args.name] = { + ...args, + name, + }; + return args.name; + } + + addEnv(args: EnvDefArgs) { + let env = this.#seenEnvs[args.name]?.[0]; + if (!env) { + let finalizer: EnvFinalizer; + 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.installs) { + env.install(...args.installs); + } + if (args.allowedPortDeps) { + env.allowedPortDeps(args.allowedPortDeps); + } + if (args.desc) { + env.desc(args.desc); + } + if (args.vars) { + env.vars(args.vars); + } + return env; + } + + async execTask( + { name, workingDir, envVars, argv }: ExecTaskArgs, + ) { + const task = this.#tasks[name]; + if (!task) { + throw new Error(`no task defined under "${name}"`); + } + const custom$ = $.build$({ + commandBuilder: defaultCommandBuilder().env(envVars).cwd(workingDir), + }); + await task.fn({ argv, env: envVars, $: custom$ }); + } + + toConfig( + { defaultEnv, defaultBaseEnv, secureConfig }: { + defaultEnv: string; + defaultBaseEnv: string; + secureConfig: PortsModuleSecureConfig | undefined; + }, + ) { + try { + const envsConfig = this.#processEnvs( + defaultEnv, + defaultBaseEnv, + ); + const tasksConfig = this.#processTasks(envsConfig, defaultBaseEnv); + const portsConfig = this.#processInstalls( + secureConfig?.masterPortDepAllowList ?? stdDeps(), + ); + + const config: SerializedConfig = { + modules: [{ + id: std_modules.ports, + config: portsConfig, + }, { + id: std_modules.tasks, + config: tasksConfig, + }, { + id: std_modules.envs, + config: envsConfig, + }], + blackboard: Object.fromEntries(this.#bb.entries()), + }; + return config; + } catch (cause) { + throw new Error(`error constructing config for serialization`, { cause }); + } + } + + #getSet(setId: string) { + let set = this.#installSets.get(setId); + if (!set) { + set = { installs: [], allowedDeps: {} }; + this.#installSets.set(setId, set); + } + return set; + } + + #addToBlackboard(inp: unknown) { + // jsonHash.digest is async + const hash = objectHash(jsonHash.canonicalize(inp as jsonHash.Tree)); + + if (!this.#bb.has(hash)) { + this.#bb.set(hash, inp); + } + return hash; + } + + // this processes the defined envs, normalizing dependency (i.e. "envBase") + // relationships to produce the standard EnvsModuleConfig + #processEnvs( + defaultEnv: string, + defaultBaseEnv: string, + ) { + const all = {} as Record< + string, + ReturnType & { envBaseResolved: null | string } + >; + const indie = [] as string[]; + const revDeps = new Map(); + for ( + const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) + ) { + const final = finalizer(); + const { name, base } = final; + const envBaseResolved = typeof base === "string" + ? base + : base + ? defaultBaseEnv + : null; + all[name] = { ...final, envBaseResolved }; + if (envBaseResolved) { + let parentRevDeps = revDeps.get(envBaseResolved); + if (!parentRevDeps) { + parentRevDeps = []; + revDeps.set(envBaseResolved, parentRevDeps); + } + parentRevDeps.push(final.name); + } else { + indie.push(name); + } + } + const processed = {} as Record< + string, + { installSetId?: string; vars: Record } + >; + const out: EnvsModuleConfig = { envs: {}, defaultEnv }; + const workingSet = [...indie]; + while (workingSet.length > 0) { + const item = workingSet.pop()!; + const final = all[item]; + + const base = final.envBaseResolved + ? processed[final.envBaseResolved] + : null; + + const processedVars = { + ...(base?.vars ?? {}), + ...final.vars, + }; + + let processedInstallSetId: string | undefined; + { + const installSet = this.#installSets.get(final.installSetId); + if (installSet) { + // if base also has an install set + if (base?.installSetId) { + // merge the parent's installs into this one + const baseSet = this.#installSets.get( + base.installSetId, + )!; + const mergedInstallsSet = new Set([ + ...installSet.installs, + ...baseSet.installs, + ]); + installSet.installs = [...mergedInstallsSet.values()]; + for ( + const [key, val] of Object.entries(baseSet.allowedDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the parent + if (!installSet.allowedDeps[key]) { + installSet.allowedDeps[key] = val; + } + } + } + processedInstallSetId = final.installSetId; + } // if there's no install set found under the id + else { + // implies that the env has not ports explicitly configured + if (base) { + processedInstallSetId = base.installSetId; + } + } + } + processed[final.name] = { + installSetId: processedInstallSetId, + vars: processedVars, + }; + out.envs[final.name] = { + desc: final.desc, + provides: [ + ...Object.entries(processedVars).map(( + [key, val], + ) => { + const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; + return prov; + }), + ], + }; + if (processedInstallSetId) { + const prov: InstallSetRefProvision = { + ty: "ghjk.ports.InstallSetRef", + setId: processedInstallSetId, + }; + out.envs[final.name].provides.push(prov); + } + + const curRevDeps = revDeps.get(final.name); + if (curRevDeps) { + workingSet.push(...curRevDeps); + revDeps.delete(final.name); + } + } + return out; + } + + #processTasks(envsConfig: EnvsModuleConfig, defaultBaseEnv: string) { + const out: TasksModuleConfig = { + envs: {}, + tasks: {}, + }; + for ( + const [name, args] of Object + .entries( + this.#tasks, + ) + ) { + const { workingDir, desc, dependsOn, base } = args; + const envBaseResolved = typeof base === "string" + ? base + : base + ? defaultBaseEnv + : null; + + const envBaseRecipe = envBaseResolved + ? envsConfig.envs[envBaseResolved] + : null; + + const taskEnvRecipe: EnvRecipe = { + provides: [], + }; + + const taskInstallSet: InstallSet = { + installs: args.installs ?? [], + allowedDeps: Object.fromEntries( + (args.allowedPortDeps ?? []).map((dep) => [dep.manifest.name, dep]), + ), + }; + + const mergedEnvVars = args.envVars ?? {}; + if (envBaseRecipe) { + for ( + const prov of envBaseRecipe + .provides as ( + | WellKnownProvision + | InstallSetRefProvision + )[] + ) { + if (prov.ty == "posix.envVar") { + if (!mergedEnvVars[prov.key]) { + mergedEnvVars[prov.key] = prov.val; + } + } else if (prov.ty == "ghjk.ports.InstallSetRef") { + const baseSet = this.#installSets.get(prov.setId)!; + const mergedInstallsSet = new Set([ + ...taskInstallSet.installs, + ...baseSet.installs, + ]); + taskInstallSet.installs = [...mergedInstallsSet.values()]; + for ( + const [key, val] of Object.entries(baseSet.allowedDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the base + if (!taskInstallSet.allowedDeps[key]) { + taskInstallSet.allowedDeps[key] = val; + } + } + } else { + taskEnvRecipe.provides.push(prov); + } + } + } + if (taskInstallSet.installs.length > 0) { + const setId = `ghjkTaskInstSet___${name}`; + this.#installSets.set(setId, taskInstallSet); + const prov: InstallSetRefProvision = { + ty: "ghjk.ports.InstallSetRef", + setId, + }; + taskEnvRecipe.provides.push(prov); + } + + taskEnvRecipe.provides.push( + ...Object.entries(mergedEnvVars).map(( + [key, val], + ) => { + const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; + return prov; + }), + ); + + const envHash = objectHash( + jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), + ); + out.envs[envHash] = taskEnvRecipe; + + out.tasks[name] = { + name, + workingDir: typeof workingDir == "object" + ? workingDir.toString() + : workingDir, + desc, + dependsOn, + envHash, + }; + } + for (const [name, { dependsOn }] of Object.entries(out.tasks)) { + for (const depName of dependsOn ?? []) { + if (!out.tasks[depName]) { + throw new Error( + `task "${name}" depend on non-existent task "${depName}"`, + ); + } + } + } + + return out; + } + + #processInstalls(masterAllowList: AllowedPortDep[]) { + const out: PortsModuleConfigHashed = { + sets: {}, + }; + const masterPortDepAllowList = Object.fromEntries( + masterAllowList.map((dep) => [dep.manifest.name, dep] as const), + ); + for ( + const [setId, set] of this.#installSets.entries() + ) { + for (const [portName, _] of Object.entries(set.allowedDeps)) { + if (!masterPortDepAllowList[portName]) { + throw new Error( + `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, + ); + } + } + for (const [name, hash] of Object.entries(masterPortDepAllowList)) { + if (!set.allowedDeps[name]) { + set.allowedDeps[name] = hash; + } + } + out.sets[setId] = { + installs: set.installs.map((inst) => this.#addToBlackboard(inst)), + allowedDeps: this.#addToBlackboard(Object.fromEntries( + Object.entries(set.allowedDeps).map( + ([key, dep]) => [key, this.#addToBlackboard(dep)], + ), + )), + }; + } + return out; + } +} + +type EnvFinalizer = () => { + name: string; + installSetId: string; + base: string | boolean; + vars: Record; + desc?: string; +}; + +// this class will be exposed to users and thus features +// a contrived implementation of the `build`/`finalize` method +// all to avoid exposing the function in the public api +export class EnvBuilder { + #installSetId: string; + #file: GhjkfileBuilder; + #base: string | boolean = true; + #vars: Record = {}; + #desc?: string; + + constructor( + file: GhjkfileBuilder, + setFinalizer: (fin: EnvFinalizer) => void, + public name: string, + ) { + this.#file = file; + this.#installSetId = `ghjkEnvProvInstSet___${name}`; + setFinalizer(() => ({ + name: this.name, + installSetId: this.#installSetId, + base: this.#base, + vars: this.#vars, + desc: this.#desc, + })); + } + + base(base: string | boolean) { + this.#base = base; + return this; + } + + /* + * Provision a port install in the environment. + */ + install(...configs: InstallConfigFat[]) { + for (const config of configs) { + this.#file.addInstall(this.#installSetId, config); + } + return this; + } + + /* + * This is treated as a single set and will replace previously any configured set. + */ + allowedPortDeps(deps: AllowedPortDep[]) { + this.#file.setAllowedPortDeps(this.#installSetId, deps); + return this; + } + + /* + * Add an environment variable. + */ + var(key: string, val: string) { + this.vars({ [key]: val }); + return this; + } + + /* + * Add multiple environment variable. + */ + vars(envVars: Record) { + Object.assign(this.#vars, envVars); + return this; + } + + /* + * Description of the environment. + */ + desc(str: string) { + this.#desc = str; + return this; + } +} + +export function stdSecureConfig( + args: { + additionalAllowedPorts?: PortsModuleSecureConfig["masterPortDepAllowList"]; + enableRuntimes?: boolean; + } & Pick, +): PortsModuleSecureConfig { + const { additionalAllowedPorts, enableRuntimes = false } = args; + const out: PortsModuleSecureConfig = { + masterPortDepAllowList: [ + ...stdDeps({ enableRuntimes }), + ...additionalAllowedPorts ?? [], + ], + }; + return out; +} + +export function stdDeps(args = { enableRuntimes: false }) { + const out: AllowedPortDep[] = [ + ...Object.values(std_ports.map), + ]; + if (args.enableRuntimes) { + out.push( + ...[ + node.default(), + cpy.default(), + ].map((fatInst) => { + return portsValidators.allowedPortDep.parse({ + manifest: fatInst.port, + defaultInst: thinInstallConfig(fatInst), + }); + }), + ); + } + return out; +} diff --git a/host/mod.ts b/host/mod.ts index 62a4b5e7..d56afa84 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -1,4 +1,10 @@ -import { cliffy_cmd, deep_eql, jsonHash, zod } from "../deps/cli.ts"; +import { + cliffy_cmd, + deep_eql, + jsonHash, + zod, + zod_val_err, +} from "../deps/cli.ts"; import logger, { isColorfulTty } from "../utils/logger.ts"; import { @@ -6,15 +12,16 @@ import { bufferHashHex, Json, objectHashHex, - PathRef, + Path, stringHashHex, } from "../utils/mod.ts"; import validators, { SerializedConfig } from "./types.ts"; import * as std_modules from "../modules/std.ts"; -import * as deno from "./deno.ts"; +import * as denoFile from "../ghjkfiles/deno/mod.ts"; import type { ModuleBase } from "../modules/mod.ts"; import { GhjkCtx } from "../modules/types.ts"; import { serializePlatform } from "../modules/ports/types/platform.ts"; +import { DePromisify } from "../port.ts"; export interface CliArgs { ghjkShareDir: string; @@ -28,93 +35,139 @@ type HostCtx = { export async function cli(args: CliArgs) { const ghjkShareDir = $.path(args.ghjkShareDir).resolve().normalize() .toString(); + // items to run at end of function + const defer = [] as (() => Promise)[]; - const subcmds = { - print: new cliffy_cmd.Command() - .description("Emit different discovered and built values to stdout.") - .action(function () { - this.showHelp(); - }) - .command( - "share-dir-path", - new cliffy_cmd.Command() - .description("Print the path where ghjk is installed in.") - .action(function () { - console.log(ghjkShareDir); - }), - ), - deno: new cliffy_cmd.Command() - .description("Access the deno cli used by ghjk.") - .useRawArgs() - .action(async function (_, ...args) { - logger().debug(args); - await $.raw`${Deno.execPath()} ${args}` - .env("DENO_EXEC_PATH", Deno.execPath()); - }), - }; + const subcmds: Record = {}; + + let serializedConfig: object | undefined; + let ghjkDir: string | undefined; + let ghjkfilePath: string | undefined; + // most of the CLI is only avail if there's a + // ghjkfile detected if (args.ghjkfilePath) { - const ghjkfilePath = $.path(args.ghjkfilePath).resolve().normalize() + ghjkfilePath = $.path(args.ghjkfilePath).resolve().normalize() .toString(); - const ghjkDir = $.path(ghjkfilePath).parentOrThrow().join(".ghjk") + ghjkDir = $.path(ghjkfilePath).parentOrThrow().join(".ghjk") .toString(); logger().debug({ ghjkfilePath, ghjkDir }); const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, blackboard: new Map() }; const hcx = { fileHashMemoStore: new Map() }; - const { subCommands: configCommands, serializedConfig } = await readConfig( + const { + subCommands: configCommands, + serializedConfig: config, + writeLockFile, + } = await readConfig( gcx, hcx, ); - - Object.assign(subcmds, configCommands); - - subcmds.print = subcmds.print - .command( - "ghjk-dir-path", - new cliffy_cmd.Command() - .description("Print the path where ghjk is installed in.") - .action(function () { - console.log(ghjkDir); - }), - ) - .command( - "ghjkfile-path", - new cliffy_cmd.Command() - .description("Print the path of the ghjk.ts used") - .action(function () { - console.log(ghjkfilePath); - }), - ) - .command( - "config", - new cliffy_cmd.Command() - .description( - "Print the extracted ans serialized config from the ghjkfile", - ) - .action(function () { - console.log(Deno.inspect(serializedConfig, { - depth: 10, - colors: isColorfulTty(), - })); - }), - ); + serializedConfig = config; + // lock entries are generated across program usage + // so we defer writing it out until the end + defer.push(writeLockFile); + + for (const [cmdName, [cmd, src]] of Object.entries(configCommands)) { + const conflict = subcmds[cmdName]; + if (conflict) { + throw new Error( + `CLI command conflict under name "${cmdName}" from host and module "${src}"`, + ); + } + subcmds[cmdName] = cmd; + } } - let cmd: cliffy_cmd.Command = new cliffy_cmd.Command() + const root = new cliffy_cmd.Command() .name("ghjk") .version("0.1.1") // FIXME: better way to resolve version .description("Programmable runtime manager.") .action(function () { 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()); + }), + ) + .command( + "print", + new cliffy_cmd.Command() + .description("Emit different discovered and built values to stdout.") + .action(function () { + this.showHelp(); + }) + .command( + "share-dir-path", + new cliffy_cmd.Command() + .description("Print the path where ghjk is installed in.") + .action(function () { + if (!ghjkShareDir) { + throw new Error("no ghjkfile found."); + } + // deno-lint-ignore no-console + console.log(ghjkShareDir); + }), + ) + .command( + "ghjkdir-path", + new cliffy_cmd.Command() + .description("Print the path where ghjk is installed in.") + .action(function () { + if (!ghjkDir) { + throw new Error("no ghjkfile found."); + } + // deno-lint-ignore no-console + console.log(ghjkDir); + }), + ) + .command( + "ghjkfile-path", + new cliffy_cmd.Command() + .description("Print the path of the ghjk.ts used") + .action(function () { + if (!ghjkfilePath) { + throw new Error("no ghjkfile found."); + } + // deno-lint-ignore no-console + console.log(ghjkfilePath); + }), + ) + .command( + "config", + new cliffy_cmd.Command() + .description( + "Print the extracted ans serialized config from the ghjkfile", + ) + .action(function () { + if (!serializedConfig) { + throw new Error("no ghjkfile found."); + } + // deno-lint-ignore no-console + console.log(Deno.inspect(serializedConfig, { + depth: 10, + colors: isColorfulTty(), + })); + }), + ), + ); for (const [name, subcmd] of Object.entries(subcmds)) { - cmd = cmd.command(name, subcmd); + root.command(name, subcmd); } - await cmd - .command("completions", new cliffy_cmd.CompletionsCommand()) - .parse(Deno.args); + await root.parse(Deno.args); + await Promise.all(defer.map((fn) => fn())); } async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { @@ -136,15 +189,16 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { const lockFilePath = ghjkDirPath.join("lock.json"); const hashFilePath = ghjkDirPath.join("hash.json"); - const subCommands = {} as Record; + // command name to [cmd, source module id] + const subCommands = {} as Record; const lockEntries = {} as Record; - const curEnvVars = Deno.env.toObject(); - const foundLockObj = await readLockFile(lockFilePath); const foundHashObj = await readHashFile(hashFilePath); - const ghjkfileHash = await fileHashHex(hcx, configPath); + const ghjkfileHash = await fileDigestHex(hcx, configPath); + + const curEnvVars = Deno.env.toObject(); let configExt: SerializedConfigExt | null = null; // TODO: figure out cross platform lockfiles :O @@ -178,7 +232,7 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { const envHashesMatch = async () => { const oldHashes = foundHashObj!.envVarHashes; - const newHashes = await hashEnvVars(curEnvVars, [ + const newHashes = await envVarDigests(curEnvVars, [ ...Object.keys(oldHashes), ]); return deep_eql(oldHashes, newHashes); @@ -187,7 +241,7 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { const cwd = $.path(Deno.cwd()); const fileHashesMatch = async () => { const oldHashes = foundHashObj!.readFileHashes; - const newHashes = await hashFiles(hcx, [ + const newHashes = await fileDigests(hcx, [ ...Object.keys(oldHashes), ], cwd); return deep_eql(oldHashes, newHashes); @@ -204,14 +258,16 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { }; // avoid reserializing the config if // the ghjkfile and environment is _satisfcatorily_ - // similar + // similar. "cache validation" if ( + // NOTE: these are ordered by the amount effort it takes + // to check each foundHashObj && foundHashObj.ghjkfileHash == ghjkfileHash && platformMatch() && - await fileHashesMatch() && + await envHashesMatch() && await fileListingsMatch() && - await envHashesMatch() + await fileHashesMatch() ) { configExt = { config: foundLockObj.config, @@ -222,17 +278,13 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { } } + // configExt will be falsy if no lockfile was found + // or if it failed cache validation if (!configExt) { logger().info("serializing ghjkfile", configPath); configExt = await readAndSerializeConfig(hcx, configPath, curEnvVars); } - const newLockObj: zod.infer = { - version: "0", - platform: serializePlatform(Deno.build), - moduleEntries: {} as Record, - config: configExt.config, - }; const newHashObj: zod.infer = { version: "0", ghjkfileHash, @@ -240,7 +292,7 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { readFileHashes: configExt.readFileHashes, listedFiles: configExt.listedFiles, }; - const instances = []; + const instances = [] as [string, ModuleBase, unknown][]; for (const man of configExt.config.modules) { const mod = std_modules.map[man.id]; if (!mod) { @@ -250,60 +302,80 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { const pMan = await instance.processManifest( gcx, man, - newLockObj.config.blackboard, + configExt.config.blackboard, lockEntries[man.id], ); instances.push([man.id, instance, pMan] as const); - subCommands[man.id] = instance.command(gcx, pMan); - } - // generate the lock entries after *all* the modules - // are done processing their config to allow - // any shared stores to be properly populated - // e.g. the resolution memo store - newLockObj.moduleEntries = Object.fromEntries( - await Array.fromAsync( - instances.map( - async ( - [id, instance, pMan], - ) => [id, await instance.genLockEntry(gcx, pMan)], - ), - ), - ); - // avoid writing lockfile if nothing's changed - if (!foundLockObj || !deep_eql(newLockObj, foundLockObj)) { - await lockFilePath.writeJsonPretty(newLockObj); + for (const [cmdName, cmd] of Object.entries(instance.commands(gcx, pMan))) { + const conflict = subCommands[cmdName]; + if (conflict) { + throw new Error( + `CLI command conflict under name "${cmdName}" from modules "${man.id}" & "${ + conflict[1] + }"`, + ); + } + subCommands[cmdName] = [cmd, man.id]; + } } if (!foundHashObj || !deep_eql(newHashObj, foundHashObj)) { await hashFilePath.writeJsonPretty(newHashObj); } - return { subCommands, serializedConfig: configExt.config }; + + return { + subCommands, + serializedConfig: configExt.config, + async writeLockFile() { + const newLockObj: zod.infer = { + version: "0", + platform: serializePlatform(Deno.build), + moduleEntries: {} as Record, + config: configExt!.config, + }; + + // generate the lock entries after *all* the modules + // are done processing their config to allow + // any shared stores to be properly populated + // e.g. the resolution memo store + newLockObj.moduleEntries = Object.fromEntries( + await Array.fromAsync( + instances.map( + async ( + [id, instance, pMan], + ) => [id, await instance.genLockEntry(gcx, pMan)], + ), + ), + ); + // avoid writing lockfile if nothing's changed + if (!foundLockObj || !deep_eql(newLockObj, foundLockObj)) { + await lockFilePath.writeJsonPretty(newLockObj); + } + }, + }; } -type HashStore = Record; +type DigestsMap = Record; -type SerializedConfigExt = { - config: SerializedConfig; - envVarHashes: HashStore; - readFileHashes: HashStore; - listedFiles: string[]; -}; +type SerializedConfigExt = DePromisify< + ReturnType +>; async function readAndSerializeConfig( hcx: HostCtx, - configPath: PathRef, + configPath: Path, envVars: Record, -): Promise { +) { switch (configPath.extname()) { case "": logger().warn("config file has no extension, assuming deno config"); /* falls through */ case ".ts": { logger().debug("serializing ts config", configPath); - const res = await deno.getSerializedConfig( + const res = await denoFile.getSerializedConfig( configPath.toFileUrl().href, envVars, ); - const envVarHashes = await hashEnvVars(envVars, res.accessedEnvKeys); + const envVarHashes = await envVarDigests(envVars, res.accessedEnvKeys); const cwd = $.path(Deno.cwd()); const cwdStr = cwd.toString(); const listedFiles = res.listedFiles @@ -313,7 +385,7 @@ async function readAndSerializeConfig( // consider reading mtime of files when read by the serializer and comparing // them before hashing to make sure we get the same file // not sure what to do if it has changed though, re-serialize? - const readFileHashes = await hashFiles(hcx, res.readFiles, cwd); + const readFileHashes = await fileDigests(hcx, res.readFiles, cwd); return { config: validateRawConfig(res.config, configPath), @@ -335,15 +407,16 @@ async function readAndSerializeConfig( function validateRawConfig( raw: unknown, - configPath: PathRef, + configPath: Path, ): SerializedConfig { const res = validators.serializedConfig.safeParse(raw); if (!res.success) { - logger().error("zod error", res.error); - logger().error("serializedConf", raw); - throw new Error(`error parsing seralized config from ${configPath}`); + throw new Error( + `error parsing seralized config from ${configPath}: ${ + zod_val_err.fromZodError(res.error).toString() + }`, + ); } - return res.data; } @@ -354,18 +427,31 @@ const lockObjValidator = zod.object({ config: validators.serializedConfig, }); -type LockObject = zod.infer; - -async function readLockFile(lockFilePath: PathRef): Promise { - const raw = await lockFilePath.readMaybeJson(); - if (!raw) return null; - const res = lockObjValidator.safeParse(raw); - if (!res.success) { - throw new Error(`error parsing lockfile from ${lockFilePath}`, { - cause: res.error, - }); +/** + * The lock.json file stores the serialized config and some entries + * from modules. It's primary purpose is as a memo store to avoid + * re-serialization on each CLI invocation. + */ +async function readLockFile(lockFilePath: Path) { + const rawStr = await lockFilePath.readMaybeText(); + 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; + } catch (err) { + logger().error( + `error parsing lockfile from ${lockFilePath}: ${err.toString()}`, + ); + if (Deno.stderr.isTerminal() && await $.confirm("Discard lockfile?")) { + return; + } else { + throw err; + } } - return res.data; } const hashObjValidator = zod.object({ @@ -377,20 +463,33 @@ const hashObjValidator = zod.object({ // TODO: track listed dirs in case a `walk`ed directory has a new entry }); -async function readHashFile(hashFilePath: PathRef) { - const raw = await hashFilePath.readMaybeJson(); - if (!raw) return; - const res = hashObjValidator.safeParse(raw); - if (!res.success) { - throw new Error(`error parsing hashfile from ${hashObjValidator}`, { - cause: res.error, - }); +/** + * The hash.json file stores the digests of all external accesses + * of a ghjkfile during serialization. The primary purpose is to + * do "cache invalidation" on ghjkfiles, re-serializing them if + * any of the digests change. + */ +async function readHashFile(hashFilePath: Path) { + const rawStr = await hashFilePath.readMaybeText(); + 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; + } catch (err) { + logger().error( + `error parsing hashfile from ${hashObjValidator}: ${err.toString()}`, + ); + logger().warn("discarding invalid hashfile"); + return; } - return res.data; } -async function hashEnvVars(all: Record, accessed: string[]) { - const hashes = {} as HashStore; +async function envVarDigests(all: Record, accessed: string[]) { + const hashes = {} as DigestsMap; for (const key of accessed) { const val = all[key]; if (!val) { @@ -403,10 +502,10 @@ async function hashEnvVars(all: Record, accessed: string[]) { return hashes; } -async function hashFiles(hcx: HostCtx, readFiles: string[], cwd: PathRef) { +async function fileDigests(hcx: HostCtx, readFiles: string[], cwd: Path) { const cwdStr = cwd.toString(); - const readFileHashes = {} as HashStore; - for (const path of readFiles) { + const readFileHashes = {} as DigestsMap; + await Promise.all(readFiles.map(async (path) => { const pathRef = cwd.resolve(path); const relativePath = pathRef .toString() @@ -415,7 +514,7 @@ async function hashFiles(hcx: HostCtx, readFiles: string[], cwd: PathRef) { const stat = await pathRef.lstat(); if (stat) { const contentHash = (stat.isFile || stat.isSymlink) - ? await fileHashHex(hcx, pathRef) + ? await fileDigestHex(hcx, pathRef) : null; readFileHashes[relativePath] = await objectHashHex({ ...stat, @@ -424,11 +523,15 @@ async function hashFiles(hcx: HostCtx, readFiles: string[], cwd: PathRef) { } else { readFileHashes[relativePath] = null; } - } + })); return readFileHashes; } -function fileHashHex(hcx: HostCtx, path: PathRef) { +/** + * Returns the hash digest of a file. Makes use of a memo + * to dedupe work. + */ +function fileDigestHex(hcx: HostCtx, path: Path) { const absolute = path.resolve().toString(); let promise = hcx.fileHashMemoStore.get(absolute); if (!promise) { diff --git a/install.ts b/install.ts index 16db7830..0efef5b1 100755 --- a/install.ts +++ b/install.ts @@ -3,25 +3,33 @@ //! Install ghjk for the current user import "./setup_logger.ts"; -import { defaultInstallArgs, detectShell, install } from "./install/mod.ts"; +import { defaultInstallArgs, install } from "./install/mod.ts"; +import { detectShell } from "./utils/mod.ts"; if (import.meta.main) { const skipBinInstall = Deno.env.get("GHJK_INSTALL_SKIP_EXE"); const noLockfile = Deno.env.get("GHJK_INSTALL_NO_LOCKFILE"); + let shellsToHook = Deno.env.get("GHJK_INSTALL_HOOK_SHELLS") + ?.split(",") + ?.map((str) => str.trim()) + ?.filter((str) => str.length > 0); + if (!shellsToHook) { + const userShell = await detectShell(); + if (!userShell) { + throw new Error( + "Unable to detect user's shell. Set $GHJK_INSTALL_HOOK_SHELLS to an empty string if no shell hooks are desired.", + ); + } + shellsToHook = [userShell]; + } await install({ ...defaultInstallArgs, ghjkShareDir: Deno.env.get("GHJK_SHARE_DIR") ?? defaultInstallArgs.ghjkShareDir, skipExecInstall: !!skipBinInstall && skipBinInstall != "0" && skipBinInstall != "false", - shellsToHook: Deno.env.get("GHJK_INSTALL_HOOK_SHELLS") - ?.split(",") - ?.map((str) => str.trim()) - ?.filter((str) => str.length > 0) ?? - [ - await detectShell(), - ], + shellsToHook, ghjkExecInstallDir: Deno.env.get("GHJK_INSTALL_EXE_DIR") ?? defaultInstallArgs.ghjkExecInstallDir, ghjkExecDenoExec: Deno.env.get("GHJK_INSTALL_DENO_EXEC") ?? diff --git a/install/ghjk.sh b/install/ghjk.sh index 518f29ba..4ea0977c 100644 --- a/install/ghjk.sh +++ b/install/ghjk.sh @@ -3,6 +3,9 @@ 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 +# NOTE: avoid putting too much in here as the ghjk bin is meant +# to be optional. + # if ghjkfile var is set, set the GHJK_DIR overriding # any set by the user if [ -n "${GHJKFILE+x}" ]; then @@ -36,4 +39,4 @@ fi # we don't want to quote $lock_flag as it's not exactly a single # string param to deno # shellcheck disable=SC2086 -__DENO_EXEC__ run --unstable-kv --unstable-worker-options -A $lock_flag __MAIN_TS_URL__ "$@" +exec __DENO_EXEC__ run --unstable-kv --unstable-worker-options -A $lock_flag __MAIN_TS_URL__ "$@" diff --git a/install/hook.fish b/install/hook.fish index 282e552c..c5672389 100644 --- a/install/hook.fish +++ b/install/hook.fish @@ -1,9 +1,20 @@ -function ghjk_reload --on-variable PWD +function get_ctime_ts + switch (uname -s | tr '[:upper:]' '[:lower:]') + case "linux" + stat -c "%Y" $argv + case "darwin" + stat -f "%Sm" -t "%s" $argv + case "*" + stat -c "%Y" $argv + end +end + +function ghjk_reload --on-variable PWD --on-event ghjk_env_dir_change # --on-variable GHJK_ENV if set --query GHJK_CLEANUP_FISH # restore previous env eval $GHJK_CLEANUP_FISH + set --erase GHJK_CLEANUP_FISH end - set --erase GHJK_CLEANUP_FISH set --local cur_dir set --local local_ghjk_dir $GHJK_DIR @@ -35,26 +46,46 @@ function ghjk_reload --on-variable PWD end if test -n "$local_ghjk_dir" - # locate the default env - set --local default_env $local_ghjk_dir/envs/default - if test -d $default_env + # 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 # load the shim - . $default_env/loader.fish + . $active_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) # 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 $default_env/loader.fish -ot $cur_dir/ghjk.ts + if test $active_env_dir/activate.fish -ot $cur_dir/ghjk.ts set_color FF4500 - echo "[ghjk] Detected drift from default environment, please sync..." + if test $active_env = "default" + echo "[ghjk] Possible drift from default environment, please sync..." + else + echo "[ghjk] Possible drift from active environment ($active_env), please sync..." + end set_color normal end else set_color FF4500 - echo "[ghjk] No default environment found, please sync..." + if test $active_env = "default" + echo "[ghjk] Default environment not found, please sync..." + else + echo "[ghjk] Active environment ($active_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 + end +end + ghjk_reload diff --git a/install/hook.sh b/install/hook.sh index f612aa59..544d43d6 100644 --- a/install/hook.sh +++ b/install/hook.sh @@ -1,15 +1,28 @@ # shellcheck disable=SC2148 # keep this posix compatible as it supports bash and zsh +get_ctime_ts () { + case "$(uname -s | tr '[:upper:]' '[:lower:]')" in + "linux") + stat -c "%Y" "$1" + ;; + "darwin") + stat -f "%Sm" -t "%s" "$1" + ;; + "*") + stat -c "%Y" "$1" + ;; + esac +} + ghjk_reload() { if [ -n "${GHJK_CLEANUP_POSIX+x}" ]; then # restore previous env eval "$GHJK_CLEANUP_POSIX" + unset GHJK_CLEANUP_POSIX fi - unset GHJK_CLEANUP_POSIX - local cur_dir - local local_ghjk_dir="${GHJK_DIR:-}" + local_ghjk_dir="${GHJK_DIR:-}" # if $GHJKFILE is set, set the GHJK_DIR overriding # any set by the user if [ -n "${GHJKFILE+x}" ]; then @@ -38,21 +51,36 @@ ghjk_reload() { if [ -n "$local_ghjk_dir" ]; then # export GHJK_DIR - # locate the default env - default_env="$local_ghjk_dir/envs/default" - if [ -d "$default_env" ]; then + # locate the active env + active_env="${GHJK_ENV:-default}"; + active_env_dir="$local_ghjk_dir/envs/$active_env" + if [ -d "$active_env_dir" ]; then # load the shim # shellcheck source=/dev/null - . "$default_env/loader.sh" + . "$active_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")" + export GHJK_LAST_ENV_DIR + export GHJK_LAST_ENV_DIR_CTIME # FIXME: -ot not valid in POSIX # FIXME: this assumes ghjkfile is of kind ghjk.ts # shellcheck disable=SC3000-SC4000 - if [ "$default_env/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then - printf "\033[0;33m[ghjk] Detected drift from default environment, please sync...\033[0m\n" + if [ "$active_env_dir/activate.sh" -ot "$cur_dir/ghjk.ts" ]; then + if [ "$active_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" + fi + fi else - printf "\033[0;31m[ghjk] No default environment found, please sync...\033[0m\n" + if [ "$active_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" + fi fi fi } @@ -61,9 +89,14 @@ ghjk_reload() { export GHJK_LAST_PWD="$PWD" precmd() { - if [ "$GHJK_LAST_PWD" != "$PWD" ]; then + # 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 ghjk_reload export GHJK_LAST_PWD="$PWD" + # export GHJK_LAST_ENV="$GHJK_ENV" fi } diff --git a/install/mod.ts b/install/mod.ts index 0cbbf40c..219b497d 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -33,18 +33,6 @@ const getHooksVfs = async () => ({ ), }); -export async function detectShell(): Promise { - let path = Deno.env.get("SHELL"); - if (!path) { - try { - path = await $`ps -p ${Deno.ppid} -o comm=`.text(); - } catch (err) { - throw new Error(`cannot get parent process name: ${err}`); - } - } - return std_path.basename(path, ".exe").toLowerCase().trim(); -} - async function unpackVFS( vfs: Record, baseDir: string, @@ -101,24 +89,37 @@ interface InstallArgs { homeDir: string; ghjkShareDir: string; shellsToHook: string[]; - /// The mark used when adding the hook to the user's shell rcs - /// Override t + /** The mark used when adding the hook to the user's shell rcs. + * Override to allow multiple hooks in your rc. + */ shellHookMarker: string; - /// The ghjk bin is optional, one can always invoke it - /// using `deno run --flags uri/to/ghjk/main.ts`; + /** + * The ghjk bin is optional, one can always invoke it + * using `deno run --flags uri/to/ghjk/main.ts`; + */ skipExecInstall: boolean; - /// The directory in which to install the ghjk exec - /// Preferrably, one that's in PATH + /** The directory in which to install the ghjk exec + * Preferrably, one that's in PATH + */ ghjkExecInstallDir: string; - /// the deno exec to be used by the ghjk executable - /// by default will be "deno" i.e. whatever the shell resolves that to + /** + * The deno exec to be used by the ghjk executable + * by default will be "deno" i.e. whatever in $PATH that resolves that to. + */ ghjkExecDenoExec: string; - /// The cache dir to use by the ghjk deno installation + /** + * The cache dir to use by the ghjk deno installation. + */ ghjkDenoCacheDir?: string; - // Disable using a lockfile for the ghjk command + /** + * Disable using a lockfile for the ghjk command + */ noLockfile: boolean; } +/** + * @field: + */ export const defaultInstallArgs: InstallArgs = { ghjkShareDir: std_path.resolve(dirs().shareDir, "ghjk"), homeDir: dirs().homeDir, @@ -128,8 +129,10 @@ export const defaultInstallArgs: InstallArgs = { // TODO: respect xdg dirs ghjkExecInstallDir: std_path.resolve(dirs().homeDir, ".local", "bin"), ghjkExecDenoExec: Deno.execPath(), - // the default behvaior kicks in with ghjkDenoCacheDir is falsy - // ghjkDenoCacheDir: undefined, + /** + * the default behvaior kicks in with ghjkDenoCacheDir is falsy + * ghjkDenoCacheDir: undefined, + */ noLockfile: false, }; diff --git a/mod.ts b/mod.ts index fd2c986d..65fd9bff 100644 --- a/mod.ts +++ b/mod.ts @@ -1,549 +1,51 @@ //! This module is intended to be re-exported by `ghjk.ts` config scripts. Please //! avoid importing elsewhere at it has side-effects. -// NOTE: avoid adding sources of randomness -// here to make the resulting config reasonably stable -// across serializaiton. No random identifiers. // TODO: harden most of the items in here import "./setup_logger.ts"; // ports specific imports -import portsValidators from "./modules/ports/types.ts"; import type { - AllowedPortDep, InstallConfigFat, - InstallSet, - InstallSetRefProvision, - PortsModuleConfigHashed, PortsModuleSecureConfig, } from "./modules/ports/types.ts"; import logger from "./utils/logger.ts"; +import { $ } from "./utils/mod.ts"; import { - $, - defaultCommandBuilder, - thinInstallConfig, - unwrapParseRes, -} 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"; -// host -import type { SerializedConfig } from "./host/types.ts"; -import * as std_modules from "./modules/std.ts"; -// tasks -import { dax, jsonHash, objectHash } from "./deps/common.ts"; + EnvBuilder, + GhjkfileBuilder, + stdDeps, + stdSecureConfig, +} from "./ghjkfiles/mod.ts"; +import type { EnvDefArgs, TaskDefArgs, TaskFn } from "./ghjkfiles/mod.ts"; // WARN: this module has side-effects and only ever import // types from it import type { ExecTaskArgs } from "./modules/tasks/deno.ts"; -import { TasksModuleConfig } from "./modules/tasks/types.ts"; -// envs -import { - EnvRecipe, - EnvsModuleConfig, - WellKnownProvision, -} from "./modules/envs/types.ts"; const DEFAULT_BASE_ENV_NAME = "main"; -export type EnvDefArgs = { - name: string; - installs?: InstallConfigFat[]; - allowedPortDeps?: AllowedPortDep[]; - /* - * If true or not set, will base the task's env on top - * of the default env (usually `main`). If false, will build on - * top of a new env. If given a string, will use the identified env as a base - * for the task env. - */ - envBase?: string | boolean; -}; - -export type TaskFnArgs = { - $: dax.$Type; - argv: string[]; - env: Record; -}; - -export type TaskFn = (args: TaskFnArgs) => Promise | any; - -/* - * Configuration for a task. - */ -export type TaskDefArgs = { - name: string; - fn: TaskFn; - desc?: string; - dependsOn?: string[]; - workingDir?: string | dax.PathRef; - envVars?: Record; - allowedPortDeps?: AllowedPortDep[]; - installs?: InstallConfigFat[]; - envBase?: string | boolean; -}; - -class GhjkfileBuilder { - #installSets = new Map(); - #tasks = {} as Record; - #bb = new Map(); - #seenEnvs: Record = {}; - - addInstall(setId: string, configUnclean: InstallConfigFat) { - const config = unwrapParseRes( - portsValidators.installConfigFat.safeParse(configUnclean), - { - config: configUnclean, - }, - `error parsing InstallConfig`, - ); - - const set = this.#getSet(setId); - set.installs.push(config); - logger().debug("install added", config); - } - - setAllowedPortDeps(setId: string, deps: AllowedPortDep[]) { - const set = this.#getSet(setId); - set.allowedDeps = Object.fromEntries( - deps.map(( - dep, - ) => [dep.manifest.name, dep]), - ); - } - - addTask(args: TaskDefArgs) { - // NOTE: we make sure the env base declared here exists - // this call is necessary to make sure that a `task` can - // be declared before the `env` but still depend on it. - // Order-indepency like this makes the `ghjk.ts` way less - // brittle. - if (typeof args.envBase == "string") { - this.addEnv({ name: args.envBase }); - } - - this.#tasks[args.name] = { - ...args, - name, - }; - return args.name; - } - - addEnv(args: EnvDefArgs) { - let env = this.#seenEnvs[args.name]?.[0]; - if (!env) { - let finalizer: EnvFinalizer; - env = new EnvBuilder(this, (fin) => finalizer = fin, args.name); - this.#seenEnvs[args.name] = [env, finalizer!]; - } - if (args.envBase !== undefined) { - env.base(args.envBase); - } - if (args.installs) { - env.install(...args.installs); - } - if (args.allowedPortDeps) { - env.allowedPortDeps(args.allowedPortDeps); - } - return env; - } - - async execTask( - { name, workingDir, envVars, argv }: ExecTaskArgs, - ) { - const task = this.#tasks[name]; - if (!task) { - throw new Error(`no task defined under "${name}"`); - } - const custom$ = $.build$({ - commandBuilder: defaultCommandBuilder().env(envVars).cwd(workingDir), - }); - await task.fn({ argv, env: envVars, $: custom$ }); - } - - toConfig(secureConfig: PortsModuleSecureConfig | undefined) { - try { - const defaultEnv = secureConfig?.defaultEnv ?? DEFAULT_BASE_ENV_NAME; - const defaultBaseEnv = secureConfig?.defaultBaseEnv ?? - DEFAULT_BASE_ENV_NAME; - const envsConfig = this.#processEnvs( - defaultEnv, - defaultBaseEnv, - ); - const tasksConfig = this.#processTasks(envsConfig, defaultBaseEnv); - const portsConfig = this.#processInstalls( - secureConfig?.masterPortDepAllowList ?? stdDeps(), - ); - - const config: SerializedConfig = { - modules: [{ - id: std_modules.ports, - config: portsConfig, - }, { - id: std_modules.tasks, - config: tasksConfig, - }, { - id: std_modules.envs, - config: envsConfig, - }], - blackboard: Object.fromEntries(this.#bb.entries()), - }; - return config; - } catch (cause) { - throw new Error(`error constructing config for serialization`, { cause }); - } - } - - #getSet(setId: string) { - let set = this.#installSets.get(setId); - if (!set) { - set = { installs: [], allowedDeps: {} }; - this.#installSets.set(setId, set); - } - return set; - } - - #addToBlackboard(inp: unknown) { - // jsonHash.digest is async - const hash = objectHash(jsonHash.canonicalize(inp as jsonHash.Tree)); - - if (!this.#bb.has(hash)) { - this.#bb.set(hash, inp); - } - return hash; - } - - // this processes the defined envs, normalizing dependency (i.e. "envBase") - // relationships to produce the standard EnvsModuleConfig - #processEnvs( - defaultEnv: string, - defaultBaseEnv: string, - ) { - const all = {} as Record< - string, - ReturnType & { envBaseResolved: null | string } - >; - const indie = [] as string[]; - const revDeps = new Map(); - for ( - const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) - ) { - const final = finalizer(); - const { name, envBase } = final; - const envBaseResolved = typeof envBase === "string" - ? envBase - : envBase - ? defaultBaseEnv - : null; - all[name] = { ...final, envBaseResolved }; - if (envBaseResolved) { - let parentRevDeps = revDeps.get(envBaseResolved); - if (!parentRevDeps) { - parentRevDeps = []; - revDeps.set(envBaseResolved, parentRevDeps); - } - parentRevDeps.push(final.name); - } else { - indie.push(name); - } - } - const processed = {} as Record; - const out: EnvsModuleConfig = { envs: {}, defaultEnv }; - const workingSet = [...indie]; - while (workingSet.length > 0) { - const item = workingSet.pop()!; - const final = all[item]; - - const base = final.envBaseResolved - ? processed[final.envBaseResolved] - : null; - - let processedInstallSetId: string | undefined; - { - const installSet = this.#installSets.get(final.installSetId); - if (installSet) { - // if base also has an install set - if (base?.installSetId) { - // merge the parent's installs into this one - const baseSet = this.#installSets.get( - base.installSetId, - )!; - const mergedInstallsSet = new Set([ - ...installSet.installs, - ...baseSet.installs, - ]); - installSet.installs = [...mergedInstallsSet.values()]; - for ( - const [key, val] of Object.entries(baseSet.allowedDeps) - ) { - // prefer the port dep config of the child over any - // similar deps in the parent - if (!installSet.allowedDeps[key]) { - installSet.allowedDeps[key] = val; - } - } - } - processedInstallSetId = final.installSetId; - } // if there's no install set found under the id - else { - // implies that the env has not ports explicitly configured - if (base) { - processedInstallSetId = base.installSetId; - } - } - } - processed[final.name] = { installSetId: processedInstallSetId }; - out.envs[final.name] = { - provides: [ - ...Object.entries(final.vars).map(( - [key, val], - ) => { - const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; - return prov; - }), - ], - }; - if (processedInstallSetId) { - const prov: InstallSetRefProvision = { - ty: "ghjk.ports.InstallSetRef", - setId: processedInstallSetId, - }; - out.envs[final.name].provides.push(prov); - } - - const curRevDeps = revDeps.get(final.name); - if (curRevDeps) { - workingSet.push(...curRevDeps); - revDeps.delete(final.name); - } - } - return out; - } - - #processTasks(envsConfig: EnvsModuleConfig, defaultBaseEnv: string) { - const out: TasksModuleConfig = { - envs: {}, - tasks: {}, - }; - for ( - const [name, args] of Object - .entries( - this.#tasks, - ) - ) { - const { workingDir, desc, dependsOn, envBase } = args; - const envBaseResolved = typeof envBase === "string" - ? envBase - : envBase - ? defaultBaseEnv - : null; - - const envBaseRecipe = envBaseResolved - ? envsConfig.envs[envBaseResolved] - : null; - - const taskEnvRecipe: EnvRecipe = { - provides: [], - }; - - const taskInstallSet: InstallSet = { - installs: args.installs ?? [], - allowedDeps: Object.fromEntries( - (args.allowedPortDeps ?? []).map((dep) => [dep.manifest.name, dep]), - ), - }; - - const mergedEnvVars = args.envVars ?? {}; - if (envBaseRecipe) { - for ( - const prov of envBaseRecipe - .provides as ( - | WellKnownProvision - | InstallSetRefProvision - )[] - ) { - if (prov.ty == "posix.envVar") { - if (!mergedEnvVars[prov.key]) { - mergedEnvVars[prov.key] = prov.val; - } - } else if (prov.ty == "ghjk.ports.InstallSetRef") { - const baseSet = this.#installSets.get(prov.setId)!; - const mergedInstallsSet = new Set([ - ...taskInstallSet.installs, - ...baseSet.installs, - ]); - taskInstallSet.installs = [...mergedInstallsSet.values()]; - for ( - const [key, val] of Object.entries(baseSet.allowedDeps) - ) { - // prefer the port dep config of the child over any - // similar deps in the base - if (!taskInstallSet.allowedDeps[key]) { - taskInstallSet.allowedDeps[key] = val; - } - } - } else { - taskEnvRecipe.provides.push(prov); - } - } - } - if (taskInstallSet.installs.length > 0) { - const setId = `ghjkTaskInstSet___${name}`; - this.#installSets.set(setId, taskInstallSet); - const prov: InstallSetRefProvision = { - ty: "ghjk.ports.InstallSetRef", - setId, - }; - taskEnvRecipe.provides.push(prov); - } - - taskEnvRecipe.provides.push( - ...Object.entries(mergedEnvVars).map(( - [key, val], - ) => { - const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; - return prov; - }), - ); - - const envHash = objectHash( - jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), - ); - out.envs[envHash] = taskEnvRecipe; - - out.tasks[name] = { - name, - workingDir: typeof workingDir == "object" - ? workingDir.toString() - : workingDir, - desc, - dependsOn, - envHash, - }; - } - for (const [name, { dependsOn }] of Object.entries(out.tasks)) { - for (const depName of dependsOn ?? []) { - if (!out.tasks[depName]) { - throw new Error( - `task "${name}" depend on non-existent task "${depName}"`, - ); - } - } - } - - return out; - } - - #processInstalls(masterAllowList: AllowedPortDep[]) { - const out: PortsModuleConfigHashed = { - sets: {}, - }; - const masterPortDepAllowList = Object.fromEntries( - masterAllowList.map((dep) => [dep.manifest.name, dep] as const), - ); - for ( - const [setId, set] of this.#installSets.entries() - ) { - for (const [portName, _] of Object.entries(set.allowedDeps)) { - if (!masterPortDepAllowList[portName]) { - throw new Error( - `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, - ); - } - } - for (const [name, hash] of Object.entries(masterPortDepAllowList)) { - if (!set.allowedDeps[name]) { - set.allowedDeps[name] = hash; - } - } - out.sets[setId] = { - installs: set.installs.map((inst) => this.#addToBlackboard(inst)), - allowedDeps: this.#addToBlackboard(Object.fromEntries( - Object.entries(set.allowedDeps).map( - ([key, dep]) => [key, this.#addToBlackboard(dep)], - ), - )), - }; - } - return out; - } -} - -type EnvFinalizer = () => { - name: string; - installSetId: string; - envBase: string | boolean; - vars: Record; -}; - -// this class will be exposed to users and thus features -// a contrived implementation of the `build`/`finalize` method -// all to avoid exposing the function in the public api -class EnvBuilder { - #installSetId: string; - #file: GhjkfileBuilder; - #base: string | boolean = true; - #vars: Record = {}; - - constructor( - file: GhjkfileBuilder, - setFinalizer: (fin: EnvFinalizer) => void, - public name: string, - ) { - this.#file = file; - this.#installSetId = `ghjkEnvProvInstSet___${name}`; - setFinalizer(() => ({ - name: this.name, - installSetId: this.#installSetId, - envBase: this.#base, - vars: this.#vars, - })); - } - - base(base: string | boolean) { - this.#base = base; - } - - /* - * Provision a port install in the environment. - */ - install(...configs: InstallConfigFat[]) { - for (const config of configs) { - this.#file.addInstall(this.#installSetId, config); - } - return this; - } - - /* - * This is treated as a single set and will replace previously any configured set. - */ - allowedPortDeps(deps: AllowedPortDep[]) { - this.#file.setAllowedPortDeps(this.#installSetId, deps); - } - - var(key: string, val: string) { - this.vars({ [key]: val }); - } - - vars(envVars: Record) { - Object.assign(this.#vars, envVars); - } -} - const file = new GhjkfileBuilder(); const mainEnv = file.addEnv({ name: DEFAULT_BASE_ENV_NAME, - envBase: false, + base: false, allowedPortDeps: stdDeps(), + desc: "the default default environment.", }); -export { $, logger }; +export type { EnvDefArgs, TaskDefArgs, TaskFn } from "./ghjkfiles/mod.ts"; +export { $, logger, stdDeps, stdSecureConfig }; // FIXME: ses.lockdown to freeze primoridials // freeze the object to prevent malicious tampering of the secureConfig export const ghjk = Object.freeze({ getConfig: Object.freeze( - (secureConfig: PortsModuleSecureConfig | undefined) => - file.toConfig(secureConfig), + (secureConfig: PortsModuleSecureConfig | undefined) => { + const defaultEnv = secureConfig?.defaultEnv ?? DEFAULT_BASE_ENV_NAME; + const defaultBaseEnv = secureConfig?.defaultBaseEnv ?? + DEFAULT_BASE_ENV_NAME; + return file.toConfig({ defaultEnv, defaultBaseEnv, secureConfig }); + }, ), execTask: Object.freeze( (args: ExecTaskArgs) => file.execTask(args), @@ -591,39 +93,3 @@ export function env( : { ...argsMaybe, name: nameOrArgs }; return file.addEnv(args); } - -export function stdSecureConfig( - args: { - additionalAllowedPorts?: PortsModuleSecureConfig["masterPortDepAllowList"]; - enableRuntimes?: boolean; - } & Pick, -): PortsModuleSecureConfig { - const { additionalAllowedPorts, enableRuntimes = false } = args; - const out: PortsModuleSecureConfig = { - masterPortDepAllowList: [ - ...stdDeps({ enableRuntimes }), - ...additionalAllowedPorts ?? [], - ], - }; - return out; -} - -export function stdDeps(args = { enableRuntimes: false }) { - const out: AllowedPortDep[] = [ - ...Object.values(std_ports.map), - ]; - if (args.enableRuntimes) { - out.push( - ...[ - node.default(), - cpy.default(), - ].map((fatInst) => { - return portsValidators.allowedPortDep.parse({ - manifest: fatInst.port, - defaultInst: thinInstallConfig(fatInst), - }); - }), - ); - } - return out; -} diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 5b785217..0ecc97c8 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -1,24 +1,24 @@ -/* - Design: - - `$ ghjk env activate` to switch to default environment - - `$ ghjk env list` - - `$ ghjk env info` - - By default, all things go to the `main` environment -*/ - export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; - +import { $, detectShellPath, Json, unwrapParseRes } from "../../utils/mod.ts"; import validators from "./types.ts"; -import type { EnvsModuleConfigX } from "./types.ts"; +import type { + EnvRecipeX, + EnvsModuleConfigX, + WellKnownProvision, +} from "./types.ts"; import type { GhjkCtx, ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; - -import { Blackboard } from "../../host/types.ts"; -import { reduceStrangeProvisions } from "./reducer.ts"; +import type { Blackboard } from "../../host/types.ts"; import { cookPosixEnv } from "./posix.ts"; +import { getInstallSetStore, installGraphToSetMeta } from "../ports/inter.ts"; +import type { + InstallSetProvision, + InstallSetRefProvision, +} from "../ports/types.ts"; +import { isColorfulTty } from "../../utils/logger.ts"; +import { buildInstallGraph, syncCtxFromGhjk } from "../ports/sync.ts"; export type EnvsCtx = { activeEnv: string; @@ -49,7 +49,7 @@ export class EnvsModule extends ModuleBase { validators.envsModuleConfig.safeParse(manifest.config), ); - const activeEnv = config.defaultEnv; + const activeEnv = Deno.env.get("GHJK_ENV") ?? config.defaultEnv; return Promise.resolve({ activeEnv, @@ -57,34 +57,121 @@ export class EnvsModule extends ModuleBase { }); } - command( + commands( gcx: GhjkCtx, ecx: EnvsCtx, ) { - const root: cliffy_cmd.Command = new cliffy_cmd - .Command() - .description("Envs module, the cornerstone") - .alias("e") - .alias("env") - .action(function () { - this.showHelp(); - }) - .command( - "sync", - new cliffy_cmd.Command().description("Syncs the environment.") - .action(async () => { - const envName = ecx.activeEnv; - - const env = ecx.config.envs[envName]; - // TODO: diff env and ask confirmation from user - const reducedEnv = await reduceStrangeProvisions(gcx, env); - const envDir = $.path(gcx.ghjkDir).join("envs", envName).toString(); - - await cookPosixEnv(reducedEnv, envDir, true); - }), - ) - .description("Envs module."); - return root; + return { + envs: new cliffy_cmd + .Command() + .description("Envs module, reproducable posix shells environments.") + .alias("e") + // .alias("env") + .action(function () { + this.showHelp(); + }) + .command( + "ls", + new cliffy_cmd.Command() + .description("List environments defined in the ghjkfile.") + .action(() => { + // deno-lint-ignore no-console + console.log( + Object.entries(ecx.config.envs) + .map(([name, { desc }]) => + `${name}${desc ? ": " + desc : ""}` + ) + .join("\n"), + ); + }), + ) + .command( + "activate", + new cliffy_cmd.Command() + .description(`Activate an environment. + +- 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.", + ); + } + 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 }); + }), + ) + .command( + "cook", + new cliffy_cmd.Command() + .description(`Cooks the environment to a posix shell. + +- If no [envName] is specified, this will cook the active env [${ecx.activeEnv}]`) + .arguments("[envName:string]") + .action(async function (_void, envNameMaybe) { + const envName = envNameMaybe ?? ecx.activeEnv; + await reduceAndCookEnv(gcx, ecx, envName); + }), + ) + .command( + "show", + new cliffy_cmd.Command() + .description(`Show details about an environment. + +- If no [envName] is specified, this shows details of the active env [${ecx.activeEnv}]. +- If no [envName] is specified and no env is active, this shows details of the default env [${ecx.config.defaultEnv}]. + `) + .arguments("[envName:string]") + .action(async function (_void, envNameMaybe) { + const envName = envNameMaybe ?? ecx.activeEnv; + const env = ecx.config.envs[envName]; + if (!env) { + throw new Error(`No env found under given name "${envName}"`); + } + // deno-lint-ignore no-console + console.log(Deno.inspect( + await showableEnv(gcx, env, envName), + { + depth: 10, + colors: isColorfulTty(), + }, + )); + }), + ), + sync: new cliffy_cmd.Command() + .description(`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.", + ); + } + const envName = envNameMaybe ?? ecx.activeEnv; + await reduceAndCookEnv(gcx, ecx, envName); + if (ecx.activeEnv != envName) { + await $`${shell}`.env({ GHJK_ENV: envName }); + } + }), + }; } loadLockEntry( @@ -108,3 +195,136 @@ export class EnvsModule extends ModuleBase { }; } } + +async function reduceAndCookEnv( + gcx: GhjkCtx, + ecx: EnvsCtx, + envName: string, +) { + const recipe = ecx.config.envs[envName]; + if (!recipe) { + throw new Error(`No env found under given name "${envName}"`); + } + + // TODO: diff env and ask confirmation from user + const envDir = $.path(gcx.ghjkDir).join("envs", envName); + /* + const recipeShowable = await showableEnv(gcx, recipe, envName); + const oldRecipeShowable = {}; + { + const recipeJsonPath = envDir.join("recipe.json"); + const oldRecipeRaw = await recipeJsonPath.readMaybeJson(); + + if (oldRecipeRaw) { + const oldRecipParsed = validators.envRecipe.safeParse(oldRecipeRaw); + if (oldRecipParsed.success) { + Object.assign( + oldRecipeShowable, + await showableEnv(gcx, oldRecipParsed.data, envName), + ); + } else { + logger.error(`invalid env recipe at ${recipeJsonPath}`); + } + } + } + console.log( + diff_kit.diff( + // TODO: canonicalize objects + JSON.stringify(oldRecipeShowable, undefined, 2), + JSON.stringify(recipeShowable, undefined, 2), + // new diff_kit.DiffTerm(), + ), + ); + if (!await $.confirm("cook env?")) { + return; + } + */ + + await cookPosixEnv({ + gcx, + recipe, + envName, + envDir: envDir.toString(), + createShellLoaders: true, + }); + if (envName == ecx.config.defaultEnv) { + const defaultEnvDir = $.path(gcx.ghjkDir).join("envs", "default"); + await $.removeIfExists(defaultEnvDir); + await defaultEnvDir.createSymlinkTo(envDir, { kind: "relative" }); + } +} + +async function showableEnv( + gcx: GhjkCtx, + recipe: EnvRecipeX, + envName: string, +) { + const printBag = {} as Record; + await using scx = await syncCtxFromGhjk(gcx); + for ( + const prov of recipe + .provides as ( + | WellKnownProvision + | InstallSetRefProvision + | InstallSetProvision + )[] + ) { + switch (prov.ty) { + case "posix.envVar": + printBag.envVars = { + ...printBag.envVars ?? {}, + [prov.key]: prov.val, + }; + break; + case "posix.exec": + printBag.execs = [ + ...printBag.execs ?? [], + prov.absolutePath, + ]; + break; + case "posix.sharedLib": + printBag.sharedLibs = [ + ...printBag.sharedLibs ?? [], + prov.absolutePath, + ]; + break; + case "posix.headerFile": + printBag.headerFiles = [ + ...printBag.headerFiles ?? [], + prov.absolutePath, + ]; + break; + case "ghjk.ports.InstallSet": { + const graph = await buildInstallGraph(scx, prov.set); + const setMeta = installGraphToSetMeta(graph); + printBag.ports = { + ...printBag.ports ?? {}, + [`installSet_${Math.floor(Math.random() * 101)}`]: setMeta, + }; + break; + } + case "ghjk.ports.InstallSetRef": { + const setStore = getInstallSetStore(gcx); + const set = setStore.get(prov.setId); + if (!set) { + throw new Error( + `unable to find install set ref provisioned under id ${prov.setId}`, + ); + } + const graph = await buildInstallGraph(scx, set); + const setMeta = installGraphToSetMeta(graph); + printBag.ports = { + ...printBag.ports ?? {}, + [prov.setId]: setMeta, + }; + break; + } + default: + } + } + return { + ...printBag, + ...(recipe.desc ? { desc: recipe.desc } : {}), + envName, + }; +} diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 8a2b2f5a..14d9a250 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -1,18 +1,25 @@ import { std_fs, std_path } from "../../deps/cli.ts"; -import type { WellKnownEnvRecipeX } from "./types.ts"; +import type { EnvRecipeX } from "./types.ts"; import getLogger from "../../utils/logger.ts"; -import { $, PathRef } from "../../utils/mod.ts"; +import { $, Path } from "../../utils/mod.ts"; +import type { GhjkCtx } from "../types.ts"; +import { reduceStrangeProvisions } from "./reducer.ts"; const logger = getLogger(import.meta); export async function cookPosixEnv( - env: WellKnownEnvRecipeX, - envDir: string, - createShellLoaders = false, + { gcx, recipe, envName, envDir, createShellLoaders = false }: { + gcx: GhjkCtx; + recipe: EnvRecipeX; + envName: string; + envDir: string; + createShellLoaders?: boolean; + }, ) { + const reducedRecipe = await reduceStrangeProvisions(gcx, recipe); + await $.removeIfExists(envDir); // create the shims for the user's environment const shimDir = $.path(envDir).join("shims"); - await $.removeIfExists(shimDir); const [binShimDir, libShimDir, includeShimDir] = await Promise.all([ shimDir.join("bin").ensureDir(), @@ -25,11 +32,13 @@ export async function cookPosixEnv( const binPaths = [] as string[]; const libPaths = [] as string[]; const includePaths = [] as string[]; - const vars = {} as Record; + const vars = { + GHJK_ENV: envName, + } as Record; // FIXME: detect shim conflicts // FIXME: better support for multi installs - await Promise.all(env.provides.map((item) => { + await Promise.all(reducedRecipe.provides.map((item) => { switch (item.ty) { case "posix.exec": binPaths.push(item.absolutePath); @@ -48,7 +57,7 @@ export async function cookPosixEnv( }" and "${item.val}"`, ); } - vars[item.key] = vars[item.val]; + vars[item.key] = item.val; break; default: throw Error(`unsupported provision type: ${(item as any).provision}`); @@ -70,6 +79,7 @@ export async function cookPosixEnv( includePaths, includeShimDir, ), + $.path(envDir).join("recipe.json").writeJsonPretty(reducedRecipe), ]); // write loader for the env vars mandated by the installs logger.debug("adding vars to loader", vars); @@ -110,7 +120,7 @@ export async function cookPosixEnv( /// This expands globs found in the targetPaths async function shimLinkPaths( targetPaths: string[], - shimDir: PathRef, + shimDir: Path, ) { // map of filename to shimPath const shims: Record = {}; @@ -156,8 +166,11 @@ async function writeLoader( env: Record, pathVars: Record, ) { - const loader = { + const activate = { posix: [ + `if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then + eval "$GHJK_CLEANUP_POSIX" +fi`, `export GHJK_CLEANUP_POSIX="";`, ...Object.entries(env).map(([k, v]) => // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution @@ -173,7 +186,10 @@ export ${k}="${v}:$${k}"; ), ].join("\n"), fish: [ - `set --erase GHJK_CLEANUP_FISH`, + `if set --query GHJK_CLEANUP_FISH + eval $GHJK_CLEANUP_FISH + set --erase GHJK_CLEANUP_FISH +end`, ...Object.entries(env).map(([k, v]) => `set --global --append GHJK_CLEANUP_FISH "set --global --export ${k} '$${k}';"; set --global --export ${k} '${v}';` @@ -187,7 +203,7 @@ set --global --export --prepend ${k} ${v}; }; const envPathR = await $.path(envDir).ensureDir(); await Promise.all([ - envPathR.join(`loader.fish`).writeText(loader.fish), - envPathR.join(`loader.sh`).writeText(loader.posix), + envPathR.join(`activate.fish`).writeText(activate.fish), + envPathR.join(`activate.sh`).writeText(activate.posix), ]); } diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts index 28357be8..64fe228d 100644 --- a/modules/envs/reducer.ts +++ b/modules/envs/reducer.ts @@ -11,6 +11,12 @@ import { wellKnownProvisionTypes } from "./types.ts"; import validators from "./types.ts"; export type ProvisionReducerStore = Map>; + +/** + * In order to provide a means for other modules to define their own + * environment provisions, {@link ProvisionReducer}s can be registered + * here. + */ export function getProvisionReducerStore( gcx: GhjkCtx, ) { diff --git a/modules/envs/types.ts b/modules/envs/types.ts index 00554972..5850483d 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -32,6 +32,7 @@ const wellKnownProvision = zod.discriminatedUnion( ); const envRecipe = zod.object({ + desc: zod.string().nullish(), provides: zod.array(provision), }); diff --git a/modules/mod.ts b/modules/mod.ts index 2d2241d6..daa35fa1 100644 --- a/modules/mod.ts +++ b/modules/mod.ts @@ -8,22 +8,22 @@ export abstract class ModuleBase { _gcx: GhjkCtx, ): Promise | void {} */ abstract processManifest( - ctx: GhjkCtx, + gcx: GhjkCtx, manifest: ModuleManifest, bb: Blackboard, lockEnt: LockEnt | undefined, ): Promise | Ctx; // returns undefined if previous lock entry is no longer valid abstract loadLockEntry( - ctx: GhjkCtx, + gcx: GhjkCtx, raw: Json, ): Promise | LockEnt | undefined; abstract genLockEntry( - ctx: GhjkCtx, - manifest: Ctx, + gcx: GhjkCtx, + mcx: Ctx, ): Promise | Json; - abstract command( - ctx: GhjkCtx, - pman: Ctx, - ): cliffy_cmd.Command; + abstract commands( + gcx: GhjkCtx, + mcx: Ctx, + ): Record>; } diff --git a/modules/ports/ambient.ts b/modules/ports/ambient.ts index a6852bfb..6b290535 100644 --- a/modules/ports/ambient.ts +++ b/modules/ports/ambient.ts @@ -6,7 +6,7 @@ export class AmbientAccessPort extends PortBase { constructor(public manifest: AmbientAccessPortManifestX) { super(); // dependencies make no sense for ambient ports - if (manifest.deps && manifest.deps.length > 0) { + if (manifest.buildDeps && manifest.buildDeps.length > 0) { throw new Error( `ambient access plugin has deps ${JSON.stringify(manifest)}`, ); diff --git a/modules/ports/inter.ts b/modules/ports/inter.ts new file mode 100644 index 00000000..7e884131 --- /dev/null +++ b/modules/ports/inter.ts @@ -0,0 +1,49 @@ +import type { GhjkCtx } from "../types.ts"; +import type { InstallSetX } from "./types.ts"; +import type { InstallGraph } from "./sync.ts"; // TODO: rename to install.ts + +export type InstallSetStore = Map; + +/** + * {@link InstallSetStore} provides a way for other modules to get + * install sets from the {@link import("./types.ts").PortsModuleConfig} + */ +export function getInstallSetStore( + gcx: GhjkCtx, +) { + const id = "installSetStore"; + let memoStore = gcx.blackboard.get(id) as + | InstallSetStore + | undefined; + if (!memoStore) { + memoStore = new Map(); + gcx.blackboard.set(id, memoStore); + } + return memoStore; +} + +/** + * Get a user friendly description of an {@link InstallGraph}. + */ +export function installGraphToSetMeta(graph: InstallGraph) { + function installMetaFromGraph(id: string) { + const inst = graph.all[id]!; + const { + buildDepConfigs: _bDeps, + resolutionDepConfigs: _rDeps, + ...confWithoutDeps + } = inst.config; + return { + instId: inst.instId, + ...confWithoutDeps, + }; + } + const userInstallIds = new Set(graph.user); + const out = { + userInstalls: graph.user.map(installMetaFromGraph), + buildInstalls: Object.keys(graph.all) + .filter((key) => !userInstallIds.has(key)) + .map(installMetaFromGraph), + }; + return out; +} diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 49e60da9..bdeb765f 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -13,20 +13,16 @@ import { ModuleBase } from "../mod.ts"; import { buildInstallGraph, getResolutionMemo, - type InstallGraph, syncCtxFromGhjk, } from "./sync.ts"; // TODO: rename to install.ts import type { Blackboard } from "../../host/types.ts"; import { getProvisionReducerStore } from "../envs/reducer.ts"; import { installSetReducer, installSetRefReducer } from "./reducers.ts"; import type { Provision, ProvisionReducer } from "../envs/types.ts"; +import { getInstallSetStore } from "./inter.ts"; export type PortsCtx = { config: PortsModuleConfigX; - /* - * A map from a setId found in the `PortsModuleConfigX` to the `InstallGraph`. - */ - installGraphs: Map; }; const lockValidator = zod.object({ @@ -39,7 +35,7 @@ const lockValidator = zod.object({ type PortsLockEnt = zod.infer; export class PortsModule extends ModuleBase { - async processManifest( + processManifest( gcx: GhjkCtx, manifest: ModuleManifest, bb: Blackboard, @@ -60,41 +56,35 @@ export class PortsModule extends ModuleBase { config: { sets: {}, }, - installGraphs: new Map(), }; + const setStore = getInstallSetStore(gcx); // pre-process the install sets found in the config - { - // syncCx contains a reference counted db connection - // somewhere deep in there - // so we need to use `using` - await using syncCx = await syncCtxFromGhjk(gcx); - for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { - // install sets in the config use hash references to dedupe InstallConfigs, - // AllowedDepSets and AllowedDeps - // reify the references from the blackboard before continuing - const installs = hashedSet.installs.map((hash) => - unwrapParseCurry(validators.installConfigFat.safeParse(bb[hash])) - ); - const allowedDepSetHashed = unwrapParseCurry( - validators.allowDepSetHashed.safeParse( - bb[hashedSet.allowedDeps], - ), - ); - const allowedDeps = Object.fromEntries( - Object.entries(allowedDepSetHashed).map(( - [key, hash], - ) => [ - key, - unwrapParseCurry(validators.allowedPortDep.safeParse(bb[hash])), - ]), - ); - const set: InstallSetX = { - installs, - allowedDeps, - }; - pcx.config.sets[id] = set; - pcx.installGraphs.set(id, await buildInstallGraph(syncCx, set)); - } + for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { + // install sets in the config use hash references to dedupe InstallConfigs, + // AllowedDepSets and AllowedDeps + // reify the references from the blackboard before continuing + const installs = hashedSet.installs.map((hash) => + unwrapParseCurry(validators.installConfigFat.safeParse(bb[hash])) + ); + const allowedDepSetHashed = unwrapParseCurry( + validators.allowDepSetHashed.safeParse( + bb[hashedSet.allowedDeps], + ), + ); + const allowedDeps = Object.fromEntries( + Object.entries(allowedDepSetHashed).map(( + [key, hash], + ) => [ + key, + unwrapParseCurry(validators.allowedPortDep.safeParse(bb[hash])), + ]), + ); + const set: InstallSetX = { + installs, + allowedDeps, + }; + pcx.config.sets[id] = set; + setStore.set(id, set); } // register envrionment reducers for any @@ -111,32 +101,50 @@ export class PortsModule extends ModuleBase { return pcx; } - command( - _gcx: GhjkCtx, - _pcx: PortsCtx, + commands( + gcx: GhjkCtx, + pcx: PortsCtx, ) { - return new cliffy_cmd.Command() - .alias("p") - .action(function () { - this.showHelp(); - }) - .description("Ports module, install programs into your env.") - .command( - "outdated", - new cliffy_cmd.Command() - .description("TODO") - .action(function () { - throw new Error("TODO"); - }), - ) - .command( - "cleanup", - new cliffy_cmd.Command() - .description("TODO") - .action(function () { - throw new Error("TODO"); - }), - ); + return { + ports: new cliffy_cmd.Command() + .alias("p") + .action(function () { + this.showHelp(); + }) + .description("Ports module, install programs into your env.") + .command( + "resolve", + new cliffy_cmd.Command() + .description(`Resolve all installs declared in config. + +- Useful to pre-resolve and add all install configs to the lockfile.`) + .action(async function () { + // scx contains a reference counted db connection + // somewhere deep in there + // so we need to use `using` + await using scx = await syncCtxFromGhjk(gcx); + for (const [_id, set] of Object.entries(pcx.config.sets)) { + void await buildInstallGraph(scx, set); + } + }), + ) + .command( + "outdated", + new cliffy_cmd.Command() + .description("TODO") + .action(function () { + throw new Error("TODO"); + }), + ) + .command( + "cleanup", + new cliffy_cmd.Command() + .description("TODO") + .action(function () { + throw new Error("TODO"); + }), + ), + }; } loadLockEntry( gcx: GhjkCtx, diff --git a/modules/ports/reducers.ts b/modules/ports/reducers.ts index 9c843bbf..9cbf58e2 100644 --- a/modules/ports/reducers.ts +++ b/modules/ports/reducers.ts @@ -40,29 +40,24 @@ export function installSetReducer(gcx: GhjkCtx) { } export function installSetRefReducer(gcx: GhjkCtx, pcx: PortsCtx) { - return async (provisions: InstallSetRefProvision[]) => { - if (provisions.length > 1) { - throw new Error( - 'only one "ghjkPorts" provision per environment is supported', - ); - } - const { setId } = unwrapParseRes( - validators.installSetRefProvision.safeParse(provisions[0]), - {}, - "error parsing env provision", - ); - const installGraph = pcx.installGraphs.get(setId); - if (!installGraph) { - throw new Error( - `provisioned install set under id "${setId}" not found`, - ); - } - await using scx = await syncCtxFromGhjk(gcx); - const installArts = await installFromGraph(scx, installGraph); - - const out = await reduceInstArts(installGraph, installArts); - return out; - }; + const directReducer = installSetReducer(gcx); + return (provisions: InstallSetRefProvision[]) => + directReducer(provisions.map( + (prov) => { + const { setId } = unwrapParseRes( + validators.installSetRefProvision.safeParse(prov), + {}, + "error parsing env provision", + ); + const set = pcx.config.sets[setId]; + if (!set) { + throw new Error( + `provisioned install set under id "${setId}" not found`, + ); + } + return { ty: "ghjk.ports.InstallSet", set }; + }, + )); } async function reduceInstArts( diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index ea62d373..3a7dda6c 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -33,7 +33,6 @@ import type { GhjkCtx } from "../types.ts"; const logger = getLogger(import.meta); export type ResolutionMemoStore = Map>; -export type SyncCtx = DePromisify>; export function getResolutionMemo( gcx: GhjkCtx, @@ -49,6 +48,8 @@ export function getResolutionMemo( return memoStore; } +export type SyncCtx = DePromisify>; + export async function syncCtxFromGhjk( gcx: GhjkCtx, ) { @@ -94,109 +95,6 @@ export async function syncCtxFromGhjk( }; } -export async function installFromGraphAndShimEnv( - scx: SyncCtx, - envDir: string, - graph: InstallGraph, - createShellLoaders = true, -) { - const installArts = await installFromGraph( - scx, - graph, - ); - // create the shims for the user's environment - const shimDir = $.path(envDir).join("shims"); - await $.removeIfExists(shimDir); - - const [binShimDir, libShimDir, includeShimDir] = await Promise.all([ - shimDir.join("bin").ensureDir(), - shimDir.join("lib").ensureDir(), - shimDir.join("include").ensureDir(), - ]); - - // extract the env vars exported by the user specified - // installs and shim up their exported artifacts - const totalEnv: Record = {}; - // FIXME: detect shim conflicts - // FIXME: better support for multi installs - for (const instId of graph.user) { - const { binPaths, libPaths, includePaths, installPath, env } = installArts - .get( - instId, - )!; - - for (const [key, val] of Object.entries(env)) { - const conflict = totalEnv[key]; - if (conflict) { - throw new Error( - `duplicate env var found ${key} from sources ${instId} & ${ - conflict[1] - }`, - ); - } - totalEnv[key] = [val, instId]; - } - - // bin shims - void await shimLinkPaths( - binPaths, - installPath, - binShimDir.toString(), - ); - // lib shims - void await shimLinkPaths( - libPaths, - installPath, - libShimDir.toString(), - ); - // include shims - void await shimLinkPaths( - includePaths, - installPath, - includeShimDir.toString(), - ); - } - // write loader for the env vars mandated by the installs - logger.debug("adding vars to loader", totalEnv); - // FIXME: prevent malicious env manipulations - let LD_LIBRARY_ENV: string; - switch (Deno.build.os) { - case "darwin": - LD_LIBRARY_ENV = "DYLD_LIBRARY_PATH"; - break; - case "linux": - LD_LIBRARY_ENV = "LD_LIBRARY_PATH"; - break; - default: - throw new Error(`unsupported os ${Deno.build.os}`); - } - const pathVars = { - PATH: `${envDir}/shims/bin`, - LIBRARY_PATH: `${envDir}/shims/lib`, - [LD_LIBRARY_ENV]: `${envDir}/shims/lib`, - C_INCLUDE_PATH: `${envDir}/shims/include`, - CPLUS_INCLUDE_PATH: `${envDir}/shims/include`, - }; - // totalEnv contains info about the origin of the env - // which we don't need anymore - const simplifedTotalEnvs = Object.fromEntries( - Object.entries(totalEnv).map(([key, [val, _]]) => [key, val]), - ); - if (createShellLoaders) { - await writeLoader( - envDir, - simplifedTotalEnvs, - pathVars, - ); - } - return { - env: { - ...simplifedTotalEnvs, - ...pathVars, - }, - }; -} - export async function installFromGraph( scx: SyncCtx, graph: InstallGraph, @@ -481,12 +379,12 @@ export async function buildInstallGraph( graph.all[installId] = inst; - if (!manifest.deps || manifest.deps.length == 0) { + if (!manifest.buildDeps || manifest.buildDeps.length == 0) { graph.indie.push(installId); } else { // this goes into graph.depEdges const deps: [string, string][] = []; - for (const depId of manifest.deps) { + for (const depId of manifest.buildDeps) { const { manifest: depPort } = set.allowedDeps[depId.name]; if (!depPort) { throw new Error( @@ -499,7 +397,7 @@ export async function buildInstallGraph( // the conf is of the resolved kind which means // it's deps are also resolved const depInstall = validators.installConfigResolved.parse( - inst.config.depConfigs![depId.name], + inst.config.buildDepConfigs![depId.name], ); const depInstallId = await getInstallHash(depInstall); @@ -639,8 +537,8 @@ async function resolveConfig( // TODO: port version dependent portDep resolution // e.g. use python-2.7 if foo is resolved to <1.0 or use // python-3.x if foo is resolved to >1.0 - const resolveDepConfigs = {} as Record; - for (const dep of manifest.deps ?? []) { + const buildDepConfigs = {} as Record; + for (const dep of manifest.buildDeps ?? []) { const { manifest: depMan, config: depConf } = getDepConfig( set, manifest, @@ -654,13 +552,13 @@ async function resolveConfig( depMan, depConf, ); - resolveDepConfigs[dep.name] = depInstall; + buildDepConfigs[dep.name] = depInstall; } return validators.installConfigResolved.parse({ ...config, - depConfigs: resolveDepConfigs, version, + buildDepConfigs, }); } } @@ -686,7 +584,7 @@ function getDepConfig( // install configuration of an allowed dep port // can be overriden by dependent ports const res = validators.installConfigLite.safeParse( - (resolutionDep ? config.resolutionDepConfigs : config.depConfigs) + (resolutionDep ? config.resolutionDepConfigs : config.buildDepConfigs) ?.[depId.name] ?? defaultDepInstall, ); if (!res.success) { @@ -704,15 +602,17 @@ function getDepConfig( return { config: res.data, manifest: depPort }; } -/// This is a simpler version of the graph -/// based installer that the rest of this module implements -/// it resolves and installs a single config (and it's deps). -/// This primarily is used to install the manifest.resolutionDeps -/// which are required to do version resolution when building the -/// graph -/// FIXME: the usage of this function implies that resolution -/// will be redone if a config specfied by different resolutionDeps -/// Consider introducing a memoization scheme +/** + * This is a simpler version of the graph based installer that + * the rest of this module implements. + * It resolves and installs a single config (and its deps). + * This primarily is used to install the manifest.resolutionDeps + * which are required to do version resolution when building the + * main graphs. + */ +// FIXME: the usage of this function implies that resolution +// will be redone if a config specfied by different resolutionDeps +// TODO: consider introducing a memoization scheme async function resolveAndInstall( scx: SyncCtx, set: InstallSetX, @@ -737,7 +637,7 @@ async function resolveAndInstall( scx, depShimsRootPath, await Promise.all( - manifest.deps?.map( + manifest.buildDeps?.map( async (dep) => { const depConfig = getDepConfig(set, manifest, config, dep); // we not only resolve but install the dep here @@ -1055,48 +955,3 @@ async function doInstallStage( installVersion, }; } - -// create the loader scripts -// loader scripts are responsible for exporting -// different environment variables from the ports -// and mainpulating the path strings -async function writeLoader( - envDir: string, - env: Record, - pathVars: Record, -) { - const loader = { - posix: [ - `export GHJK_CLEANUP_POSIX="";`, - ...Object.entries(env).map(([k, v]) => - // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution - `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX"export ${k}='$${k}';"; -export ${k}='${v}';` - ), - ...Object.entries(pathVars).map(([k, v]) => - // NOTE: double quote the path vars for expansion - // single quote GHJK_CLEANUP additions to avoid expansion/exec before eval - `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${k}=$(echo "$${k}" | tr ":" "\\n" | grep -vE "^${envDir}" | tr "\\n" ":");${k}="\${${k}%:}";'; -export ${k}="${v}:$${k}"; -` - ), - ].join("\n"), - fish: [ - `set --erase GHJK_CLEANUP_FISH`, - ...Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP_FISH "set --global --export ${k} '$${k}';"; -set --global --export ${k} '${v}';` - ), - ...Object.entries(pathVars).map(([k, v]) => - `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${k} (string match --invert --regex "^${envDir}" $${k});'; -set --global --export --prepend ${k} ${v}; -` - ), - ].join("\n"), - }; - const envPathR = await $.path(envDir).ensureDir(); - await Promise.all([ - envPathR.join(`loader.fish`).writeText(loader.fish), - envPathR.join(`loader.sh`).writeText(loader.posix), - ]); -} diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 199ac4f1..69f2c0dc 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -36,7 +36,7 @@ const portManifestBase = zod.object({ // .nullish() // // default value set after transformation // .default("deferToNewer"), - deps: zod.array(portDep).nullish(), + buildDeps: zod.array(portDep).nullish(), resolutionDeps: zod.array(portDep).nullish(), }).passthrough(); @@ -79,12 +79,12 @@ const portManifest = zod.discriminatedUnion("ty", [ const installConfigSimple = zod.object({ version: zod.string() .nullish(), - // /// A place to put captured env vars + // // A place to put captured env vars // envVars: zod.record(zod.string(), zod.string()).nullish().default({}), }).passthrough(); const installConfigBase = installConfigSimple.merge(zod.object({ - depConfigs: zod.record( + buildDepConfigs: zod.record( portName, // FIXME: figure out cyclically putting `installConfigLite` here zod.unknown(), @@ -117,7 +117,7 @@ const installConfigFat = stdInstallConfigFat; const installConfigResolved = installConfigLite.merge(zod.object({ // NOTE: version is no longer nullish version: zod.string(), - // depConfigs: zod.record( + // buildDepConfigs: zod.record( // portName, // // FIXME: figure out cyclically putting `installConfigResolved` here // zod.object({ version: zod.string() }).passthrough(), @@ -249,7 +249,6 @@ export type AmbientAccessPortManifest = zod.input< typeof validators.ambientAccessPortManifest >; -// Describes the port itself export type PortManifest = zod.input< typeof validators.portManifest >; @@ -261,12 +260,16 @@ export type DenoWorkerPortManifestX = zod.infer< export type AmbientAccessPortManifestX = zod.infer< typeof validators.ambientAccessPortManifest >; -/// This is the transformed version of PortManifest, ready for consumption +/** + * This is the transformed version of PortManifest, ready for consumption + */ export type PortManifestX = zod.infer< typeof validators.portManifest >; -/// PortDeps are used during the port build/install process +/** + * PortDeps are used during the port build/install process + */ export type PortDep = zod.infer; export type PortDepFat = zod.infer; @@ -279,21 +282,39 @@ export type InstallConfigBaseLite = zod.input< export type InstallConfigBaseFat = zod.input< typeof validators.installConfigBaseFat >; -/// Fat install configs include the port manifest within +/** + * Fat install configs include the port manifest within. + */ export type InstallConfigFat = zod.input; -/// Fat install configs include the port manifest within +/** + * Fat install configs include the port manifest within. + */ export type InstallConfigFatX = zod.infer; -/// Lite install configs refer to the port they use by name +/** + * Lite install configs refer to the port they use by name. + */ export type InstallConfigLite = zod.input; -/// Lite install configs refer to the port they use by name +/** + * Lite install configs refer to the port they use by name. + */ export type InstallConfigLiteX = zod.infer; -// Describes a single installation done by a specific plugin. +/** + * Describes a single installation done by a specific plugin. + */ export type InstallConfig = zod.input; -// Describes a single installation done by a specific plugin. +/** + * Describes a single installation done by a specific plugin. + */ export type InstallConfigX = zod.infer; +/** + * {@link InstallConfig} after the {@link InstallConfig.version} has been deternimed. + */ export type InstallConfigResolved = zod.input< typeof validators.installConfigResolved >; +/** + * {@inheritDoc InstallConfigResolved} + */ export type InstallConfigResolvedX = zod.infer< typeof validators.installConfigResolved >; diff --git a/modules/ports/worker.ts b/modules/ports/worker.ts index f3e6441a..5437aa74 100644 --- a/modules/ports/worker.ts +++ b/modules/ports/worker.ts @@ -145,8 +145,9 @@ type WorkerResp = { ty: "install"; }; -/// This creates a new worker for every method -/// invocation +/** + * This creates a new worker for every method invocation. + */ export class DenoWorkerPort extends PortBase { constructor( public manifest: DenoWorkerPortManifestX, @@ -154,7 +155,9 @@ export class DenoWorkerPort extends PortBase { super(); } - /// create new worker and perform "RPC" + /** + * Create new worker and perform "RPC". + */ async call( req: WorkerReq, ): Promise { diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index 4caa3a82..5f7ada62 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -9,7 +9,6 @@ import { execTaskDeno } from "./deno.ts"; const logger = getLogger(import.meta); import { cookPosixEnv } from "../envs/posix.ts"; -import { reduceStrangeProvisions } from "../envs/reducer.ts"; export type TaskGraph = DePromisify>; @@ -114,11 +113,14 @@ export async function execTask( const taskEnvDir = await Deno.makeTempDir({ prefix: `ghjkTaskEnv_${taskName}_`, }); - const reducedEnv = await reduceStrangeProvisions( - gcx, - tasksConfig.envs[taskDef.envHash], + const { env: installEnvs } = await cookPosixEnv( + { + gcx, + recipe: tasksConfig.envs[taskDef.envHash], + envName: `taskEnv_${taskName}`, + envDir: taskEnvDir, + }, ); - const { env: installEnvs } = await cookPosixEnv(reducedEnv, taskEnvDir); logger.info("executing", taskName, args); await execTaskDeno( std_path.toFileUrl(gcx.ghjkfilePath).href, diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index e895f148..b34ffdda 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -46,13 +46,13 @@ export class TasksModule extends ModuleBase { }; } - command( + commands( gcx: GhjkCtx, tcx: TasksCtx, ) { const commands = Object.entries(tcx.config.tasks).map( ([name, task]) => { - let cliffyCmd = new cliffy_cmd.Command() + const cliffyCmd = new cliffy_cmd.Command() .name(name) .useRawArgs() .action(async (_, ...args) => { @@ -65,22 +65,23 @@ export class TasksModule extends ModuleBase { ); }); if (task.desc) { - cliffyCmd = cliffyCmd.description(task.desc); + cliffyCmd.description(task.desc); } - return cliffyCmd; }, ); - let root: cliffy_cmd.Command = new cliffy_cmd.Command() + const root = new cliffy_cmd.Command() .alias("x") .action(function () { this.showHelp(); }) .description("Tasks module."); for (const cmd of commands) { - root = root.command(cmd.getName(), cmd); + root.command(cmd.getName(), cmd); } - return root; + return { + tasks: root, + }; } loadLockEntry( diff --git a/ports/asdf.ts b/ports/asdf.ts index 55c62755..f32d8daf 100644 --- a/ports/asdf.ts +++ b/ports/asdf.ts @@ -22,7 +22,7 @@ export const manifest = { name: "asdf", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [std_ports.curl_aa, std_ports.git_aa, std_ports.asdf_plugin_git], + buildDeps: [std_ports.curl_aa, std_ports.git_aa, std_ports.asdf_plugin_git], // NOTE: we require the same port set for version resolution as well resolutionDeps: [ std_ports.curl_aa, @@ -49,7 +49,7 @@ export default function conf( const { port: pluginPort, ...liteConf } = asdf_plugin_git({ pluginRepo: config.pluginRepo, }); - const depConfigs = { + const buildDepConfigs = { [std_ports.asdf_plugin_git.name]: { ...liteConf, portRef: getPortRef(pluginPort), @@ -58,8 +58,8 @@ export default function conf( return { ...confValidator.parse(config), port: manifest, - depConfigs, - resolutionDepConfigs: depConfigs, + buildDepConfigs, + resolutionDepConfigs: buildDepConfigs, }; } diff --git a/ports/asdf_plugin_git.ts b/ports/asdf_plugin_git.ts index ab06936f..60bc7669 100644 --- a/ports/asdf_plugin_git.ts +++ b/ports/asdf_plugin_git.ts @@ -22,7 +22,7 @@ export const manifest = { name: "asdf_plugin_git", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [git_aa_id], + buildDeps: [git_aa_id], resolutionDeps: [git_aa_id], platforms: osXarch(["linux", "darwin", "windows"], ["aarch64", "x86_64"]), }; diff --git a/ports/cargobi.ts b/ports/cargobi.ts index fa06bf49..aa25da75 100644 --- a/ports/cargobi.ts +++ b/ports/cargobi.ts @@ -30,7 +30,7 @@ const manifest = { name: "cargobi_cratesio", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [std_ports.cbin_ghrel, std_ports.rust_rustup], + buildDeps: [std_ports.cbin_ghrel, std_ports.rust_rustup], // FIXME: we can't know crate platform support at this point platforms: osXarch([...ALL_OS], [...ALL_ARCH]), }; @@ -56,7 +56,7 @@ export default function conf(config: CargobiInstallConf) { const { rustConfOverride, ...thisConf } = config; const out: InstallConfigFat = { ...confValidator.parse(thisConf), - depConfigs: { + buildDepConfigs: { [std_ports.rust_rustup.name]: thinInstallConfig(rust({ profile: "minimal", ...rustConfOverride, diff --git a/ports/cpy_bs.ts b/ports/cpy_bs.ts index 2fef4cc1..81cdf44d 100644 --- a/ports/cpy_bs.ts +++ b/ports/cpy_bs.ts @@ -36,7 +36,7 @@ export const manifest = { version: "0.1.0", moduleSpecifier: import.meta.url, // python-build-standalone use zstd tarballs - deps: [tar_aa_id, zstd_aa_id], + buildDeps: [tar_aa_id, zstd_aa_id], platforms: osXarch(["linux", "darwin", "windows"], ["x86_64", "aarch64"]), }; diff --git a/ports/dummy.ts b/ports/dummy.ts index 10de2284..8ba1dfcd 100644 --- a/ports/dummy.ts +++ b/ports/dummy.ts @@ -5,18 +5,33 @@ import type { InstallArgs, InstallConfigSimple, } from "../port.ts"; -import { $, ALL_ARCH, ALL_OS, osXarch, PortBase, std_fs } from "../port.ts"; +import { + $, + ALL_ARCH, + ALL_OS, + osXarch, + PortBase, + std_fs, + zod, +} from "../port.ts"; const manifest = { ty: "denoWorker@v1" as const, name: "dummy", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [], platforms: osXarch([...ALL_OS], [...ALL_ARCH]), }; -export default function conf(config: InstallConfigSimple = {}) { +const confValidator = zod.object({ + output: zod.string().nullish(), +}); + +export type DummyInstallConf = + & InstallConfigSimple + & zod.input; + +export default function conf(config: DummyInstallConf = {}) { return { ...config, port: manifest, @@ -35,10 +50,11 @@ export class Port extends PortBase { } async download(args: DownloadArgs) { + const conf = confValidator.parse(args.config); // TODO: windows suport await $.path(args.downloadPath).join("bin", "dummy").writeText( `#!/bin/sh -echo 'dummy hey'`, +echo ${conf.output ?? "dummy hey"}`, { mode: 0o700, }, diff --git a/ports/infisical.ts b/ports/infisical.ts index 25e92274..8667c83d 100644 --- a/ports/infisical.ts +++ b/ports/infisical.ts @@ -18,7 +18,6 @@ const manifest = { name: "infisical_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [], // NOTE: infisical supports more arches than deno platforms: osXarch(["linux", "darwin", "windows", "netbsd", "freebsd"], [ "aarch64", diff --git a/ports/jq_ghrel.ts b/ports/jq_ghrel.ts index c608dfb1..27fa2674 100644 --- a/ports/jq_ghrel.ts +++ b/ports/jq_ghrel.ts @@ -71,7 +71,7 @@ export class Port extends GithubReleasePort { const [{ name: fileName }] = this.downloadUrls(args); const fileDwnPath = $.path(args.downloadPath).resolve(fileName); - await fileDwnPath.copyFile( + await fileDwnPath.copy( (await installPath .join("bin") .ensureDir()) diff --git a/ports/meta_cli_ghrel.ts b/ports/meta_cli_ghrel.ts index 7d1de443..033d6d63 100644 --- a/ports/meta_cli_ghrel.ts +++ b/ports/meta_cli_ghrel.ts @@ -19,7 +19,7 @@ const manifest = { name: "meta_cli_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ + buildDeps: [ // we have to use tar because their tarballs for darwin use gnu sparse std_ports.tar_aa, ], diff --git a/ports/mold.ts b/ports/mold.ts index 5ebef9a8..cc32e3da 100644 --- a/ports/mold.ts +++ b/ports/mold.ts @@ -18,7 +18,7 @@ const manifest = { name: "mold_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ + buildDeps: [ // we have to use tar because their tarballs contain symlinks std_ports.tar_aa, ], diff --git a/ports/node.ts b/ports/node.ts index 5e1be2cc..23405ab6 100644 --- a/ports/node.ts +++ b/ports/node.ts @@ -30,7 +30,7 @@ export const manifest = { moduleSpecifier: import.meta.url, // FIXME: tar doens't support windows // TODO: platform disambiguated deps - deps: [tar_aa_id], + buildDeps: [tar_aa_id], // NOTE: node supports more archs than deno but we can't include it here platforms: osXarch(["linux", "darwin", "windows"], ["aarch64", "x86_64"]), }; diff --git a/ports/npmi.ts b/ports/npmi.ts index d86f2152..835f191a 100644 --- a/ports/npmi.ts +++ b/ports/npmi.ts @@ -23,7 +23,7 @@ const manifest = { name: "npmi_npm", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ + buildDeps: [ std_ports.node_org, ], // NOTE: enable all platforms. Restrictions will apply based diff --git a/ports/pipi.ts b/ports/pipi.ts index 96d54567..1e3c05bc 100644 --- a/ports/pipi.ts +++ b/ports/pipi.ts @@ -23,7 +23,7 @@ const manifest = { name: "pipi_pypi", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [std_ports.cpy_bs_ghrel], + buildDeps: [std_ports.cpy_bs_ghrel], // NOTE: enable all platforms. Restrictions will apply based // cpy_bs support this way platforms: osXarch([...ALL_OS], [...ALL_ARCH]), diff --git a/ports/ruff.ts b/ports/ruff.ts index 10233101..777bd9eb 100644 --- a/ports/ruff.ts +++ b/ports/ruff.ts @@ -19,7 +19,7 @@ const manifest = { name: "ruff_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ + buildDeps: [ // we have to use tar because their tarballs for darwin use gnu sparse std_ports.tar_aa, ], diff --git a/ports/rust.ts b/ports/rust.ts index 88f84183..bda15bea 100644 --- a/ports/rust.ts +++ b/ports/rust.ts @@ -25,7 +25,7 @@ export const manifest = { name: "rust_rustup", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [rustup_rustlang_id], + buildDeps: [rustup_rustlang_id], // NOTE: indirectly limited by rustup instead platforms: osXarch([...ALL_OS], [...ALL_ARCH]), }; diff --git a/ports/rustup.ts b/ports/rustup.ts index 5c817225..3ab972e7 100644 --- a/ports/rustup.ts +++ b/ports/rustup.ts @@ -26,7 +26,7 @@ export const manifest = { name: "rustup_rustlang", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [git_aa_id], + buildDeps: [git_aa_id], resolutionDeps: [git_aa_id], platforms: [ ...osXarch(["darwin", "linux"], [...ALL_ARCH]), diff --git a/ports/temporal_cli.ts b/ports/temporal_cli.ts index 5a70971e..4bc867a6 100644 --- a/ports/temporal_cli.ts +++ b/ports/temporal_cli.ts @@ -17,7 +17,6 @@ const manifest = { name: "temporal_cli_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [], platforms: osXarch(["linux", "darwin", "windows"], ["aarch64", "x86_64"]), }; diff --git a/ports/terraform.ts b/ports/terraform.ts index bdd11037..f811d5a2 100644 --- a/ports/terraform.ts +++ b/ports/terraform.ts @@ -21,7 +21,6 @@ export const manifest = { name: "terraform_hashicorp", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [], platforms: osXarch([...ALL_OS], ["aarch64", "x86_64"]), }; diff --git a/ports/wasmedge.ts b/ports/wasmedge.ts index 4f9137b8..be3f915b 100644 --- a/ports/wasmedge.ts +++ b/ports/wasmedge.ts @@ -19,7 +19,7 @@ const manifest = { name: "wasmedge_ghrel", version: "0.1.0", moduleSpecifier: import.meta.url, - deps: [ + buildDeps: [ std_ports.tar_aa, ], platforms: osXarch(["linux", "darwin"], ["aarch64", "x86_64"]), diff --git a/tests/ambient.ts b/tests/ambient.ts index 821c0d7f..146b60d3 100644 --- a/tests/ambient.ts +++ b/tests/ambient.ts @@ -7,6 +7,7 @@ import * as tar from "../ports/tar.ts"; import * as git from "../ports/git.ts"; import * as curl from "../ports/curl.ts"; import * as unzip from "../ports/unzip.ts"; +import logger from "../utils/logger.ts"; const manifests = [ tar.manifest, @@ -19,7 +20,7 @@ for (const manUnclean of manifests) { Deno.test(`ambient access ${manifest.name}`, async () => { const plug = new AmbientAccessPort(manifest); const versions = await plug.listAll(); - console.log(versions); + logger(import.meta).info(versions); std_assert.assertEquals(versions.length, 1); }); } diff --git a/tests/envs.ts b/tests/envs.ts new file mode 100644 index 00000000..f4736b34 --- /dev/null +++ b/tests/envs.ts @@ -0,0 +1,204 @@ +import "../setup_logger.ts"; +import { + dockerE2eTest, + E2eTestCase, + type EnvDefArgs, + genTsGhjkFile, + localE2eTest, +} from "./utils.ts"; +import dummy from "../ports/dummy.ts"; + +type CustomE2eTestCase = Omit & { + ePoint: string; + stdin: string; + envs: EnvDefArgs[]; +}; + +const envVarTestEnvs: EnvDefArgs[] = [ + { + name: "main", + vars: { + SONG: "ditto", + }, + }, + { + name: "sss", + vars: { + SING: "Seoul Sonyo Sound", + }, + }, + { + name: "yuki", + base: false, + vars: { + HUMM: "Soul Lady", + }, + }, +]; +const envVarTestsPosix = ` +set -ex +# by default, we should be in main +[ "$SONG" = "ditto" ] || exit 101 + +ghjk envs cook sss +. .ghjk/envs/sss/activate.sh +# by default, envs should be based on main +# so they should inherit it's env vars +[ "$SONG" = "ditto" ] || exit 102 +[ "$SING" = "Seoul Sonyo Sound" ] || exit 103 + +# go back to main and "sss" variables shouldn't be around +. .ghjk/envs/main/activate.sh +[ "$SONG" = "ditto" ] || exit 104 +[ "$SING" = "Seoul Sonyo Sound" ] && exit 105 + +# env base is false for "yuki" and thus no vars from "main" +ghjk envs cook yuki +. .ghjk/envs/yuki/activate.sh +[ "$SONG" = "ditto" ] && exit 102 +[ "$HUMM" = "Soul Lady" ] || exit 103 +`; +const envVarTestsFish = ` +# by default, we should be in main +test "$SONG" = "ditto"; or exit 101; + +ghjk envs cook sss +. .ghjk/envs/sss/activate.fish +# by default, envs should be based on main +# so they should inherit it's env vars +test "$SONG" = "ditto"; or exit 103 +test "$SING" = "Seoul Sonyo Sound"; or exit 104 + +# go back to main and "sss" variables shouldn't be around +. .ghjk/envs/main/activate.fish +test $SONG" = "ditto"; or exit 105 +test $SING" = "Seoul Sonyo Sound"; and exit 106 + +# env base is false for "yuki" and thus no vars from "main" +ghjk envs cook yuki +. .ghjk/envs/yuki/activate.fish +test "$SONG" = "ditto"; and exit 107 +test "$HUMM" = "Soul Lady"; or exit 108 +`; + +const installTestEnvs: EnvDefArgs[] = [ + { + name: "main", + installs: [ + dummy({ output: "main" }), + ], + }, + { + name: "foo", + base: false, + installs: [ + dummy({ output: "foo" }), + ], + }, +]; + +const installTestsPosix = ` +set -eux +# by default, we should be in main +[ "$(dummy)" = "main" ] || exit 101; + +ghjk envs cook foo +. .ghjk/envs/foo/activate.sh +[ "$(dummy)" = "foo" ] || exit 102; + +. .ghjk/envs/main/activate.sh +[ "$(dummy)" = "main" ] || exit 102; +`; + +const installTestsFish = ` +# by default, we should be in main +test (dummy) = "main"; or exit 101; + +ghjk envs cook foo +. .ghjk/envs/foo/activate.fish +test (dummy) = "foo"; or exit 102; + +. .ghjk/envs/main/activate.fish +test (dummy) = "main"; or exit 102; +`; + +const cases: CustomE2eTestCase[] = [ + { + name: "prov_env_vars_bash", + ePoint: `bash -s`, + envs: envVarTestEnvs, + stdin: envVarTestsPosix, + }, + { + name: "prov_env_vars_zsh", + ePoint: `zsh -s`, + envs: envVarTestEnvs, + stdin: envVarTestsPosix, + }, + { + name: "prov_env_vars_fish", + ePoint: `fish`, + envs: envVarTestEnvs, + stdin: envVarTestsFish, + }, + { + name: "prov_port_installs_bash", + ePoint: `bash -l`, + envs: installTestEnvs, + stdin: installTestsPosix, + }, + { + name: "prov_port_installs_zsh", + ePoint: `zsh -l`, + envs: installTestEnvs, + stdin: installTestsPosix, + }, + { + name: "prov_port_installs_fish", + ePoint: `fish`, + envs: installTestEnvs, + stdin: installTestsFish, + }, +]; + +function testMany( + testGroup: string, + cases: CustomE2eTestCase[], + testFn: (inp: E2eTestCase) => Promise, + defaultEnvs: Record = {}, +) { + for (const testCase of cases) { + Deno.test( + `${testGroup} - ${testCase.name}`, + () => + testFn({ + ...testCase, + tsGhjkfileStr: genTsGhjkFile( + { envDefs: testCase.envs }, + ), + ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], + envVars: { + ...defaultEnvs, + ...testCase.envVars, + }, + }), + ); + } +} + +const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); +if (e2eType == "both") { + testMany("envsDockerE2eTest", cases, dockerE2eTest); + testMany(`envsLocalE2eTest`, cases, localE2eTest); +} else if (e2eType == "local") { + testMany("envsLocalE2eTest", cases, localE2eTest); +} else if ( + e2eType == "docker" || + !e2eType +) { + testMany("envsDockerE2eTest", cases, dockerE2eTest); +} else { + throw new Error( + `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, + ); +} diff --git a/tests/hooks.ts b/tests/hooks.ts index baa8f9a0..120153da 100644 --- a/tests/hooks.ts +++ b/tests/hooks.ts @@ -2,8 +2,8 @@ import "../setup_logger.ts"; import { dockerE2eTest, E2eTestCase, + genTsGhjkFile, localE2eTest, - tsGhjkFileFromInstalls, } from "./utils.ts"; import dummy from "../ports/dummy.ts"; import type { InstallConfigFat } from "../port.ts"; @@ -171,13 +171,13 @@ function testMany( () => testFn({ ...testCase, - tsGhjkfileStr: tsGhjkFileFromInstalls( + tsGhjkfileStr: genTsGhjkFile( { installConf: testCase.installConf ?? dummy(), taskDefs: [] }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], - envs: { + envVars: { ...defaultEnvs, - ...testCase.envs, + ...testCase.envVars, }, }), ); diff --git a/tests/ports.ts b/tests/ports.ts index e52e9b8e..40cc5b42 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -4,10 +4,11 @@ import { stdSecureConfig } from "../mod.ts"; import { dockerE2eTest, E2eTestCase, + genTsGhjkFile, localE2eTest, - tsGhjkFileFromInstalls, } from "./utils.ts"; import * as ports from "../ports/mod.ts"; +import dummy from "../ports/dummy.ts"; import type { InstallConfigFat, PortsModuleSecureConfig, @@ -21,6 +22,12 @@ type CustomE2eTestCase = Omit & { }; // order tests by download size to make failed runs less expensive const cases: CustomE2eTestCase[] = [ + // 0 megs + { + name: "dummy", + installConf: dummy(), + ePoint: `dummy`, + }, // 2 megs { name: "jq", @@ -214,7 +221,7 @@ function testMany( std_async.deadline( testFn({ ...testCase, - tsGhjkfileStr: tsGhjkFileFromInstalls( + tsGhjkfileStr: genTsGhjkFile( { installConf: testCase.installConf, secureConf: testCase.secureConf, @@ -223,22 +230,27 @@ function testMany( ), ePoints: [ ...["bash -c", "fish -c", "zsh -c"].map((sh) => ({ - cmd: `env ${sh} '${testCase.ePoint}'`, + cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], })), - // FIXME: better tests for the `InstallDb` + /* // FIXME: better tests for the `InstallDb` // installs db means this shouldn't take too long // as it's the second sync - { cmd: "env bash -c 'timeout 1 ghjk env sync'" }, + { + cmd: [ + ..."env".split(" "), + "bash -c 'timeout 1 ghjk envs cook'", + ], + }, */ ], - envs: { + envVars: { ...defaultEnvs, - ...testCase.envs, + ...testCase.envVars, }, }), // building the test docker image might taka a while // but we don't want some bug spinlocking the ci for // an hour - 300_000, + 5 * 60 * 1000, ), }, ); diff --git a/tests/tasks.ts b/tests/tasks.ts index 20f6a487..82ec3a91 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -2,17 +2,19 @@ import "../setup_logger.ts"; import { dockerE2eTest, E2eTestCase, + genTsGhjkFile, localE2eTest, type TaskDefArgs, - tsGhjkFileFromInstalls, } from "./utils.ts"; import * as ghjk from "../mod.ts"; import * as ports from "../ports/mod.ts"; +import { stdSecureConfig } from "../ghjkfiles/mod.ts"; type CustomE2eTestCase = Omit & { ePoint: string; stdin: string; tasks: TaskDefArgs[]; + enableRuntimesOnMasterPDAL?: boolean; }; const cases: CustomE2eTestCase[] = [ { @@ -70,6 +72,7 @@ ghjk x protoc`, }], ePoint: `fish`, stdin: `ghjk x test`, + enableRuntimesOnMasterPDAL: true, }, { name: "default_port_deps", @@ -129,13 +132,18 @@ function testMany( () => testFn({ ...testCase, - tsGhjkfileStr: tsGhjkFileFromInstalls( - { installConf: [], taskDefs: testCase.tasks }, + tsGhjkfileStr: genTsGhjkFile( + { + taskDefs: testCase.tasks, + secureConf: stdSecureConfig({ + enableRuntimes: testCase.enableRuntimesOnMasterPDAL, + }), + }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], - envs: { + envVars: { ...defaultEnvs, - ...testCase.envs, + ...testCase.envVars, }, }), ); diff --git a/tests/test-alpine.Dockerfile b/tests/test-alpine.Dockerfile index b1626ce8..e9655187 100644 --- a/tests/test-alpine.Dockerfile +++ b/tests/test-alpine.Dockerfile @@ -46,7 +46,7 @@ RUN ln -s ./main.ts /bin/ghjk WORKDIR /app -ENV GHJK_LOG=debug +ENV GHJK_LOG=info ENV GHJK_INSTALL_EXE_DIR=/usr/bin ENV GHJK_INSTALL_HOOK_SHELLS=fish,bash,zsh # share the module cache of the image @@ -67,7 +67,7 @@ RUN <; - ePoints: { cmd: string; stdin?: string }[]; + envVars?: Record; + ePoints: { cmd: string | string[]; stdin?: string }[]; }; const dockerCmd = (Deno.env.get("DOCKER_CMD") ?? "docker").split(/\s/); @@ -22,7 +23,7 @@ const templateStrings = { }; export async function dockerE2eTest(testCase: E2eTestCase) { - const { name, envs: testEnvs, ePoints, tsGhjkfileStr } = testCase; + const { name, envVars: testEnvs, ePoints, tsGhjkfileStr } = testCase; const tag = `ghjk_e2e_${name}`; const env = { ...testEnvs, @@ -34,18 +35,6 @@ export async function dockerE2eTest(testCase: E2eTestCase) { // repo in the host fs to point to the copy of the // repo in the image .replaceAll(devGhjkPath, "file://$ghjk/") - // .replace(/\\/g, "\\\\") - // - // escape backticks - // .replace(/`/g, "\\`") - // escpape ${VAR} types of vars - // .replace(/\$\{([^\}]*)\}/g, "\\${$1}") - // escpae $VAR types of vars - // double dollar is treated as escpae so place a mark betewen - // .replace(/\$([A-Za-z])/g, "\\$$1") - // .replace(/\$([A-Za-z])/g, "\\$$1") - // remove mark - // .replace(//g, "") .replaceAll("$ghjk", "/ghjk"); const dFile = dbg(dFileTemplate @@ -70,13 +59,17 @@ export async function dockerE2eTest(testCase: E2eTestCase) { ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) .flat(), tag, - ePoint.cmd, - ]}` + ]} ${ePoint.cmd}` .env(env); if (ePoint.stdin) { cmd = cmd.stdinText(ePoint.stdin!); } - await cmd; + try { + await cmd; + } catch (err) { + logger(import.meta).error(err); + throw err; + } } await $ .raw`${dockerCmd} rmi '${tag}'` @@ -84,7 +77,7 @@ export async function dockerE2eTest(testCase: E2eTestCase) { } export async function localE2eTest(testCase: E2eTestCase) { - const { envs: testEnvs, ePoints, tsGhjkfileStr } = testCase; + const { envVars: testEnvs, ePoints, tsGhjkfileStr } = testCase; const tmpDir = $.path( await Deno.makeTempDir({ prefix: "ghjk_le2e_", @@ -104,6 +97,9 @@ export async function localE2eTest(testCase: E2eTestCase) { ZDOTDIR: ghjkShareDir.toString(), GHJK_SHARE_DIR: ghjkShareDir.toString(), PATH: `${ghjkShareDir.toString()}:${Deno.env.get("PATH")}`, + // shield tests from external envs + GHJK_ENV: "main", + HOME: tmpDir.toString(), }; // install ghjk await install({ @@ -117,10 +113,11 @@ export async function localE2eTest(testCase: E2eTestCase) { // don't modify system shell configs shellsToHook: [], }); + await $`${ghjkShareDir.join("ghjk").toString()} print config` .cwd(tmpDir.toString()) .env(env); - await $`${ghjkShareDir.join("ghjk").toString()} ports sync` + await $`${ghjkShareDir.join("ghjk").toString()} envs cook` .cwd(tmpDir.toString()) .env(env); /* @@ -142,6 +139,7 @@ export async function localE2eTest(testCase: E2eTestCase) { for (const ePoint of ePoints) { let cmd = $.raw`${ePoint.cmd}` .cwd(tmpDir.toString()) + .clearEnv() .env(env); if (ePoint.stdin) { cmd = cmd.stdinText(ePoint.stdin); @@ -151,16 +149,17 @@ export async function localE2eTest(testCase: E2eTestCase) { await tmpDir.remove({ recursive: true }); } -export function tsGhjkFileFromInstalls( - { installConf, secureConf, taskDefs }: { - installConf: InstallConfigFat | InstallConfigFat[]; +export function genTsGhjkFile( + { installConf, secureConf, taskDefs, envDefs }: { + installConf?: InstallConfigFat | InstallConfigFat[]; secureConf?: PortsModuleSecureConfig; - taskDefs: TaskDefArgs[]; + taskDefs?: TaskDefArgs[]; + envDefs?: EnvDefArgs[]; }, ) { - const installConfArray = Array.isArray(installConf) - ? installConf - : [installConf]; + const installConfArray = installConf + ? Array.isArray(installConf) ? installConf : [installConf] + : []; const serializedPortsInsts = JSON.stringify( installConfArray, @@ -177,21 +176,33 @@ export function tsGhjkFileFromInstalls( secureConf ?? null, (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, ); - const tasks = taskDefs.map( + const tasks = (taskDefs ?? []).map( (def) => { - const { name, ...withoutName } = def; const stringifiedSection = JSON.stringify( - withoutName, + def, (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, ); return $.dedent` - ghjk.task("${name}", { + ghjk.task({ ...JSON.parse(\`${stringifiedSection}\`), fn: ${def.fn.toString()} })`; }, ).join("\n"); + const envs = (envDefs ?? []).map( + (def) => { + const stringifiedSection = JSON.stringify( + def, + (_, val) => + typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, + ); + return $.dedent` + ghjk.env({ + ...JSON.parse(\`${stringifiedSection}\`), + })`; + }, + ).join("\n"); return ` export { ghjk } from "$ghjk/mod.ts"; import * as ghjk from "$ghjk/mod.ts"; @@ -207,5 +218,6 @@ ${serializedSecConf} export const secureConfig = JSON.parse(secConfStr); ${tasks} +${envs} `; } diff --git a/utils/logger.ts b/utils/logger.ts index 839b39d4..c65b0f4b 100644 --- a/utils/logger.ts +++ b/utils/logger.ts @@ -58,14 +58,15 @@ function formatter(lr: std_log.LogRecord) { return msg; } -export class ConsoleErrHandler extends std_log.handlers.BaseHandler { +export class ConsoleErrHandler extends std_log.BaseHandler { constructor( levelName: std_log.LevelName, - options: std_log.HandlerOptions = { formatter }, + options: std_log.BaseHandlerOptions = { formatter }, ) { super(levelName, options); } override log(msg: string): void { + // deno-lint-ignore no-console console.error(msg); } override format(logRecord: std_log.LogRecord): string { @@ -99,7 +100,7 @@ export class TestConsoleErrHandler extends ConsoleErrHandler { constructor( public throwLevel: number, levelName: std_log.LevelName, - options: std_log.HandlerOptions = { formatter }, + options: std_log.BaseHandlerOptions = { formatter }, ) { super(levelName, options); } diff --git a/utils/mod.ts b/utils/mod.ts index d35b9d42..77fdf4bf 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -122,9 +122,11 @@ export function tryDepExecShimPath( return path; } -// Lifted from https://deno.land/x/hextools@v1.0.0 -// MIT License -// Copyright (c) 2020 Santiago Aguilar Hernández +/** + * Lifted from https://deno.land/x/hextools@v1.0.0 + * MIT License + * Copyright (c) 2020 Santiago Aguilar Hernández + */ export function bufferToHex(buffer: ArrayBuffer): string { return Array.prototype.map.call( new Uint8Array(buffer), @@ -139,6 +141,7 @@ export async function bufferHashHex( const hashBuf = await crypto.subtle.digest(algo, buf); return bufferToHex(hashBuf); } + export async function stringHashHex( val: string, algo: AlgorithmIdentifier = "SHA-256", @@ -163,21 +166,21 @@ export function getPortRef(manifest: PortManifest) { export async function getInstallHash(install: InstallConfigResolvedX) { const fullHashHex = await objectHashHex(install as jsonHash.Tree); const hashHex = fullHashHex.slice(0, 8); - return `${install.portRef}+${hashHex}`; + return `${install.portRef}!${hashHex}`; } -export type PathRef = dax.PathRef; +export type Path = dax.Path; export function defaultCommandBuilder() { const builder = new dax.CommandBuilder() .printCommand(true); - builder.setPrintCommandLogger((_, cmd) => { + builder.setPrintCommandLogger((cmd) => { // clean up the already colorized print command logs // TODO: remove when https://github.com/dsherret/dax/pull/203 // is merged return logger().debug( "spawning", - $.stripAnsi(cmd).split(/\s/), + cmd, ); }); return builder; @@ -193,10 +196,10 @@ export const $ = dax.build$( iterableLimit: 500, }); }, - pathToString(path: dax.PathRef) { + pathToString(path: Path) { return path.toString(); }, - async removeIfExists(path: dax.PathRef | string) { + async removeIfExists(path: Path | string) { const pathRef = $.path(path); if (await pathRef.exists()) { await pathRef.remove({ recursive: true }); @@ -340,7 +343,10 @@ export type DownloadFileArgs = { mode?: number; headers?: Record; }; -/// This avoid re-downloading a file if it's already successfully downloaded before. + +/** + * This avoid re-downloading a file if it's already successfully downloaded before. + */ export async function downloadFile( args: DownloadFileArgs, ) { @@ -365,11 +371,11 @@ export async function downloadFile( await $.path(downloadPath).ensureDir(); - await tmpFilePath.copyFile(fileDwnPath); + await tmpFilePath.copy(fileDwnPath); return downloadPath.toString(); } -/* * +/** * This returns a tmp path that's guaranteed to be * on the same file system as targetDir by * checking if $TMPDIR satisfies that constraint @@ -402,8 +408,19 @@ export async function sameFsTmpRoot( // take care of it return $.path(await Deno.makeTempDir({ prefix: "ghjk_sync" })); } + export type Rc = ReturnType>; +/** + * A reference counted box that runs the dispose method when all refernces + * are disposed of.. + * @example Basic usage + * ``` + * using myVar = rc(setTimeout(() => console.log("hola)), clearTimeout) + * spawnOtherThing(myVar.clone()); + * // dispose will only run here as long as `spawnOtherThing` has no references + * ``` + */ export function rc(val: T, onDrop: (val: T) => void) { const rc = { counter: 1, @@ -429,6 +446,10 @@ export function rc(val: T, onDrop: (val: T) => void) { export type AsyncRc = ReturnType>; +/** + * A reference counted box that makse use of `asyncDispose`. + * `async using myVar = asyncRc(setTimeout(() => console.log("hola)), clearTimeout)` + */ export function asyncRc(val: T, onDrop: (val: T) => Promise) { const rc = { counter: 1, @@ -499,6 +520,11 @@ export async function expandGlobsAndAbsolutize(path: string, wd: string) { } return [std_path.resolve(wd, path)]; } + +/** + * Unwrap the result object returned by the `safeParse` method + * on zod schemas. + */ export function unwrapParseRes( res: zod.SafeParseReturnType, cause: object = {}, @@ -514,3 +540,28 @@ export function unwrapParseRes( } return res.data; } + +/** + * Attempts to detect the shell in use by the user. + */ +export async function detectShellPath(): Promise { + let path = Deno.env.get("SHELL"); + if (!path) { + try { + path = await $`ps -p ${Deno.ppid} -o comm=`.text(); + } catch { + return; + } + } + return path; +} + +/** + * {@inheritdoc detectShellPath} + */ +export async function detectShell(): Promise { + const shellPath = await detectShellPath(); + return shellPath + ? std_path.basename(shellPath, ".exe").toLowerCase().trim() + : undefined; +} diff --git a/utils/unarchive.ts b/utils/unarchive.ts index d6a581c0..4ff3b3fb 100644 --- a/utils/unarchive.ts +++ b/utils/unarchive.ts @@ -8,9 +8,11 @@ import { std_untar, } from "../deps/ports.ts"; -/// Uses file extension to determine type -/// Does not support extracting symlinks -/// Does not support tarballs using [GnuSparse](https://www.gnu.org/software/tar/manual/html_node/Sparse-Recovery.html) +/** + * - Uses file extension to determine archive type. + * - Does not support extracting symlinks + * - Does not support tarballs using {@link https://www.gnu.org/software/tar/manual/html_node/Sparse-Recovery.html | GnuSparse} + */ export async function unarchive( path: string, dest = "./", @@ -71,7 +73,9 @@ export async function untar( } } -/// This does not close the reader +/** + * This does not close the reader. + */ export async function untarReader( reader: Deno.Reader, dest = "./", From 1376effeffb92236d12d2522024a052433872f29 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 7 May 2024 04:31:21 +0300 Subject: [PATCH 07/21] feat: env hooks (#59) * feat(envs,tasks): env `onEnter`/`onExit` hooks * wip: anon tasks * feat: anonymous tasks * tests: anon tasks * wip: wip * feat: env onEnter/onExit hooks * tests: env hook tests * fix: bug in path outputs * fix: use latest setup-ghjk * fix: bug on check task * chore: bump deno to 1.43.1 * fix: miss field in test harness * fix: use `no-prune` on docker tests * fix: don't rmi at all * fix: timeout --- .ghjk/deno.lock | 110 ++++++- .ghjk/lock.json | 126 +++++--- .github/workflows/nightly.yml | 4 +- .github/workflows/tests.yml | 6 +- .gitignore | 1 + README.md | 5 + check.ts | 1 - deno.jsonc | 13 +- deno.lock | 26 ++ deps/common.ts | 18 +- docs/architecture.md | 2 +- examples/tasks/ghjk.ts | 14 +- {ghjkfiles => files}/deno/mod.ts | 0 {ghjkfiles => files}/deno/worker.ts | 2 +- {ghjkfiles => files}/mod.ts | 464 ++++++++++++++++++++++------ ghjk.ts | 8 +- host/mod.ts | 317 +++++++++---------- install.sh | 2 +- install/ghjk.sh | 10 +- install/mod.ts | 99 +++--- install/utils.ts | 44 +++ main.ts | 20 +- mod.ts | 120 +++++-- modules/envs/mod.ts | 18 +- modules/envs/posix.ts | 104 +++++-- modules/envs/reducer.ts | 5 + modules/envs/types.ts | 13 + modules/ports/db.ts | 38 ++- modules/ports/sync.ts | 101 +++--- modules/ports/types.ts | 19 -- modules/tasks/deno.ts | 8 +- modules/tasks/exec.ts | 123 +++++--- modules/tasks/mod.ts | 20 +- modules/tasks/types.ts | 47 ++- modules/types.ts | 7 +- ports/mold.ts | 2 +- ports/npmi.ts | 2 +- ports/pipi.ts | 2 +- tests/envHooks.ts | 157 ++++++++++ tests/envs.ts | 98 +++--- tests/ports.ts | 98 ++---- tests/{hooks.ts => reloadHooks.ts} | 70 +---- tests/tasks.ts | 146 +++++---- tests/test.Dockerfile | 2 +- tests/utils.ts | 76 ++++- utils/logger.ts | 2 +- utils/mod.ts | 110 +++---- 47 files changed, 1774 insertions(+), 906 deletions(-) rename {ghjkfiles => files}/deno/mod.ts (100%) rename {ghjkfiles => files}/deno/worker.ts (98%) rename {ghjkfiles => files}/mod.ts (55%) create mode 100644 install/utils.ts create mode 100644 tests/envHooks.ts rename tests/{hooks.ts => reloadHooks.ts} (66%) diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 93382d85..934a1934 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -2,15 +2,110 @@ "version": "3", "packages": { "specifiers": { - "npm:zod-validation-error": "npm:zod-validation-error@3.1.0_zod@3.22.4" + "jsr:@david/dax@0.41.0": "jsr:@david/dax@0.41.0", + "jsr:@david/which@0.3": "jsr:@david/which@0.3.0", + "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", + "jsr:@ghjk/dax@0.40.2-alpha-ghjk": "jsr:@ghjk/dax@0.40.2-alpha-ghjk", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", + "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" + }, + "jsr": { + "@david/dax@0.41.0": { + "integrity": "9e1ecf66a0415962cc8ad3ba4e3fa93ce0f1a1cc797dd95c36fdfb6977dc7fc8", + "dependencies": [ + "jsr:@david/which@^0.4.1", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@david/which@0.3.0": { + "integrity": "6bdb62c40ac90edcf328e854fa8103a8db21e7c326089cbe3c3a1cf7887d3204" + }, + "@david/which@0.4.1": { + "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" + }, + "@ghjk/dax@0.40.2-alpha-ghjk": { + "integrity": "87bc93e9947779cb2f3922fe277e21ea8c716de804b2627f80ba9e7bc3d0d019", + "dependencies": [ + "jsr:@david/which@0.3", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/bytes@0.221.0": { + "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/io@0.221.0": { + "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/bytes@^0.221.0" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + }, + "@std/streams@0.221.0": { + "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", + "dependencies": [ + "jsr:@std/io@^0.221.0" + ] + } }, "npm": { + "@noble/hashes@1.4.0": { + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dependencies": {} + }, + "multiformats@13.1.0": { + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==", + "dependencies": {} + }, "zod-validation-error@3.1.0_zod@3.22.4": { "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", "dependencies": { "zod": "zod@3.22.4" } }, + "zod-validation-error@3.2.0_zod@3.22.4": { + "integrity": "sha512-cYlPR6zuyrgmu2wRTdumEAJGuwI7eHVHGT+VyneAQxmRAKtGRL1/7pjz4wfLhz4J05f5qoSZc3rGacswgyTjjw==", + "dependencies": { + "zod": "zod@3.22.4" + } + }, "zod@3.22.4": { "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "dependencies": {} @@ -459,6 +554,19 @@ "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", + "https://deno.land/x/zod@v3.23.5/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", + "https://deno.land/x/zod@v3.23.5/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.23.5/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.23.5/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.23.5/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.23.5/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", + "https://deno.land/x/zod@v3.23.5/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.23.5/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.23.5/helpers/util.ts": "3301a69867c9e589ac5b3bc4d7a518b5212858cd6a25e8b02d635c9c32ba331c", + "https://deno.land/x/zod@v3.23.5/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "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://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/423d38e/deps/cli.ts": "4eacc555cf80686b487e7502db63a4cfbc2060a7b847d15b14cf1cc008a3b65c", diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 509afa1c..e5e4dc9d 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -5,12 +5,12 @@ "ports": { "version": "0", "configResolutions": { - "95dbc2b8c604a5996b88c5b1b4fb0c10b3e0d9cac68f57eb915b012c44288e93": { - "version": "v0.2.61", + "bciqjlw6cxddajjmznoemlmnu7mgbbm7a3hfmnd2x5oivwajmiqui5ey": { + "version": "v0.2.62", "buildDepConfigs": {}, "portRef": "act_ghrel@0.1.0" }, - "076a5b8ee3bdc68ebf20a696378458465042bb7dc1e49ac2dc98e5fa0dab3e25": { + "bciqao2s3r3r33ruox4qknfrxqrmemuccxn64dze2ylojrzp2bwvt4ji": { "version": "3.7.0", "buildDepConfigs": { "cpy_bs_ghrel": { @@ -33,7 +33,7 @@ "portRef": "pipi_pypi@0.1.0", "packageName": "pre-commit" }, - "84ecde630296f01e7cb8443c58d1596d668c357a0d9837c0a678b8a541ed0a39": { + "bciqij3g6mmbjn4a6ps4eipcy2fmw2zumgv5a3gbxycthroffihwquoi": { "version": "3.12.3", "buildDepConfigs": { "tar_aa": { @@ -49,17 +49,17 @@ }, "portRef": "cpy_bs_ghrel@0.1.0" }, - "9e3fa7742c431c34ae7ba8d1e907e50c937ccfb631fb4dcfb7a1773742abe267": { + "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { "version": "1.34", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, - "4f16c72030e922711abf15474d30e3cb232b18144beb73322b297edecfcdb86f": { + "bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": { "version": "v1.5.5,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0" }, - "a79698808eea53aedd8e83387b2f44e90a1a48d76193c5ccf0fc6efe29bd70f6": { + "bciqkpfuyqchouu5o3whigod3f5coscq2jdlwde6fztypy3x6fg6xb5q": { "version": "v26.1", "buildDepConfigs": {}, "portRef": "protoc_ghrel@0.1.0" @@ -81,20 +81,20 @@ "sets": { "ghjkEnvProvInstSet___main": { "installs": [ - "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", - "2a0176fec803325cc31d4a9b15f77f4e07938cc4", - "b6c49b375643a285e20b6ec0f7a692214bd0f392" + "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", + "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", + "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q" ], - "allowedDeps": "3c71ccb92f3785a685b27d7b897fef4b80ad6b24" + "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" }, "ghjkEnvProvInstSet___test": { "installs": [ - "aa103d26454710ca5d7f43358123341380389864", - "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f", - "2a0176fec803325cc31d4a9b15f77f4e07938cc4", - "b6c49b375643a285e20b6ec0f7a692214bd0f392" + "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa", + "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", + "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", + "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q" ], - "allowedDeps": "3c71ccb92f3785a685b27d7b897fef4b80ad6b24" + "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" } } } @@ -102,8 +102,24 @@ { "id": "tasks", "config": { - "envs": {}, - "tasks": {} + "envs": { + "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja": { + "provides": [] + } + }, + "tasks": { + "bciqe2qc66fi4voc5zoaujvysa3yffxgokfpsuxpebchmflgjaceeqry": { + "ty": "denoFile@v1", + "key": "UEiB15QTt_KnJPsbHJIOCnssrKFfjKyZxq8UqIFTCsXb3SA==", + "envHash": "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja" + }, + "bciqezzz3obs4torm2uxhgwloj6meas2wvmpnxobmwib4ey6x226qpza": { + "ty": "denoFile@v1", + "key": "UEiAGQuHMWAC4VRQJE9YCMI99mgodAeTV86EAv8ROiTRRHA==", + "envHash": "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja" + } + }, + "tasksNamed": [] } }, { @@ -113,6 +129,22 @@ "main": { "desc": "the default default environment.", "provides": [ + { + "ty": "hook.onEnter.posixExec", + "program": "ghjk", + "arguments": [ + "x", + "bciqezzz3obs4torm2uxhgwloj6meas2wvmpnxobmwib4ey6x226qpza" + ] + }, + { + "ty": "hook.onExit.posixExec", + "program": "ghjk", + "arguments": [ + "x", + "bciqe2qc66fi4voc5zoaujvysa3yffxgokfpsuxpebchmflgjaceeqry" + ] + }, { "ty": "ghjk.ports.InstallSetRef", "setId": "ghjkEnvProvInstSet___main" @@ -133,7 +165,7 @@ } ], "blackboard": { - "c4cf06e095dadfbdd5e26070bc2b7baffc5ff45f": { + "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi": { "port": { "ty": "denoWorker@v1", "name": "act_ghrel", @@ -149,7 +181,7 @@ "moduleSpecifier": "file:///ports/act.ts" } }, - "2a0176fec803325cc31d4a9b15f77f4e07938cc4": { + "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i": { "port": { "ty": "denoWorker@v1", "name": "pipi_pypi", @@ -183,7 +215,7 @@ }, "packageName": "pre-commit" }, - "b6c49b375643a285e20b6ec0f7a692214bd0f392": { + "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q": { "port": { "ty": "denoWorker@v1", "name": "cpy_bs_ghrel", @@ -207,7 +239,7 @@ "moduleSpecifier": "file:///ports/cpy_bs.ts" } }, - "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2": { + "bciqb6ua63xodzwxngnbjq35hfikiwzb3dclbqkc7e6xgjdt5jin4pia": { "manifest": { "ty": "ambientAccess@v1", "name": "tar_aa", @@ -227,7 +259,7 @@ "portRef": "tar_aa@0.1.0" } }, - "9d26d0d90f6ecdd69d0705a042b01a344aa626ee": { + "bciqfl5s36w335ducrb6f6gwb3vuwup7vzqwwg67pq42xtkngsnxqobi": { "manifest": { "ty": "ambientAccess@v1", "name": "git_aa", @@ -261,7 +293,7 @@ "portRef": "git_aa@0.1.0" } }, - "3c447f912abf18883bd05314f946740975ee0dd3": { + "bciqcfe7qyxmokpn6pgtaj35r5qg74jkehuu6cvyrtcsnegvwlm64oqy": { "manifest": { "ty": "ambientAccess@v1", "name": "curl_aa", @@ -295,7 +327,7 @@ "portRef": "curl_aa@0.1.0" } }, - "dfb0f5e74666817e6ab8cbceca0c9da271142bca": { + "bciqgkpwxjmo5phw5se4ugyiz4xua3xrd54quzmk7wdwpq3vghglogjy": { "manifest": { "ty": "ambientAccess@v1", "name": "unzip_aa", @@ -317,7 +349,7 @@ "portRef": "unzip_aa@0.1.0" } }, - "d9122eff1fe3ef56872e53dae725ff3ccb37472e": { + "bciqmcvyepuficjj3mwshsbfecwdmzch5gwxqo557icnq4zujtdllh4a": { "manifest": { "ty": "ambientAccess@v1", "name": "zstd_aa", @@ -337,7 +369,7 @@ "portRef": "zstd_aa@0.1.0" } }, - "5314c90de340dfd1ef21421dcbdcba726b4d03b9": { + "bciqk4ivbyqvpxwcaj5reufmveqldiizo6xmqiqq7njtaczgappydoka": { "manifest": { "ty": "denoWorker@v1", "name": "rustup_rustlang", @@ -368,7 +400,7 @@ "portRef": "rustup_rustlang@0.1.0" } }, - "ebba9b42698f7f065a359575f195153ca1adba7b": { + "bciqjcmf46h2h6teenwbsda35igg4hea6ro5vh6nfieehk4jkuiqaj2a": { "manifest": { "ty": "denoWorker@v1", "name": "rust_rustup", @@ -404,7 +436,7 @@ "portRef": "rust_rustup@0.1.0" } }, - "45999e7561d7f6a661191f58ee35e67755d375e0": { + "bciqpgt5wsiw4y7qzovqbt2yrdgq5mvhhjpcg6cxzt4w4taudyen44ca": { "manifest": { "ty": "denoWorker@v1", "name": "cargo_binstall_ghrel", @@ -421,7 +453,7 @@ "portRef": "cargo_binstall_ghrel@0.1.0" } }, - "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7": { + "bciqo7cq7igschrhers3wiibbqpaavdf33fdfdalr4cu7gxr7cblifby": { "manifest": { "ty": "denoWorker@v1", "name": "pnpm_ghrel", @@ -440,7 +472,7 @@ "portRef": "pnpm_ghrel@0.1.0" } }, - "16e0e281e0f961fcc805896fc146d2c011c8d694": { + "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli": { "manifest": { "ty": "denoWorker@v1", "name": "asdf_plugin_git", @@ -469,7 +501,7 @@ "portRef": "asdf_plugin_git@0.1.0" } }, - "65ca6fb1b829a92d6423b3ea701d9602d84cf6f8": { + "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi": { "manifest": { "ty": "denoWorker@v1", "name": "node_org", @@ -493,7 +525,7 @@ "portRef": "node_org@0.1.0" } }, - "d82c92542f0ed9c49a0383922c1d968ba88f0c4b": { + "bciqctvtiscapp6cmlaxuaxnyac664hs3y3xsa5kqh4ctmhbsiehusly": { "manifest": { "ty": "denoWorker@v1", "name": "cpy_bs_ghrel", @@ -520,21 +552,21 @@ "portRef": "cpy_bs_ghrel@0.1.0" } }, - "3c71ccb92f3785a685b27d7b897fef4b80ad6b24": { - "tar_aa": "e0d1f160d2d7755765f6f01a27a0c33a02ff98d2", - "git_aa": "9d26d0d90f6ecdd69d0705a042b01a344aa626ee", - "curl_aa": "3c447f912abf18883bd05314f946740975ee0dd3", - "unzip_aa": "dfb0f5e74666817e6ab8cbceca0c9da271142bca", - "zstd_aa": "d9122eff1fe3ef56872e53dae725ff3ccb37472e", - "rustup_rustlang": "5314c90de340dfd1ef21421dcbdcba726b4d03b9", - "rust_rustup": "ebba9b42698f7f065a359575f195153ca1adba7b", - "cargo_binstall_ghrel": "45999e7561d7f6a661191f58ee35e67755d375e0", - "pnpm_ghrel": "b80f4de14adc81c11569bf5f3a2d10b92ad5f1a7", - "asdf_plugin_git": "16e0e281e0f961fcc805896fc146d2c011c8d694", - "node_org": "65ca6fb1b829a92d6423b3ea701d9602d84cf6f8", - "cpy_bs_ghrel": "d82c92542f0ed9c49a0383922c1d968ba88f0c4b" + "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi": { + "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", + "node_org": "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi", + "cpy_bs_ghrel": "bciqctvtiscapp6cmlaxuaxnyac664hs3y3xsa5kqh4ctmhbsiehusly" }, - "aa103d26454710ca5d7f43358123341380389864": { + "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa": { "port": { "ty": "denoWorker@v1", "name": "protoc_ghrel", diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f26d0499..02efe78d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - DENO_VERSION: "1.42.1" + DENO_VERSION: "1.43.1" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHJK_LOG_PANIC_LEVEL: error DENO_DIR: .deno-dir @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: metatypedev/setup-ghjk@32fe7ad4eab41d5e62189208afa6fe17112a5563 + - uses: metatypedev/setup-ghjk@318209a9d215f70716a4ac89dbeb9653a2deb8bc with: installer-url: ./install.ts env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fbeda562..d05d849a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,11 +9,13 @@ on: - ready_for_review env: - DENO_VERSION: "1.42.1" + DENO_VERSION: "1.43.1" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHJK_LOG: debug GHJK_LOG_PANIC_LEVEL: error DENO_DIR: .deno-dir + # removing the images after every test is unncessary + DOCKER_NO_RMI: 1 jobs: changes: @@ -77,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: metatypedev/setup-ghjk@32fe7ad4eab41d5e62189208afa6fe17112a5563 + - uses: metatypedev/setup-ghjk@318209a9d215f70716a4ac89dbeb9653a2deb8bc with: installer-url: ./install.ts env: diff --git a/.gitignore b/.gitignore index dc99f6cd..3f14b39f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store play.* +examples/**/.ghjk diff --git a/README.md b/README.md index c15ecf6e..a36dc4cb 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,11 @@ TBD: this feature is in development. TBD: this feature is still in development. +#### Anonymous tasks + +Tasks that aren't give names can not be invoked from the CLI. They can be useful +for tasks that are meant to be common dependencies of other tasks. + ### Secure configs Certain options are configured through the `secureConfig` object. diff --git a/check.ts b/check.ts index f82503a3..77a9dfd7 100755 --- a/check.ts +++ b/check.ts @@ -1,5 +1,4 @@ #!/bin/env -S ghjk deno run --allow-env --allow-run --allow-read --allow-write=. -// # FIXME: find a way to resolve !DENO_EXEC_PATH in shebangs import "./setup_logger.ts"; import { $ } from "./utils/mod.ts"; diff --git a/deno.jsonc b/deno.jsonc index dafb6678..a2187dd4 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,8 +1,8 @@ { "tasks": { - "test": "GHJK_LOG=info deno test --parallel --unstable-worker-options --unstable-kv -A tests/*", + "test": "deno test --parallel --unstable-worker-options --unstable-kv -A tests/*", "cache": "deno cache deps/*", - "check": "deno run -A check.ts" + "check": "deno run -A ./check.ts" }, "fmt": { "exclude": [ @@ -22,7 +22,14 @@ "rules": { "include": [ "no-console", - "no-sync-fn-in-async-fn" + "no-sync-fn-in-async-fn", + "no-external-import", + "no-inferrable-types", + "no-self-compare", + "no-throw-literal" + // "verbatim-module-syntax" + // "no-await-in-loop" + // "ban-untagged-todo" ], "exclude": [ "no-explicit-any" diff --git a/deno.lock b/deno.lock index 60d8a39e..08ec8ec5 100644 --- a/deno.lock +++ b/deno.lock @@ -3,7 +3,9 @@ "packages": { "specifiers": { "jsr:@david/dax@0.40.1": "jsr:@david/dax@0.40.1", + "jsr:@david/dax@0.41.0": "jsr:@david/dax@0.41.0", "jsr:@david/which@0.3": "jsr:@david/which@0.3.0", + "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", "jsr:@ghjk/dax@0.40.2-alpha-ghjk": "jsr:@ghjk/dax@0.40.2-alpha-ghjk", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", @@ -14,7 +16,9 @@ "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", + "npm:@noble/hashes@1.4.0": "npm:@noble/hashes@1.4.0", "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" }, @@ -30,9 +34,23 @@ "jsr:@std/streams@0.221.0" ] }, + "@david/dax@0.41.0": { + "integrity": "9e1ecf66a0415962cc8ad3ba4e3fa93ce0f1a1cc797dd95c36fdfb6977dc7fc8", + "dependencies": [ + "jsr:@david/which@^0.4.1", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, "@david/which@0.3.0": { "integrity": "6bdb62c40ac90edcf328e854fa8103a8db21e7c326089cbe3c3a1cf7887d3204" }, + "@david/which@0.4.1": { + "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" + }, "@ghjk/dax@0.40.2-alpha-ghjk": { "integrity": "87bc93e9947779cb2f3922fe277e21ea8c716de804b2627f80ba9e7bc3d0d019", "dependencies": [ @@ -81,10 +99,18 @@ } }, "npm": { + "@noble/hashes@1.4.0": { + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dependencies": {} + }, "@types/node@18.16.19": { "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": {} }, + "multiformats@13.1.0": { + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==", + "dependencies": {} + }, "zod-validation-error@3.1.0_zod@3.23.3": { "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", "dependencies": { diff --git a/deps/common.ts b/deps/common.ts index 13627211..56e6f7ed 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -11,9 +11,19 @@ export * as std_fmt_colors from "https://deno.land/std@0.213.0/fmt/colors.ts"; export * as std_url from "https://deno.land/std@0.213.0/url/mod.ts"; export * as std_path from "https://deno.land/std@0.213.0/path/mod.ts"; export * as std_fs from "https://deno.land/std@0.213.0/fs/mod.ts"; -// export * as dax from "jsr:@david/dax@0.40.1"; -export * as dax from "jsr:@ghjk/dax@0.40.2-alpha-ghjk"; -export * as jsonHash from "https://deno.land/x/json_hash@0.2.0/mod.ts"; -export { default as objectHash } from "https://deno.land/x/object_hash@2.0.3/mod.ts"; +// avoid using the following directly and go through the +// wrappers in ./utils/mod.ts +export * as dax from "jsr:@david/dax@0.41.0"; +// class re-exports are tricky. +export { Path as _DaxPath } from "jsr:@david/dax@0.41.0"; +// export * as dax from "jsr:@ghjk/dax@0.40.2-alpha-ghjk"; + +export { canonicalize as json_canonicalize } from "https://deno.land/x/json_hash@0.2.0/canon.ts"; export { default as deep_eql } from "https://deno.land/x/deep_eql@v5.0.1/index.js"; +// export * as multibase16 from "npm:multiformats@13.1.0/bases/base16"; +export * as multibase32 from "npm:multiformats@13.1.0/bases/base32"; +export * as multibase64 from "npm:multiformats@13.1.0/bases/base64"; +export * as multisha2 from "npm:multiformats@13.1.0/hashes/sha2"; +export * as multihasher from "npm:multiformats@13.1.0/hashes/hasher"; +export { sha256 as syncSha256 } from "npm:@noble/hashes@1.4.0/sha256"; diff --git a/docs/architecture.md b/docs/architecture.md index 81519ee9..90d607f8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -62,7 +62,7 @@ the game in ghjk so we prefer to err on the side of modularity. argument to `getConfig`. - `ghjk/mod.ts` exposes a bunch of helpers for authoring conventional `ghjk.ts` but as far as the host is concerned, it's only aware of the - `getConfig(secureConfig?): SerializedConfig` interface. + `getConfig(ghjkfileUrl, secureConfig?): SerializedConfig` interface. ### Ghjkdir diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts index 4869b9d4..225ed3a7 100644 --- a/examples/tasks/ghjk.ts +++ b/examples/tasks/ghjk.ts @@ -2,17 +2,15 @@ export { ghjk } from "../../mod.ts"; import { logger, task } from "../../mod.ts"; import * as ports from "../../ports/mod.ts"; -task("greet", async ({ $, argv: [name] }) => { +task("greet", async ($, { argv: [name] }) => { await $`echo Hello ${name}!`; }); const ha = task({ name: "ha", - installs: [ - ports.protoc(), - ], + installs: [ports.protoc()], envVars: { STUFF: "stuffier" }, - async fn({ $ }) { + async fn($) { await $`echo $STUFF; protoc --version; `; @@ -35,7 +33,11 @@ task("hum", { fn: () => logger().info(`hum`), }); +// not all tasks need to be named +// but anon tasks can't be accessed from the CLI +const anon = task(() => logger().info("anon")); + task("hey", { - dependsOn: ["hii", "ho"], + dependsOn: ["hii", "ho", anon], fn: () => logger().info(`hey`), }); diff --git a/ghjkfiles/deno/mod.ts b/files/deno/mod.ts similarity index 100% rename from ghjkfiles/deno/mod.ts rename to files/deno/mod.ts diff --git a/ghjkfiles/deno/worker.ts b/files/deno/worker.ts similarity index 98% rename from ghjkfiles/deno/worker.ts rename to files/deno/worker.ts index c53be849..57c8201d 100644 --- a/ghjkfiles/deno/worker.ts +++ b/files/deno/worker.ts @@ -37,7 +37,7 @@ async function serializeConfig(uri: string, envVars: Record) { const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(uri); - const rawConfig = await mod.ghjk.getConfig(mod.secureConfig); + const rawConfig = await mod.ghjk.getConfig(uri, mod.secureConfig); const config = JSON.parse(JSON.stringify(rawConfig)); return { config, diff --git a/ghjkfiles/mod.ts b/files/mod.ts similarity index 55% rename from ghjkfiles/mod.ts rename to files/mod.ts index 1885cd3f..2a476e56 100644 --- a/ghjkfiles/mod.ts +++ b/files/mod.ts @@ -1,7 +1,11 @@ +//! This provides the backing implementation of the Ghjkfile frontends. + // NOTE: avoid adding sources of randomness // here to make the resulting config reasonably stable // across serializaiton. No random identifiers. +import { multibase32, multibase64 } from "../deps/common.ts"; + // ports specific imports import portsValidators from "../modules/ports/types.ts"; import type { @@ -10,12 +14,12 @@ import type { InstallSet, InstallSetRefProvision, PortsModuleConfigHashed, - PortsModuleSecureConfig, } from "../modules/ports/types.ts"; import logger from "../utils/logger.ts"; import { $, defaultCommandBuilder, + objectHash, Path, thinInstallConfig, unwrapParseRes, @@ -27,15 +31,15 @@ import * as node from "../ports/node.ts"; import type { SerializedConfig } from "../host/types.ts"; import * as std_modules from "../modules/std.ts"; // tasks -import { dax, jsonHash, objectHash } from "../deps/common.ts"; // WARN: this module has side-effects and only ever import // types from it import type { ExecTaskArgs } from "../modules/tasks/deno.ts"; -import { TasksModuleConfig } from "../modules/tasks/types.ts"; +import { TaskDefHashed, TasksModuleConfig } from "../modules/tasks/types.ts"; // envs -import { +import type { EnvRecipe, EnvsModuleConfig, + Provision, WellKnownProvision, } from "../modules/envs/types.ts"; @@ -43,7 +47,7 @@ export type EnvDefArgs = { name: string; installs?: InstallConfigFat[]; allowedPortDeps?: AllowedPortDep[]; - /* + /** * If true or not set, will base the task's env on top * of the default env (usually `main`). If false, will build on * top of a new env. If given a string, will use the identified env as a base @@ -52,22 +56,33 @@ export type EnvDefArgs = { base?: string | boolean; desc?: string; vars?: Record; + /** + * Task to execute when environment is activated. + */ + onEnter?: string | string[]; + /** + * Task to execute when environment is deactivated. + */ + onExit?: string | string[]; }; export type TaskFnArgs = { - $: dax.$Type; + $: ReturnType; argv: string[]; env: Record; + workingDir: string; }; -export type TaskFn = (args: TaskFnArgs) => Promise | any; +export type TaskFn = ( + $: ReturnType, + args: TaskFnArgs, +) => Promise | any; -/* - * Configuration for a task. +/** + * Configure a task under the given name or key. */ export type TaskDefArgs = { - name: string; - fn: TaskFn; + name?: string; desc?: string; dependsOn?: string[]; workingDir?: string | Path; @@ -77,12 +92,60 @@ export type TaskDefArgs = { base?: string | boolean; }; -export class GhjkfileBuilder { +export type DenoTaskDefArgs = TaskDefArgs & { + /** + * The logic to run when the task is invoked. + * + * Note: functions are optional for tasks. If none is set, + * it'll be a no-op. The task it depends on will still be run. + */ + fn?: TaskFn; + /** + * In order to key the right task when ghjk is requesting + * execution of a specific task, we identify each using a hash. + * The {@field fn} is `toString`ed in the hash input. + * If a ghjkfile is produing identical anonymous tasks for + * instance, it can provide a none to disambiguate beteween each + * through hash differences. + * + * NOTE: the nonce must be stable across serialization. + * NOTE: closing over values is generally ill-advised on tasks + * fns. If you want to close over values, make sure they're stable + * across re-serializations. + */ + nonce?: string; +}; + +type TaskDefTyped = DenoTaskDefArgs & { ty: "denoFile@v1" }; + +export class Ghjkfile { #installSets = new Map(); - #tasks = {} as Record; + #tasks = new Map(); #bb = new Map(); #seenEnvs: Record = {}; + /* dump() { + return { + installSets: Object.fromEntries(this.#installSets), + bb: Object.fromEntries(this.#bb), + seenEnvs: Object.fromEntries( + Object.entries(this.#seenEnvs).map(( + [key, [_builder, finalizer]], + ) => [key, finalizer()]), + ), + tasks: Object.fromEntries( + Object.entries(this.#tasks).map(([key, task]) => [key, { + ...task, + ...(task.ty === "denoFile@v1" + ? { + fn: task.fn.toString(), + } + : {}), + }]), + ), + }; + } */ + addInstall(setId: string, configUnclean: InstallConfigFat) { const config = unwrapParseRes( portsValidators.installConfigFat.safeParse(configUnclean), @@ -94,7 +157,7 @@ export class GhjkfileBuilder { const set = this.#getSet(setId); set.installs.push(config); - logger().debug("install added", config); + logger(import.meta).debug("install added", config); } setAllowedPortDeps(setId: string, deps: AllowedPortDep[]) { @@ -106,7 +169,7 @@ export class GhjkfileBuilder { ); } - addTask(args: TaskDefArgs) { + addTask(args: TaskDefTyped) { // NOTE: we make sure the env base declared here exists // this call is necessary to make sure that a `task` can // be declared before the `env` but still depend on it. @@ -115,12 +178,37 @@ export class GhjkfileBuilder { if (typeof args.base == "string") { this.addEnv({ name: args.base }); } - - this.#tasks[args.name] = { + let key = args.name; + if (!key) { + switch (args.ty) { + case "denoFile@v1": { + const { fn, workingDir, ...argsRest } = args; + key = objectHash(JSON.parse(JSON.stringify({ + ...argsRest, + workingDir: workingDir instanceof Path + ? workingDir.toString() + : workingDir, + ...(fn + ? { + // NOTE: we serialize the function to a string before + // hashing. + fn: fn.toString(), + } + : {}), + }))); + key = multibase64.base64urlpad.encode( + multibase32.base32.decode(key), + ); + break; + } + default: + throw new Error(`unexpected task type: ${args.ty}`); + } + } + this.#tasks.set(key, { ...args, - name, - }; - return args.name; + }); + return key; } addEnv(args: EnvDefArgs) { @@ -145,40 +233,51 @@ export class GhjkfileBuilder { if (args.vars) { env.vars(args.vars); } + if (args.onEnter) { + env.onEnter(...args.onEnter); + } + if (args.onExit) { + env.onEnter(...args.onExit); + } return env; } async execTask( - { name, workingDir, envVars, argv }: ExecTaskArgs, + { key, workingDir, envVars, argv }: ExecTaskArgs, ) { - const task = this.#tasks[name]; + const task = this.#tasks.get(key); if (!task) { - throw new Error(`no task defined under "${name}"`); + throw new Error(`no task defined under "${key}"`); + } + if (task.ty != "denoFile@v1") { + throw new Error(`task under "${key}" has unexpected type ${task.ty}`); + } + if (task.fn) { + const custom$ = task$(argv, envVars, workingDir); + await task.fn(custom$, { argv, env: envVars, $: custom$, workingDir }); } - const custom$ = $.build$({ - commandBuilder: defaultCommandBuilder().env(envVars).cwd(workingDir), - }); - await task.fn({ argv, env: envVars, $: custom$ }); } toConfig( - { defaultEnv, defaultBaseEnv, secureConfig }: { + { defaultEnv, defaultBaseEnv, masterPortDepAllowList }: { defaultEnv: string; defaultBaseEnv: string; - secureConfig: PortsModuleSecureConfig | undefined; + ghjkfileUrl: string; + masterPortDepAllowList: AllowedPortDep[]; }, ) { try { - const envsConfig = this.#processEnvs( - defaultEnv, + const envsConfig = this.#processEnvs(defaultEnv, defaultBaseEnv); + const tasksConfig = this.#processTasks( + envsConfig, defaultBaseEnv, ); - const tasksConfig = this.#processTasks(envsConfig, defaultBaseEnv); const portsConfig = this.#processInstalls( - secureConfig?.masterPortDepAllowList ?? stdDeps(), + masterPortDepAllowList ?? stdDeps(), ); const config: SerializedConfig = { + blackboard: Object.fromEntries(this.#bb.entries()), modules: [{ id: std_modules.ports, config: portsConfig, @@ -189,7 +288,6 @@ export class GhjkfileBuilder { id: std_modules.envs, config: envsConfig, }], - blackboard: Object.fromEntries(this.#bb.entries()), }; return config; } catch (cause) { @@ -208,7 +306,7 @@ export class GhjkfileBuilder { #addToBlackboard(inp: unknown) { // jsonHash.digest is async - const hash = objectHash(jsonHash.canonicalize(inp as jsonHash.Tree)); + const hash = objectHash(JSON.parse(JSON.stringify(inp))); if (!this.#bb.has(hash)) { this.#bb.set(hash, inp); @@ -216,8 +314,9 @@ export class GhjkfileBuilder { return hash; } - // this processes the defined envs, normalizing dependency (i.e. "envBase") - // relationships to produce the standard EnvsModuleConfig + /** this processes the defined envs, normalizing dependency (i.e. "envBase") + * relationships to produce the standard EnvsModuleConfig + */ #processEnvs( defaultEnv: string, defaultBaseEnv: string, @@ -232,30 +331,30 @@ export class GhjkfileBuilder { const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) ) { const final = finalizer(); - const { name, base } = final; - const envBaseResolved = typeof base === "string" - ? base - : base + const envBaseResolved = typeof final.base === "string" + ? final.base + : final.base ? defaultBaseEnv : null; - all[name] = { ...final, envBaseResolved }; + all[final.name] = { ...final, envBaseResolved }; if (envBaseResolved) { - let parentRevDeps = revDeps.get(envBaseResolved); - if (!parentRevDeps) { - parentRevDeps = []; - revDeps.set(envBaseResolved, parentRevDeps); + const parentRevDeps = revDeps.get(envBaseResolved); + if (parentRevDeps) { + parentRevDeps.push(final.name); + } else { + revDeps.set(envBaseResolved, [final.name]); } - parentRevDeps.push(final.name); } else { - indie.push(name); + indie.push(final.name); } } + const processed = {} as Record< string, { installSetId?: string; vars: Record } >; - const out: EnvsModuleConfig = { envs: {}, defaultEnv }; - const workingSet = [...indie]; + const moduleConfig: EnvsModuleConfig = { envs: {}, defaultEnv }; + const workingSet = indie; while (workingSet.length > 0) { const item = workingSet.pop()!; const final = all[item]; @@ -307,7 +406,42 @@ export class GhjkfileBuilder { installSetId: processedInstallSetId, vars: processedVars, }; - out.envs[final.name] = { + const hooks = [ + ...final.onEnterHookTasks.map( + (key) => [key, "hook.onEnter.posixExec"] as const, + ), + ...final.onExitHookTasks.map( + (key) => [key, "hook.onExit.posixExec"] as const, + ), + ].map(([taskKey, ty]) => { + const task = this.#tasks.get(taskKey); + if (!task) { + throw new Error("unable to find task for onEnterHook", { + cause: { + env: final.name, + taskKey, + }, + }); + } + if (task.ty == "denoFile@v1") { + const prov: InlineTaskHookProvision = { + ty: "inline.hook.ghjkTask", + finalTy: ty, + taskKey, + }; + return prov; + } + throw new Error( + `unsupported task type "${task.ty}" used for environment hook`, + { + cause: { + taskKey, + task, + }, + }, + ); + }); + moduleConfig.envs[final.name] = { desc: final.desc, provides: [ ...Object.entries(processedVars).map(( @@ -316,6 +450,8 @@ export class GhjkfileBuilder { const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; return prov; }), + // env hooks + ...hooks, ], }; if (processedInstallSetId) { @@ -323,7 +459,7 @@ export class GhjkfileBuilder { ty: "ghjk.ports.InstallSetRef", setId: processedInstallSetId, }; - out.envs[final.name].provides.push(prov); + moduleConfig.envs[final.name].provides.push(prov); } const curRevDeps = revDeps.get(final.name); @@ -332,21 +468,55 @@ export class GhjkfileBuilder { revDeps.delete(final.name); } } - return out; + // sanity checks + if (revDeps.size > 0) { + throw new Error("working set empty but pending items found"); + } + return moduleConfig; } - #processTasks(envsConfig: EnvsModuleConfig, defaultBaseEnv: string) { - const out: TasksModuleConfig = { + #processTasks( + envsConfig: EnvsModuleConfig, + defaultBaseEnv: string, + ) { + const indie = [] as string[]; + const deps = new Map(); + const revDeps = new Map(); + const nameToKey = Object.fromEntries( + Object.entries(this.#tasks) + .filter(([_, { name }]) => !!name) + .map(([hash, { name }]) => [name, hash] as const), + ); + for (const [key, args] of this.#tasks) { + if (args.dependsOn && args.dependsOn.length > 0) { + const depKeys = args.dependsOn.map((nameOrKey) => + nameToKey[nameOrKey] ?? nameOrKey + ); + deps.set(key, depKeys); + for (const depKey of depKeys) { + const depRevDeps = revDeps.get(depKey); + if (depRevDeps) { + depRevDeps.push(key); + } else { + revDeps.set(depKey, [key]); + } + } + } else { + indie.push(key); + } + } + const workingSet = indie; + const localToFinalKey = {} as Record; + const moduleConfig: TasksModuleConfig = { envs: {}, tasks: {}, + tasksNamed: [], }; - for ( - const [name, args] of Object - .entries( - this.#tasks, - ) - ) { + while (workingSet.length > 0) { + const key = workingSet.pop()!; + const args = this.#tasks.get(key)!; const { workingDir, desc, dependsOn, base } = args; + const envBaseResolved = typeof base === "string" ? base : base @@ -403,7 +573,7 @@ export class GhjkfileBuilder { } } if (taskInstallSet.installs.length > 0) { - const setId = `ghjkTaskInstSet___${name}`; + const setId = `ghjkTaskInstSet___${key}`; this.#installSets.set(setId, taskInstallSet); const prov: InstallSetRefProvision = { ty: "ghjk.ports.InstallSetRef", @@ -421,32 +591,97 @@ export class GhjkfileBuilder { }), ); - const envHash = objectHash( - jsonHash.canonicalize(taskEnvRecipe as jsonHash.Tree), - ); - out.envs[envHash] = taskEnvRecipe; + const envHash = objectHash(JSON.parse(JSON.stringify(taskEnvRecipe))); + moduleConfig.envs[envHash] = taskEnvRecipe; - out.tasks[name] = { - name, + const def: TaskDefHashed = { + ty: args.ty, + key, workingDir: typeof workingDir == "object" ? workingDir.toString() : workingDir, desc, - dependsOn, + dependsOn: dependsOn?.map((keyOrHash) => + localToFinalKey[nameToKey[keyOrHash] ?? keyOrHash] + ), envHash, }; + const taskHash = objectHash(def); + // we prefer the name as a key if present + const finalKey = args.name ?? taskHash; + moduleConfig.tasks[finalKey] = def; + localToFinalKey[key] = finalKey; + + if (args.name) { + moduleConfig.tasksNamed.push(args.name); + } + for (const revDepKey of revDeps.get(key) ?? []) { + const revDepDeps = deps.get(revDepKey)!; + // swap remove + const idx = revDepDeps.indexOf(key); + const last = revDepDeps.pop()!; + if (revDepDeps.length > idx) { + revDepDeps[idx] = last; + } + + if (revDepDeps.length == 0) { + deps.delete(revDepKey); + workingSet.push(revDepKey); + } + } } - for (const [name, { dependsOn }] of Object.entries(out.tasks)) { + + // do some sanity checks + for (const [key, { dependsOn }] of Object.entries(moduleConfig.tasks)) { for (const depName of dependsOn ?? []) { - if (!out.tasks[depName]) { + if (!moduleConfig.tasks[depName]) { throw new Error( - `task "${name}" depend on non-existent task "${depName}"`, + `task "${key}" depend on non-existent task "${depName}"`, + { + cause: { + workingSet, + revDeps, + moduleConfig, + tasks: this.#tasks, + nameToKey, + }, + }, ); } } } + if (deps.size > 0) { + throw new Error("working set empty but pending items found", { + cause: { + workingSet, + revDeps, + moduleConfig, + tasks: this.#tasks, + }, + }); + } - return out; + for (const [_name, env] of Object.entries(envsConfig.envs)) { + env.provides = env.provides.map( + (prov) => { + if ( + prov.ty == "inline.hook.ghjkTask" + ) { + const inlineProv = prov as InlineTaskHookProvision; + const taskKey = localToFinalKey[inlineProv.taskKey]; + const out: WellKnownProvision = { + ty: inlineProv.finalTy, + program: "ghjk", + arguments: ["x", taskKey], + }; + return out; + } + return prov; + }, + ); + } + + return moduleConfig; } #processInstalls(masterAllowList: AllowedPortDep[]) { @@ -490,6 +725,8 @@ type EnvFinalizer = () => { base: string | boolean; vars: Record; desc?: string; + onEnterHookTasks: string[]; + onExitHookTasks: string[]; }; // this class will be exposed to users and thus features @@ -497,13 +734,15 @@ type EnvFinalizer = () => { // all to avoid exposing the function in the public api export class EnvBuilder { #installSetId: string; - #file: GhjkfileBuilder; + #file: Ghjkfile; #base: string | boolean = true; #vars: Record = {}; #desc?: string; + #onEnterHookTasks: string[] = []; + #onExitHookTasks: string[] = []; constructor( - file: GhjkfileBuilder, + file: Ghjkfile, setFinalizer: (fin: EnvFinalizer) => void, public name: string, ) { @@ -515,6 +754,8 @@ export class EnvBuilder { base: this.#base, vars: this.#vars, desc: this.#desc, + onExitHookTasks: this.#onExitHookTasks, + onEnterHookTasks: this.#onEnterHookTasks, })); } @@ -523,7 +764,7 @@ export class EnvBuilder { return this; } - /* + /** * Provision a port install in the environment. */ install(...configs: InstallConfigFat[]) { @@ -533,7 +774,7 @@ export class EnvBuilder { return this; } - /* + /** * This is treated as a single set and will replace previously any configured set. */ allowedPortDeps(deps: AllowedPortDep[]) { @@ -541,7 +782,7 @@ export class EnvBuilder { return this; } - /* + /** * Add an environment variable. */ var(key: string, val: string) { @@ -549,7 +790,7 @@ export class EnvBuilder { return this; } - /* + /** * Add multiple environment variable. */ vars(envVars: Record) { @@ -557,29 +798,29 @@ export class EnvBuilder { return this; } - /* + /** * Description of the environment. */ desc(str: string) { this.#desc = str; return this; } -} -export function stdSecureConfig( - args: { - additionalAllowedPorts?: PortsModuleSecureConfig["masterPortDepAllowList"]; - enableRuntimes?: boolean; - } & Pick, -): PortsModuleSecureConfig { - const { additionalAllowedPorts, enableRuntimes = false } = args; - const out: PortsModuleSecureConfig = { - masterPortDepAllowList: [ - ...stdDeps({ enableRuntimes }), - ...additionalAllowedPorts ?? [], - ], - }; - return out; + /** + * Tasks to execute on enter. + */ + onEnter(...taskKey: string[]) { + this.#onEnterHookTasks.push(...taskKey); + return this; + } + + /** + * Tasks to execute on enter. + */ + onExit(...taskKey: string[]) { + this.#onExitHookTasks.push(...taskKey); + return this; + } } export function stdDeps(args = { enableRuntimes: false }) { @@ -592,12 +833,41 @@ export function stdDeps(args = { enableRuntimes: false }) { node.default(), cpy.default(), ].map((fatInst) => { - return portsValidators.allowedPortDep.parse({ + const out: AllowedPortDep = { manifest: fatInst.port, defaultInst: thinInstallConfig(fatInst), - }); + }; + return portsValidators.allowedPortDep.parse(out); }), ); } return out; } + +function task$( + argv: string[], + env: Record, + workingDir: string, +) { + const custom$ = Object.assign( + // NOTE: order is important on who assigns to who + // here + $.build$({ + commandBuilder: defaultCommandBuilder().env(env).cwd(workingDir), + }), + { + argv, + env, + workingDir, + }, + ); + return custom$; +} + +type InlineTaskHookProvision = Provision & { + ty: "inline.hook.ghjkTask"; + finalTy: + | "hook.onEnter.posixExec" + | "hook.onExit.posixExec"; + taskKey: string; +}; diff --git a/ghjk.ts b/ghjk.ts index cc7acabf..33925ad7 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,5 +1,5 @@ export { ghjk } from "./mod.ts"; -import { env, install, stdSecureConfig } from "./mod.ts"; +import { env, install, stdSecureConfig, task } from "./mod.ts"; import * as ports from "./ports/mod.ts"; // these are just for quick testing @@ -9,9 +9,13 @@ install(); install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], - ports.cpy_bs({}), + ports.cpy_bs(), ); +env("main") + .onEnter(task(($) => $`echo enter`)) + .onExit(task(($) => $`echo exit`)); + env("test", { installs: [ports.protoc()], }); diff --git a/host/mod.ts b/host/mod.ts index d56afa84..e5b2cd2b 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -1,23 +1,16 @@ -import { - cliffy_cmd, - deep_eql, - jsonHash, - zod, - zod_val_err, -} from "../deps/cli.ts"; -import logger, { isColorfulTty } from "../utils/logger.ts"; - +import { cliffy_cmd, deep_eql, zod, zod_val_err } from "../deps/cli.ts"; +import logger from "../utils/logger.ts"; import { $, - bufferHashHex, + bufferHashAsync, Json, - objectHashHex, + objectHash, Path, - stringHashHex, + stringHash, } from "../utils/mod.ts"; import validators, { SerializedConfig } from "./types.ts"; import * as std_modules from "../modules/std.ts"; -import * as denoFile from "../ghjkfiles/deno/mod.ts"; +import * as denoFile from "../files/deno/mod.ts"; import type { ModuleBase } from "../modules/mod.ts"; import { GhjkCtx } from "../modules/types.ts"; import { serializePlatform } from "../modules/ports/types/platform.ts"; @@ -26,57 +19,68 @@ import { DePromisify } from "../port.ts"; export interface CliArgs { ghjkShareDir: string; ghjkfilePath?: string; + ghjkDirPath?: string; } type HostCtx = { fileHashMemoStore: Map>; + curEnvVars: Record; }; export async function cli(args: CliArgs) { - const ghjkShareDir = $.path(args.ghjkShareDir).resolve().normalize() - .toString(); // items to run at end of function const defer = [] as (() => Promise)[]; - const subcmds: Record = {}; - + const ghjkShareDir = $.path(args.ghjkShareDir).resolve().normalize(); let serializedConfig: object | undefined; - let ghjkDir: string | undefined; - let ghjkfilePath: string | undefined; + let gcx: GhjkCtx | undefined; - // most of the CLI is only avail if there's a - // ghjkfile detected - if (args.ghjkfilePath) { - ghjkfilePath = $.path(args.ghjkfilePath).resolve().normalize() - .toString(); - ghjkDir = $.path(ghjkfilePath).parentOrThrow().join(".ghjk") + if (!args.ghjkDirPath && args.ghjkfilePath) { + args.ghjkDirPath = $.path(args.ghjkfilePath).parentOrThrow().join(".ghjk") .toString(); - logger().debug({ ghjkfilePath, ghjkDir }); + } - const gcx = { ghjkShareDir, ghjkfilePath, ghjkDir, blackboard: new Map() }; - const hcx = { fileHashMemoStore: new Map() }; + const subcmds: Record = {}; - const { - subCommands: configCommands, - serializedConfig: config, - writeLockFile, - } = await readConfig( - gcx, - hcx, - ); - serializedConfig = config; - // lock entries are generated across program usage - // so we defer writing it out until the end - defer.push(writeLockFile); + // most of the CLI is only avail if there's a + // ghjkfile detected + if (args.ghjkDirPath) { + gcx = { + ghjkShareDir, + ghjkDir: $.path(args.ghjkDirPath).resolve().normalize(), + ghjkfilePath: args.ghjkfilePath + ? $.path(args.ghjkfilePath).resolve().normalize() + : undefined, + blackboard: new Map(), + }; + logger().debug({ ghjkfilePath: gcx.ghjkfilePath, ghjkDir: gcx?.ghjkDir }); - for (const [cmdName, [cmd, src]] of Object.entries(configCommands)) { - const conflict = subcmds[cmdName]; - if (conflict) { - throw new Error( - `CLI command conflict under name "${cmdName}" from host and module "${src}"`, - ); + if (!await gcx.ghjkDir.join(".gitignore").exists()) { + gcx.ghjkDir.join(".gitignore").writeText($.dedent` + envs + hash.json`); + } + + // this returns nothing if no valid lockifle or ghjkfile + // is found + const commands = await commandsFromConfig(gcx); + if (commands) { + serializedConfig = commands.config; + // lock entries are generated across program usage + // so we defer writing it out until the end + defer.push(commands.writeLockFile); + + for ( + const [cmdName, [cmd, src]] of Object.entries(commands.subCommands) + ) { + const conflict = subcmds[cmdName]; + if (conflict) { + throw new Error( + `CLI command conflict under name "${cmdName}" from host and module "${src}"`, + ); + } + subcmds[cmdName] = cmd; } - subcmds[cmdName] = cmd; } } @@ -118,7 +122,7 @@ export async function cli(args: CliArgs) { throw new Error("no ghjkfile found."); } // deno-lint-ignore no-console - console.log(ghjkShareDir); + console.log(ghjkShareDir.toString()); }), ) .command( @@ -126,11 +130,11 @@ export async function cli(args: CliArgs) { new cliffy_cmd.Command() .description("Print the path where ghjk is installed in.") .action(function () { - if (!ghjkDir) { + if (!gcx) { throw new Error("no ghjkfile found."); } // deno-lint-ignore no-console - console.log(ghjkDir); + console.log(gcx.ghjkDir.toString()); }), ) .command( @@ -138,11 +142,11 @@ export async function cli(args: CliArgs) { new cliffy_cmd.Command() .description("Print the path of the ghjk.ts used") .action(function () { - if (!ghjkfilePath) { + if (!gcx?.ghjkfilePath) { throw new Error("no ghjkfile found."); } // deno-lint-ignore no-console - console.log(ghjkfilePath); + console.log(gcx.ghjkfilePath.toString()); }), ) .command( @@ -151,15 +155,20 @@ export async function cli(args: CliArgs) { .description( "Print the extracted ans serialized config from the ghjkfile", ) - .action(function () { + .option( + "--json", + `Use json format when printing config.`, + ) + .action(function ({ json }) { if (!serializedConfig) { throw new Error("no ghjkfile found."); } // deno-lint-ignore no-console - console.log(Deno.inspect(serializedConfig, { - depth: 10, - colors: isColorfulTty(), - })); + console.log( + json + ? JSON.stringify(serializedConfig) + : $.inspect(serializedConfig), + ); }), ), ); @@ -170,42 +179,41 @@ export async function cli(args: CliArgs) { await Promise.all(defer.map((fn) => fn())); } -async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { - const configPath = $.path(gcx.ghjkfilePath); - const configFileStat = await configPath.stat(); - // FIXME: subset of ghjk commands should be functional - // even if config file not found - if (!configFileStat) { - throw new Error("unable to locate config file", { - cause: gcx, - }); - } - const ghjkDirPath = $.path(gcx.ghjkDir); - if (!await ghjkDirPath.join(".gitignore").exists()) { - ghjkDirPath.join(".gitignore").writeText($.dedent` - envs - hash.json`); - } - const lockFilePath = ghjkDirPath.join("lock.json"); - const hashFilePath = ghjkDirPath.join("hash.json"); +async function commandsFromConfig(gcx: GhjkCtx) { + const hcx: HostCtx = { + fileHashMemoStore: new Map(), + curEnvVars: Deno.env.toObject(), + }; - // command name to [cmd, source module id] - const subCommands = {} as Record; - const lockEntries = {} as Record; + const lockFilePath = gcx.ghjkDir.join("lock.json"); + const hashFilePath = gcx.ghjkDir.join("hash.json"); const foundLockObj = await readLockFile(lockFilePath); const foundHashObj = await readHashFile(hashFilePath); - const ghjkfileHash = await fileDigestHex(hcx, configPath); + const lockEntries = {} as Record; - const curEnvVars = Deno.env.toObject(); + const ghjkfileHash = await gcx.ghjkfilePath?.exists() + ? await fileDigestHex(hcx, gcx.ghjkfilePath!) + : undefined; let configExt: SerializedConfigExt | null = null; // TODO: figure out cross platform lockfiles :O if ( foundLockObj && // lockfile found - foundLockObj.version == "0" + foundHashObj && + foundLockObj.version == "0" && + // avoid reserializing the config if + // the ghjkfile and environment is _satisfcatorily_ + // similar. "cache validation" + await isHashFileValid(hcx, foundLockObj, foundHashObj, ghjkfileHash) ) { + configExt = { + config: foundLockObj.config, + envVarHashes: foundHashObj.envVarHashes, + readFileHashes: foundHashObj.readFileHashes, + listedFiles: foundHashObj.listedFiles, + }; logger().debug("loading lockfile", lockFilePath); for (const man of foundLockObj.config.modules) { const mod = std_modules.map[man.id]; @@ -226,65 +234,13 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { entry as Json, ); } - - const platformMatch = () => - serializePlatform(Deno.build) == foundLockObj.platform; - - const envHashesMatch = async () => { - const oldHashes = foundHashObj!.envVarHashes; - const newHashes = await envVarDigests(curEnvVars, [ - ...Object.keys(oldHashes), - ]); - return deep_eql(oldHashes, newHashes); - }; - - const cwd = $.path(Deno.cwd()); - const fileHashesMatch = async () => { - const oldHashes = foundHashObj!.readFileHashes; - const newHashes = await fileDigests(hcx, [ - ...Object.keys(oldHashes), - ], cwd); - return deep_eql(oldHashes, newHashes); - }; - - const fileListingsMatch = async () => { - const oldListed = foundHashObj!.listedFiles; - for (const path of oldListed) { - if (!await cwd.resolve(path).exists()) { - return false; - } - } - return true; - }; - // avoid reserializing the config if - // the ghjkfile and environment is _satisfcatorily_ - // similar. "cache validation" - if ( - // NOTE: these are ordered by the amount effort it takes - // to check each - foundHashObj && - foundHashObj.ghjkfileHash == ghjkfileHash && - platformMatch() && - await envHashesMatch() && - await fileListingsMatch() && - await fileHashesMatch() - ) { - configExt = { - config: foundLockObj.config, - envVarHashes: foundHashObj.envVarHashes, - readFileHashes: foundHashObj.readFileHashes, - listedFiles: foundHashObj.listedFiles, - }; - } - } - - // configExt will be falsy if no lockfile was found - // or if it failed cache validation - if (!configExt) { - logger().info("serializing ghjkfile", configPath); - configExt = await readAndSerializeConfig(hcx, configPath, curEnvVars); + } else if (gcx.ghjkfilePath) { + logger().info("serializing ghjkfile", gcx.ghjkfilePath); + configExt = await readGhjkfile(hcx, gcx.ghjkfilePath); + } else { + // nothing to get the commands from + return; } - const newHashObj: zod.infer = { version: "0", ghjkfileHash, @@ -292,7 +248,10 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { readFileHashes: configExt.readFileHashes, listedFiles: configExt.listedFiles, }; + // command name to [cmd, source module id] + const subCommands = {} as Record; const instances = [] as [string, ModuleBase, unknown][]; + for (const man of configExt.config.modules) { const mod = std_modules.map[man.id]; if (!mod) { @@ -324,7 +283,7 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { return { subCommands, - serializedConfig: configExt.config, + config: configExt.config, async writeLockFile() { const newLockObj: zod.infer = { version: "0", @@ -354,16 +313,60 @@ async function readConfig(gcx: GhjkCtx, hcx: HostCtx) { }; } +async function isHashFileValid( + hcx: HostCtx, + foundLockFile: zod.infer, + foundHashFile: zod.infer, + ghjkfileHash?: string, +) { + const platformMatch = () => + serializePlatform(Deno.build) == foundLockFile.platform; + + const envHashesMatch = () => { + const oldHashes = foundHashFile!.envVarHashes; + const newHashes = envVarDigests(hcx.curEnvVars, [ + ...Object.keys(oldHashes), + ]); + return deep_eql(oldHashes, newHashes); + }; + + const cwd = $.path(Deno.cwd()); + const fileHashesMatch = async () => { + const oldHashes = foundHashFile!.readFileHashes; + const newHashes = await fileDigests(hcx, [ + ...Object.keys(oldHashes), + ], cwd); + return deep_eql(oldHashes, newHashes); + }; + + const fileListingsMatch = async () => { + const oldListed = foundHashFile!.listedFiles; + for (const path of oldListed) { + if (!await cwd.resolve(path).exists()) { + return false; + } + } + return true; + }; + // NOTE: these are ordered by the amount effort it takes + // to check each + // we only check file hash of the ghjk file if it's present + return (ghjkfileHash ? foundHashFile.ghjkfileHash == ghjkfileHash : true) && + platformMatch() && + envHashesMatch() && + await fileListingsMatch() && + await fileHashesMatch(); +} + type DigestsMap = Record; type SerializedConfigExt = DePromisify< - ReturnType + ReturnType >; -async function readAndSerializeConfig( +async function readGhjkfile( hcx: HostCtx, configPath: Path, - envVars: Record, ) { switch (configPath.extname()) { case "": @@ -373,9 +376,9 @@ async function readAndSerializeConfig( logger().debug("serializing ts config", configPath); const res = await denoFile.getSerializedConfig( configPath.toFileUrl().href, - envVars, + hcx.curEnvVars, ); - const envVarHashes = await envVarDigests(envVars, res.accessedEnvKeys); + const envVarHashes = envVarDigests(hcx.curEnvVars, res.accessedEnvKeys); const cwd = $.path(Deno.cwd()); const cwdStr = cwd.toString(); const listedFiles = res.listedFiles @@ -456,7 +459,7 @@ async function readLockFile(lockFilePath: Path) { const hashObjValidator = zod.object({ version: zod.string(), - ghjkfileHash: zod.string(), + ghjkfileHash: zod.string().nullish(), envVarHashes: zod.record(zod.string(), zod.string().nullish()), readFileHashes: zod.record(zod.string(), zod.string().nullish()), listedFiles: zod.string().array(), @@ -488,7 +491,7 @@ async function readHashFile(hashFilePath: Path) { } } -async function envVarDigests(all: Record, accessed: string[]) { +function envVarDigests(all: Record, accessed: string[]) { const hashes = {} as DigestsMap; for (const key of accessed) { const val = all[key]; @@ -496,7 +499,7 @@ async function envVarDigests(all: Record, accessed: string[]) { // use null if the serializer accessed hashes[key] = null; } else { - hashes[key] = await stringHashHex(val); + hashes[key] = stringHash(val); } } return hashes; @@ -505,21 +508,21 @@ async function envVarDigests(all: Record, accessed: string[]) { async function fileDigests(hcx: HostCtx, readFiles: string[], cwd: Path) { const cwdStr = cwd.toString(); const readFileHashes = {} as DigestsMap; - await Promise.all(readFiles.map(async (path) => { - const pathRef = cwd.resolve(path); - const relativePath = pathRef + await Promise.all(readFiles.map(async (pathStr) => { + const path = cwd.resolve(pathStr); + const relativePath = path .toString() .replace(cwdStr, "."); // FIXME: stream read into hash to improve mem usage - const stat = await pathRef.lstat(); + const stat = await path.lstat(); if (stat) { const contentHash = (stat.isFile || stat.isSymlink) - ? await fileDigestHex(hcx, pathRef) + ? await fileDigestHex(hcx, path) : null; - readFileHashes[relativePath] = await objectHashHex({ - ...stat, + readFileHashes[relativePath] = objectHash({ + ...JSON.parse(JSON.stringify(stat)), contentHash, - } as jsonHash.Tree); + }); } else { readFileHashes[relativePath] = null; } @@ -540,7 +543,7 @@ function fileDigestHex(hcx: HostCtx, path: Path) { } return promise; async function inner() { - return await bufferHashHex( + return await bufferHashAsync( await path.readBytes(), ); } diff --git a/install.sh b/install.sh index d10d6aca..89de7e54 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,7 @@ set -e -u GHJK_VERSION="${GHJK_VERSION:-v0.1.0-alpha}" GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.ts}" GHJK_SHARE_DIR="${GHJK_SHARE_DIR:-$HOME/.local/share/ghjk}" -DENO_VERSION="${DENO_VERSION:-v1.42.1}" +DENO_VERSION="${DENO_VERSION:-v1.43.1}" # make sure the version is prepended with v if [ "${DENO_VERSION#"v"}" = "$DENO_VERSION" ]; then diff --git a/install/ghjk.sh b/install/ghjk.sh index 4ea0977c..f711d3ac 100644 --- a/install/ghjk.sh +++ b/install/ghjk.sh @@ -2,9 +2,13 @@ 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 +GHJK_MAIN_URL="${GHJK_MAIN_URL:-__MAIN_TS_URL__}" -# NOTE: avoid putting too much in here as the ghjk bin is meant -# to be optional. +# NOTE: avoid putting too much in here as this is only one +# method of getting the ghjk bin which is all utlimately optional +# anyways. + +# NOTE: keep this in sync with impls in install/exec.ts # if ghjkfile var is set, set the GHJK_DIR overriding # any set by the user @@ -39,4 +43,4 @@ fi # we don't want to quote $lock_flag as it's not exactly a single # string param to deno # shellcheck disable=SC2086 -exec __DENO_EXEC__ run --unstable-kv --unstable-worker-options -A $lock_flag __MAIN_TS_URL__ "$@" +exec __DENO_EXEC__ run __UNSTABLE_FLAGS__ -A $lock_flag $GHJK_MAIN_URL "$@" diff --git a/install/mod.ts b/install/mod.ts index 219b497d..7af7f4fb 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -1,10 +1,26 @@ //! this installs the different shell ghjk hooks in ~/.local/share/ghjk //! and a `ghjk` bin at ~/.local/share/bin -import logger from "../utils/logger.ts"; -import { std_fs, std_path } from "../deps/cli.ts"; +// TODO: explore installing deno.lock from ghjk repo and +// relying on --frozen-lockfile + +import getLogger from "../utils/logger.ts"; import { $, dirs, importRaw } from "../utils/mod.ts"; +import type { Path } from "../utils/mod.ts"; + +const logger = getLogger(import.meta); +/** + * Deno unstable flags needed for ghjk host. + */ +export const unstableFlags = [ + "--unstable-kv", + "--unstable-worker-options", +]; + +// TODO: calculate and add integrity hashes to these raw imports +// as they won't be covered by deno.lock +// - use pre-commit-hook plus ghjk tasks to do find+replace // null means it should be removed (for cleaning up old versions) const getHooksVfs = async () => ({ "env.sh": ( @@ -35,38 +51,39 @@ const getHooksVfs = async () => ({ async function unpackVFS( vfs: Record, - baseDir: string, + baseDirRaw: Path, replacements: [RegExp, string][], ): Promise { - await $.path(baseDir).ensureDir(); + const baseDir = await $.path(baseDirRaw).ensureDir(); for (const [subpath, content] of Object.entries(vfs)) { - const path = std_path.resolve(baseDir, subpath); + const path = baseDir.join(subpath); if (content === null) { - await $.path(baseDir).remove({ recursive: true }); + await path.remove({ recursive: true }); } else { let text = content.trim(); for (const [re, repl] of replacements) { text = text.replace(re, repl); } - await $.path(std_path.dirname(path)).ensureDir(); - await $.path(path).writeText(text); + await path.parentOrThrow().ensureDir(); + await path.writeText(text); } } } async function filterAddContent( - path: string, + path: Path, marker: RegExp, content: string | null, ) { - const file = await Deno.readTextFile(path).catch(async (err) => { - if (err instanceof Deno.errors.NotFound) { - await Deno.mkdir(std_path.dirname(path), { recursive: true }); - return ""; - } - throw err; - }); + const file = await path.readText() + .catch(async (err) => { + if (err instanceof Deno.errors.NotFound) { + await $.path(path).parentOrThrow().ensureDir(); + return ""; + } + throw err; + }); const lines = file.split("\n"); let i = 0; @@ -82,7 +99,7 @@ async function filterAddContent( lines.push(content); } - await Deno.writeTextFile(path, lines.join("\n")); + await path.writeText(lines.join("\n")); } interface InstallArgs { @@ -117,17 +134,15 @@ interface InstallArgs { noLockfile: boolean; } -/** - * @field: - */ export const defaultInstallArgs: InstallArgs = { - ghjkShareDir: std_path.resolve(dirs().shareDir, "ghjk"), + ghjkShareDir: $.path(dirs().shareDir).resolve("ghjk").toString(), homeDir: dirs().homeDir, shellsToHook: [], shellHookMarker: "ghjk-hook-default", skipExecInstall: true, // TODO: respect xdg dirs - ghjkExecInstallDir: std_path.resolve(dirs().homeDir, ".local", "bin"), + ghjkExecInstallDir: $.path(dirs().homeDir).resolve(".local", "bin") + .toString(), ghjkExecDenoExec: Deno.execPath(), /** * the default behvaior kicks in with ghjkDenoCacheDir is falsy @@ -145,21 +160,19 @@ const shellConfig: Record = { export async function install( args: InstallArgs = defaultInstallArgs, ) { - logger().debug("installing", args); + logger.debug("installing", args); if (Deno.build.os == "windows") { throw new Error("windows is not yet supported, please use wsl"); } - const ghjkShareDir = std_path.resolve( - Deno.cwd(), - std_path.normalize(args.ghjkShareDir), - ); + const ghjkShareDir = $.path(Deno.cwd()) + .resolve(args.ghjkShareDir); - logger().debug("unpacking vfs", { ghjkShareDir }); + logger.debug("unpacking vfs", { ghjkShareDir }); await unpackVFS( await getHooksVfs(), ghjkShareDir, - [[/__GHJK_SHARE_DIR__/g, ghjkShareDir]], + [[/__GHJK_SHARE_DIR__/g, ghjkShareDir.toString()]], ); for (const shell of args.shellsToHook) { @@ -169,8 +182,8 @@ export async function install( throw new Error(`unsupported shell: ${shell}`); } - const rcPath = std_path.resolve(homeDir, shellConfig[shell]); - logger().debug("installing hook", { + const rcPath = $.path(homeDir).join(shellConfig[shell]); + logger.debug("installing hook", { ghjkShareDir, shell, marker: args.shellHookMarker, @@ -190,28 +203,32 @@ export async function install( case "solaris": case "illumos": case "darwin": { - await std_fs.ensureDir(args.ghjkExecInstallDir); - const exePath = std_path.resolve(args.ghjkExecInstallDir, `ghjk`); - logger().debug("installing executable", { exePath }); + const installDir = await $.path(args.ghjkExecInstallDir).ensureDir(); + const exePath = installDir.resolve(`ghjk`); + logger.debug("installing executable", { exePath }); // use an isolated cache by default - const denoCacheDir = args.ghjkDenoCacheDir ?? - std_path.resolve(ghjkShareDir, "deno"); - await Deno.writeTextFile( - exePath, + const denoCacheDir = args.ghjkDenoCacheDir + ? $.path(args.ghjkDenoCacheDir) + : ghjkShareDir.resolve("deno"); + await exePath.writeText( (await importRaw(import.meta.resolve("./ghjk.sh"))) .replaceAll( "__GHJK_SHARE_DIR__", - ghjkShareDir, + ghjkShareDir.toString(), ) .replaceAll( "__DENO_CACHE_DIR", - denoCacheDir, + denoCacheDir.toString(), ) .replaceAll( "__DENO_EXEC__", args.ghjkExecDenoExec, ) + .replaceAll( + "__UNSTABLE_FLAGS__", + unstableFlags.join(" "), + ) .replaceAll( "__MAIN_TS_URL__", import.meta.resolve("../main.ts"), @@ -224,5 +241,5 @@ export async function install( throw new Error(`${Deno.build.os} is not yet supported`); } } - logger().info("install success"); + logger.info("install success"); } diff --git a/install/utils.ts b/install/utils.ts new file mode 100644 index 00000000..1f5bb9b5 --- /dev/null +++ b/install/utils.ts @@ -0,0 +1,44 @@ +//! Please keep these in sync with `./ghjk.ts` + +import type { GhjkCtx } from "../modules/types.ts"; +import { unstableFlags } from "./mod.ts"; + +/** + * Returns a simple posix function to invoke the ghjk CLI. + */ +export function ghjk_sh( + gcx: GhjkCtx, + denoDir: string, + functionName = "__ghjk_shim", +) { + return `${functionName} () { + GHJK_SHARE_DIR="${gcx.ghjkShareDir}" \\ + DENO_DIR="${denoDir}" \\ + DENO_NO_UPDATE_CHECK=1 \\ + GHJK_DIR="${gcx.ghjkDir}" \\ + ${Deno.execPath()} run ${ + unstableFlags.join(" ") + } -A --lock ${gcx.ghjkDir}/deno.lock ${import.meta.resolve("../main.ts")} "$@" +}`; +} + +/** + * Returns a simple fish function to invoke the ghjk CLI. + */ +export function ghjk_fish( + gcx: GhjkCtx, + denoDir: string, + functionName = "__ghjk_shim", +) { + return `function ${functionName} + GHJK_SHARE_DIR="${gcx.ghjkShareDir}" \\ + DENO_DIR="${denoDir}" \\ + DENO_NO_UPDATE_CHECK=1 \\ + GHJK_DIR="${gcx.ghjkDir}" \\ + ${Deno.execPath()} run ${ + unstableFlags.join(" ") + } -A --lock ${gcx.ghjkDir}/deno.lock ${ + import.meta.resolve("../main.ts") + } $argv +end`; +} diff --git a/main.ts b/main.ts index 81571da7..42aed1fd 100755 --- a/main.ts +++ b/main.ts @@ -4,21 +4,29 @@ import "./setup_logger.ts"; import { cli } from "./host/mod.ts"; import { std_path } from "./deps/common.ts"; import logger from "./utils/logger.ts"; -import { dirs, findConfig } from "./utils/mod.ts"; +import { dirs, findEntryRecursive } from "./utils/mod.ts"; if (import.meta.main) { - const ghjkfile = Deno.env.get("GHJKFILE") ?? - await findConfig(Deno.cwd()); - if (!ghjkfile) { + // look for ghjkdir + let ghjkdir = Deno.env.get("GHJK_DIR") ?? + await findEntryRecursive(Deno.cwd(), ".ghjk"); + const ghjkfile = ghjkdir + ? await findEntryRecursive(std_path.dirname(ghjkdir), "ghjk.ts") + : await findEntryRecursive(Deno.cwd(), "ghjk.ts"); + if (!ghjkdir && !ghjkfile) { logger().warn( - "ghjk could not find any ghjkfiles, try creating a `ghjk.ts` script.", + "ghjk could not find any ghjkfiles or ghjkdirs, try creating a `ghjk.ts` script.", ); // Deno.exit(2); } + if (ghjkfile && !ghjkdir) { + ghjkdir = std_path.resolve(std_path.dirname(ghjkfile), ".ghjk"); + } await cli({ ghjkShareDir: Deno.env.get("GHJK_SHARE_DIR") ?? - std_path.resolve(dirs().shareDir, "ghjk"), + dirs().shareDir.resolve("ghjk").toString(), ghjkfilePath: ghjkfile ? std_path.resolve(Deno.cwd(), ghjkfile) : undefined, + ghjkDirPath: ghjkdir ? std_path.resolve(Deno.cwd(), ghjkdir) : undefined, }); } else { throw new Error( diff --git a/mod.ts b/mod.ts index 65fd9bff..4bb39ad0 100644 --- a/mod.ts +++ b/mod.ts @@ -5,27 +5,24 @@ import "./setup_logger.ts"; +import { zod } from "./deps/common.ts"; // ports specific imports +import portsValidators from "./modules/ports/types.ts"; import type { + AllowedPortDep, InstallConfigFat, - PortsModuleSecureConfig, } from "./modules/ports/types.ts"; import logger from "./utils/logger.ts"; -import { $ } from "./utils/mod.ts"; -import { - EnvBuilder, - GhjkfileBuilder, - stdDeps, - stdSecureConfig, -} from "./ghjkfiles/mod.ts"; -import type { EnvDefArgs, TaskDefArgs, TaskFn } from "./ghjkfiles/mod.ts"; +import { $, thinInstallConfig } from "./utils/mod.ts"; +import { EnvBuilder, Ghjkfile, stdDeps } from "./files/mod.ts"; +import type { DenoTaskDefArgs, EnvDefArgs, TaskFn } from "./files/mod.ts"; // WARN: this module has side-effects and only ever import // types from it import type { ExecTaskArgs } from "./modules/tasks/deno.ts"; const DEFAULT_BASE_ENV_NAME = "main"; -const file = new GhjkfileBuilder(); +const file = new Ghjkfile(); const mainEnv = file.addEnv({ name: DEFAULT_BASE_ENV_NAME, base: false, @@ -33,21 +30,32 @@ const mainEnv = file.addEnv({ desc: "the default default environment.", }); -export type { EnvDefArgs, TaskDefArgs, TaskFn } from "./ghjkfiles/mod.ts"; +export type { DenoTaskDefArgs, EnvDefArgs, TaskFn } from "./files/mod.ts"; export { $, logger, stdDeps, stdSecureConfig }; // FIXME: ses.lockdown to freeze primoridials // freeze the object to prevent malicious tampering of the secureConfig export const ghjk = Object.freeze({ getConfig: Object.freeze( - (secureConfig: PortsModuleSecureConfig | undefined) => { + ( + ghjkfileUrl: string, + secureConfig: DenoFileSecureConfig | undefined, + ) => { const defaultEnv = secureConfig?.defaultEnv ?? DEFAULT_BASE_ENV_NAME; const defaultBaseEnv = secureConfig?.defaultBaseEnv ?? DEFAULT_BASE_ENV_NAME; - return file.toConfig({ defaultEnv, defaultBaseEnv, secureConfig }); + return file.toConfig({ + defaultEnv, + defaultBaseEnv, + ghjkfileUrl, + masterPortDepAllowList: secureConfig?.masterPortDepAllowList ?? + stdDeps(), + }); }, ), execTask: Object.freeze( + // TODO: do we need to source the default base env from + // the secure config here? (args: ExecTaskArgs) => file.execTask(args), ), }); @@ -59,27 +67,44 @@ export function install(...configs: InstallConfigFat[]) { mainEnv.install(...configs); } -export function task(args: TaskDefArgs): string; -export function task(name: string, args: Omit): string; -export function task(name: string, fn: TaskFn): string; +/** + * Define and register a task. + */ +export function task(args: DenoTaskDefArgs): string; +export function task(name: string, args: Omit): string; +export function task( + name: string, + fn: TaskFn, + args?: Omit, +): string; +export function task(fn: TaskFn, args?: Omit): string; export function task( - nameOrArgs: string | TaskDefArgs, - argsOrFn?: Omit | TaskFn, + nameOrArgsOrFn: string | DenoTaskDefArgs | TaskFn, + argsOrFn?: Omit | TaskFn, + argsMaybe?: Omit, ): string { - let args: TaskDefArgs; - if (typeof nameOrArgs == "object") { - args = nameOrArgs; + 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: nameOrArgs }; + args = { ...argsOrFn, name: nameOrArgsOrFn }; } else if (argsOrFn) { args = { - name: nameOrArgs, + ...(argsMaybe ?? {}), + name: nameOrArgsOrFn, fn: argsOrFn, }; } else { - throw new Error("no function provided when defining task"); + args = { + name: nameOrArgsOrFn, + }; } - return file.addTask(args); + return file.addTask({ ...args, ty: "denoFile@v1" }); } export function env(args: EnvDefArgs): EnvBuilder; @@ -93,3 +118,48 @@ export function env( : { ...argsMaybe, name: nameOrArgs }; return file.addEnv(args); } + +const denoFileSecureConfig = zod.object({ + masterPortDepAllowList: zod.array(portsValidators.allowedPortDep).nullish(), + // TODO: move into envs/types + defaultEnv: zod.string().nullish(), + defaultBaseEnv: zod.string().nullish(), +}); +/* + * This is a secure sections of the config intended to be direct exports + * from the config script instead of the global variable approach the + * main [`GhjkConfig`] can take. + */ +export type DenoFileSecureConfig = zod.input< + typeof denoFileSecureConfig +>; +export type DenoFileSecureConfigX = zod.input< + typeof denoFileSecureConfig +>; + +function stdSecureConfig( + args: { + additionalAllowedPorts?: (InstallConfigFat | AllowedPortDep)[]; + enableRuntimes?: boolean; + } & Pick, +) { + const { additionalAllowedPorts, enableRuntimes = false } = args; + const out: DenoFileSecureConfig = { + ...args, + masterPortDepAllowList: [ + ...stdDeps({ enableRuntimes }), + ...additionalAllowedPorts?.map( + (dep: any) => { + const res = portsValidators.allowedPortDep.safeParse(dep); + if (res.success) return res.data; + const out: AllowedPortDep = { + manifest: dep.port, + defaultInst: thinInstallConfig(dep), + }; + return portsValidators.allowedPortDep.parse(out); + }, + ) ?? [], + ], + }; + return out; +} diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 0ecc97c8..b5c96899 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -17,7 +17,6 @@ import type { InstallSetProvision, InstallSetRefProvision, } from "../ports/types.ts"; -import { isColorfulTty } from "../../utils/logger.ts"; import { buildInstallGraph, syncCtxFromGhjk } from "../ports/sync.ts"; export type EnvsCtx = { @@ -48,8 +47,8 @@ export class EnvsModule extends ModuleBase { const config = unwrapParseCurry( validators.envsModuleConfig.safeParse(manifest.config), ); - - const activeEnv = Deno.env.get("GHJK_ENV") ?? config.defaultEnv; + const setEnv = Deno.env.get("GHJK_ENV"); + const activeEnv = setEnv && setEnv != "" ? setEnv : config.defaultEnv; return Promise.resolve({ activeEnv, @@ -139,18 +138,13 @@ export class EnvsModule extends ModuleBase { throw new Error(`No env found under given name "${envName}"`); } // deno-lint-ignore no-console - console.log(Deno.inspect( - await showableEnv(gcx, env, envName), - { - depth: 10, - colors: isColorfulTty(), - }, - )); + console.log($.inspect(await showableEnv(gcx, env, envName))); }), ), sync: new cliffy_cmd.Command() - .description(`Cooks and activates an environment. + .description(`Synchronize your shell to what's in your config. +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]") @@ -250,7 +244,7 @@ async function reduceAndCookEnv( if (envName == ecx.config.defaultEnv) { const defaultEnvDir = $.path(gcx.ghjkDir).join("envs", "default"); await $.removeIfExists(defaultEnvDir); - await defaultEnvDir.createSymlinkTo(envDir, { kind: "relative" }); + await defaultEnvDir.symlinkTo(envDir, { kind: "relative" }); } } diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 14d9a250..833fe091 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -1,9 +1,10 @@ import { std_fs, std_path } from "../../deps/cli.ts"; import type { EnvRecipeX } from "./types.ts"; -import getLogger from "../../utils/logger.ts"; import { $, Path } from "../../utils/mod.ts"; import type { GhjkCtx } from "../types.ts"; import { reduceStrangeProvisions } from "./reducer.ts"; +import { ghjk_fish, ghjk_sh } from "../../install/utils.ts"; +import getLogger from "../../utils/logger.ts"; const logger = getLogger(import.meta); @@ -16,6 +17,7 @@ export async function cookPosixEnv( createShellLoaders?: boolean; }, ) { + logger.debug("cooking env", envName, { envDir }); const reducedRecipe = await reduceStrangeProvisions(gcx, recipe); await $.removeIfExists(envDir); // create the shims for the user's environment @@ -35,6 +37,8 @@ export async function cookPosixEnv( const vars = { GHJK_ENV: envName, } as Record; + const onEnterHooks = [] as [string, string[]][]; + const onExitHooks = [] as [string, string[]][]; // FIXME: detect shim conflicts // FIXME: better support for multi installs @@ -59,6 +63,12 @@ export async function cookPosixEnv( } vars[item.key] = item.val; break; + case "hook.onEnter.posixExec": + onEnterHooks.push([item.program, item.arguments]); + break; + case "hook.onExit.posixExec": + onExitHooks.push([item.program, item.arguments]); + break; default: throw Error(`unsupported provision type: ${(item as any).provision}`); } @@ -81,8 +91,6 @@ export async function cookPosixEnv( ), $.path(envDir).join("recipe.json").writeJsonPretty(reducedRecipe), ]); - // write loader for the env vars mandated by the installs - logger.debug("adding vars to loader", vars); // FIXME: prevent malicious env manipulations let LD_LIBRARY_ENV: string; switch (Deno.build.os) { @@ -103,10 +111,14 @@ export async function cookPosixEnv( CPLUS_INCLUDE_PATH: `${envDir}/shims/include`, }; if (createShellLoaders) { - await writeLoader( + // write loader for the env vars mandated by the installs + await writeActivators( + gcx, envDir, vars, pathVars, + onEnterHooks, + onExitHooks, ); } return { @@ -151,56 +163,104 @@ async function shimLinkPaths( throw error; } } - await shimPath.createSymlinkTo(filePath, { kind: "absolute" }); + await shimPath.symlinkTo(filePath, { kind: "absolute" }); shims[fileName] = shimPath.toString(); } return shims; } -// create the loader scripts -// loader scripts are responsible for exporting -// different environment variables from the ports -// and mainpulating the path strings -async function writeLoader( +/** + * Create the activate scripts. + * + * Activate scripts are responsible for: + * - exporting different environment variables from the ports + * - mainpulating the path strings + * - running the environment hooks + */ +async function writeActivators( + gcx: GhjkCtx, envDir: string, env: Record, pathVars: Record, + 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 ghjkShimName = "__ghjk_shim"; + 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 activate = { + // + // posix shell version posix: [ `if [ -n "$\{GHJK_CLEANUP_POSIX+x}" ]; then eval "$GHJK_CLEANUP_POSIX" fi`, `export GHJK_CLEANUP_POSIX="";`, - ...Object.entries(env).map(([k, v]) => + "\n# env vars", + ...Object.entries(env).map(([key, val]) => // NOTE: single quote the port supplied envs to avoid any embedded expansion/execution - `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX"export ${k}='$${k}';"; -export ${k}='${v}';` + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX"export ${key}='$${key}';"; +export ${key}='${val}';` ), - ...Object.entries(pathVars).map(([k, v]) => + "\n# path vars", + ...Object.entries(pathVars).map(([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'${k}=$(echo "$${k}" | tr ":" "\\n" | grep -vE "^${envDir}" | tr "\\n" ":");${k}="\${${k}%:}";'; -export ${k}="${v}:$${k}"; + `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_sh(gcx, denoDir, ghjkShimName), + "\n# on enter hooks", + ...onEnterHooksEscaped, + "\n# on exit hooks", + ...onExitHooksEscaped.map( + (command) => `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'${command};';`, + ), ].join("\n"), + // + // fish version fish: [ `if set --query GHJK_CLEANUP_FISH eval $GHJK_CLEANUP_FISH set --erase GHJK_CLEANUP_FISH end`, - ...Object.entries(env).map(([k, v]) => - `set --global --append GHJK_CLEANUP_FISH "set --global --export ${k} '$${k}';"; -set --global --export ${k} '${v}';` + "\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}';` ), - ...Object.entries(pathVars).map(([k, v]) => - `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${k} (string match --invert --regex "^${envDir}" $${k});'; -set --global --export --prepend ${k} ${v}; + "\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", + ghjk_fish(gcx, denoDir, ghjkShimName), + "\n# on enter hooks", + ...onEnterHooksEscaped, + "\n# on exit hooks", + ...onExitHooksEscaped.map( + (command) => `set --global --append GHJK_CLEANUP_FISH '${command};';`, + ), ].join("\n"), }; + const envPathR = await $.path(envDir).ensureDir(); await Promise.all([ envPathR.join(`activate.fish`).writeText(activate.fish), diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts index 64fe228d..864e9ef1 100644 --- a/modules/envs/reducer.ts +++ b/modules/envs/reducer.ts @@ -31,6 +31,11 @@ export function getProvisionReducerStore( return store; } +/** + * Looks at each provision in the recipe and if it's not a type of + * {@link WellKnownProvision}, looks for reducers in + * {@link ProvisionReducer} to convert it to one. + */ export async function reduceStrangeProvisions( gcx: GhjkCtx, env: EnvRecipeX, diff --git a/modules/envs/types.ts b/modules/envs/types.ts index 5850483d..90194fd0 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -10,11 +10,17 @@ const posixFileProvisionTypes = [ "posix.headerFile", ] as const; +export const hookProvisionTypes = [ + "hook.onEnter.posixExec", + "hook.onExit.posixExec", +] as const; + // we separate the posix file types in a separate // array in the interest of type inference export const wellKnownProvisionTypes = [ "posix.envVar", ...posixFileProvisionTypes, + ...hookProvisionTypes, ] as const; const wellKnownProvision = zod.discriminatedUnion( @@ -25,6 +31,13 @@ const wellKnownProvision = zod.discriminatedUnion( key: zod.string(), val: zod.string(), }), + ...hookProvisionTypes.map((ty) => + zod.object({ + ty: zod.literal(ty), + program: zod.string(), + arguments: zod.string().array(), + }) + ), ...posixFileProvisionTypes.map((ty) => zod.object({ ty: zod.literal(ty), absolutePath }) ), diff --git a/modules/ports/db.ts b/modules/ports/db.ts index 54934e68..1a9678a5 100644 --- a/modules/ports/db.ts +++ b/modules/ports/db.ts @@ -2,12 +2,16 @@ /// import { zod } from "../../deps/common.ts"; +// import type { PathRef } from "../../utils/mod.ts"; +// import { $ } from "../../utils/mod.ts"; import validators from "./types.ts"; +// import getLogger from "../../utils/logger.ts"; // const logger = getLogger(import.meta); // NOTE: make sure any changes to here are backwards compatible const installRowValidator = zod.object({ + // version: zod.string(), installId: zod.string(), conf: validators.installConfigLite, manifest: validators.portManifest, @@ -16,7 +20,10 @@ const installRowValidator = zod.object({ progress: zod.enum(["downloaded", "installed"]), }).passthrough(); -export type InstallRow = zod.infer; +type InstallRowVersioned = zod.infer; +// FIXME: this breaks typescript +// export type InstallRow = Omit; +export type InstallRow = InstallRowVersioned; export abstract class InstallsDb { abstract all(): Promise; @@ -63,10 +70,10 @@ class DenoKvInstallsDb extends InstallsDb { } } -// TODO: implement me -/* +/* // TODO: implement me + class InlineInstallsDb extends InstallsDb { - #map = new Map(); + #map = new Map(); #dbDir: PathRef; constructor( dbDir: string, @@ -80,17 +87,26 @@ class InlineInstallsDb extends InstallsDb { async get(id: string): Promise { let row = this.#map.get(id); if (!row) { - const res = installRowValidator.safeParse( - await this.#dbDir.join(`${id}.meta`).readMaybeJson(), - ); - if (!res.success) { - logger.warn() + const raw = await this.#dbDir.join(`${id}.meta`).readMaybeText(); + if (raw) { + try { + const rawParsed = installRowValidator.parse(JSON.parse(raw)); + if (rowParsed.version != "0") { + throw new Error(`unexpected version string: ${rowParsed.version}`); + } + row = rowParsed; + this.#map.set(id, row); + } catch (err) { + logger.warn(`error parsing install meta for "${id}"`, err); + } } } return row; } set(id: string, row: InstallRow): Promise { - this.#map.set(id, row); + const versioned = { ...row, version: "0" }; + await this.#dbDir.join(`${id}.meta`).writeJsonPretty(versioned); + this.#map.set(id, versioned); throw new Error("Method not implemented."); } delete(id: string): Promise { @@ -100,4 +116,4 @@ class InlineInstallsDb extends InstallsDb { [Symbol.dispose](): void { throw new Error("Method not implemented."); } -}*/ +} */ diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 3a7dda6c..a54ad42d 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -1,4 +1,4 @@ -import { deep_eql, jsonHash, std_fs, std_path, zod } from "../../deps/cli.ts"; +import { deep_eql, std_fs, std_path, zod } from "../../deps/cli.ts"; import getLogger from "../../utils/logger.ts"; import validators from "./types.ts"; import type { @@ -22,7 +22,7 @@ import { DePromisify, getInstallHash, getPortRef, - objectHashHex, + objectHash, type Rc, rc, sameFsTmpRoot, @@ -119,9 +119,9 @@ export async function installFromGraph( dir: tmpPath, prefix: `shims_${installId}_`, }); - for ( - const [depInstallId, depPortName] of graph.depEdges[installId] ?? [] - ) { + await Promise.all((graph.depEdges[installId] ?? []).map(async ( + [depInstallId, depPortName], + ) => { const depArts = installCtx.artifacts.get(depInstallId); if (!depArts) { throw new Error( @@ -153,7 +153,7 @@ export async function installFromGraph( ), env: depArts.env, }; - } + })); return { totalDepArts, depShimsRootPath }; }, @@ -341,7 +341,7 @@ export async function buildInstallGraph( manifest, instLite, ); - const instId = await getInstallHash(resolvedConfig); + const instId = getInstallHash(resolvedConfig); // no dupes allowed in user specified insts if (graph.user.includes(instId)) { @@ -399,7 +399,7 @@ export async function buildInstallGraph( const depInstall = validators.installConfigResolved.parse( inst.config.buildDepConfigs![depId.name], ); - const depInstallId = await getInstallHash(depInstall); + const depInstallId = getInstallHash(depInstall); // only add the install configuration for this dep port // if specific hash hasn't seen before @@ -458,13 +458,13 @@ export async function buildInstallGraph( // This takes user specified InstallConfigs and resolves // their versions to a known, installable version // It also resolves any dependencies that the config specifies -async function resolveConfig( +function resolveConfig( scx: SyncCtx, set: InstallSetX, manifest: PortManifestX, config: InstallConfigLiteX, ) { - const hash = await objectHashHex(config as jsonHash.Tree); + const hash = objectHash(JSON.parse(JSON.stringify(config))); let promise = scx.memoStore.get(hash); if (!promise) { promise = inner(); @@ -620,7 +620,7 @@ async function resolveAndInstall( configLite: InstallConfigLiteX, ) { const config = await resolveConfig(scx, set, manifest, configLite); - const installId = await getInstallHash(config); + const installId = getInstallHash(config); const cached = await scx.db.val.get(installId); // we skip it if it's already installed @@ -717,45 +717,48 @@ async function getShimmedDepArts( installs: [string, string][], ) { const totalDepArts: DepArts = {}; - for ( - const [installId, portName] of installs - ) { - const installRow = await scx.db.val.get(installId); - if (!installRow || !installRow.installArts) { - throw new Error( - `artifacts not found for "${installId}" not found in db when shimming totalDepArts`, - { - cause: { installs }, + await Promise.all( + installs + .map( + async ([installId, portName]) => { + const installRow = await scx.db.val.get(installId); + if (!installRow || !installRow.installArts) { + throw new Error( + `artifacts not found for "${installId}" not found in db when shimming totalDepArts`, + { + cause: { installs }, + }, + ); + } + const installArts = installRow.installArts; + const shimDir = $.path(shimsRootPath).resolve(installId); + const [binShimDir, libShimDir, includeShimDir] = (await Promise.all([ + shimDir.join("bin").ensureDir(), + shimDir.join("lib").ensureDir(), + shimDir.join("include").ensureDir(), + ])).map($.pathToString); + + totalDepArts[portName] = { + execs: await shimLinkPaths( + installArts.binPaths, + installArts.installPath, + binShimDir, + ), + libs: await shimLinkPaths( + installArts.libPaths, + installArts.installPath, + libShimDir, + ), + includes: await shimLinkPaths( + installArts.includePaths, + installArts.installPath, + includeShimDir, + ), + env: installArts.env, + }; }, - ); - } - const installArts = installRow.installArts; - const shimDir = $.path(shimsRootPath).resolve(installId); - const [binShimDir, libShimDir, includeShimDir] = (await Promise.all([ - shimDir.join("bin").ensureDir(), - shimDir.join("lib").ensureDir(), - shimDir.join("include").ensureDir(), - ])).map($.pathToString); - - totalDepArts[portName] = { - execs: await shimLinkPaths( - installArts.binPaths, - installArts.installPath, - binShimDir, ), - libs: await shimLinkPaths( - installArts.libPaths, - installArts.installPath, - libShimDir, - ), - includes: await shimLinkPaths( - installArts.includePaths, - installArts.installPath, - includeShimDir, - ), - env: installArts.env, - }; - } + ); return totalDepArts; } @@ -797,7 +800,7 @@ async function shimLinkPaths( throw error; } } - await $.path(shimPath).createSymlinkTo(filePath, { type: "file" }); + await $.path(shimPath).symlinkTo(filePath, { type: "file" }); shims[fileName] = shimPath; } return shims; diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 69f2c0dc..368cae6a 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -144,12 +144,6 @@ const allowedPortDep = zod.object({ defaultInst: installConfigLite, }); -const portsModuleSecureConfig = zod.object({ - masterPortDepAllowList: zod.array(allowedPortDep).nullish(), - defaultEnv: zod.string().nullish(), - defaultBaseEnv: zod.string().nullish(), -}); - const allowDepSet = zod.record(zod.string(), allowedPortDep); const allowDepSetHashed = zod.record(zod.string(), zod.string()); @@ -219,7 +213,6 @@ const validators = { installConfig, installConfigResolved, portManifest, - portsModuleSecureConfig, portsModuleConfig, portsModuleConfigHashed, allowedPortDep, @@ -342,18 +335,6 @@ export type InstallSetRefProvisionX = zod.infer< export type AllowedPortDep = zod.input; export type AllowedPortDepX = zod.infer; -/* - * This is a secure sections of the config intended to be direct exports - * from the config script instead of the global variable approach the - * main [`GhjkConfig`] can take. - */ -export type PortsModuleSecureConfig = zod.input< - typeof validators.portsModuleSecureConfig ->; -export type PortsModuleSecureConfigX = zod.input< - typeof validators.portsModuleSecureConfig ->; - export type InstallSet = zod.input; export type InstallSetX = zod.infer< typeof validators.installSet diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index 8a828804..e3c0003d 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -31,7 +31,7 @@ export type DriverResponse = { }; export type ExecTaskArgs = { - name: string; + key: string; argv: string[]; workingDir: string; envVars: Record; @@ -106,12 +106,12 @@ async function rpc(moduleUri: string, req: DriverRequests) { } export async function execTaskDeno( - configUri: string, + moduleUri: string, args: ExecTaskArgs, ) { - const resp = await rpc(configUri, { + const resp = await rpc(moduleUri, { ty: "exec", - uri: configUri, + uri: moduleUri, args, }); if (resp.ty != "exec") { diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index 5f7ada62..17374495 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -1,4 +1,3 @@ -import { std_path } from "../../deps/cli.ts"; import { $, DePromisify } from "../../utils/mod.ts"; import type { TaskDefHashedX, TasksModuleConfigX } from "./types.ts"; @@ -14,7 +13,7 @@ export type TaskGraph = DePromisify>; export function buildTaskGraph( _gcx: GhjkCtx, - portsConfig: TasksModuleConfigX, + tasksConfig: TasksModuleConfigX, // env: Blackboard, ) { const graph = { @@ -24,25 +23,25 @@ export function buildTaskGraph( // edges from dependent to dependency depEdges: {} as Record, }; - for (const [name, task] of Object.entries(portsConfig.tasks)) { - if (!portsConfig.envs[task.envHash]) { + for (const [hash, task] of Object.entries(tasksConfig.tasks)) { + if (!tasksConfig.envs[task.envHash]) { throw new Error( - `unable to find env referenced by task "${name}" under hash "${task.envHash}"`, + `unable to find env referenced by task "${hash}" under hash "${task.envHash}"`, ); } if (!task.dependsOn || task.dependsOn.length == 0) { - graph.indie.push(name); + graph.indie.push(hash); } else { - for (const depTaskName of task.dependsOn) { + for (const depTaskHash of task.dependsOn) { const testCycle = ( name: string, - depName: string, + depHash: string, ): TaskDefHashedX | undefined => { - const depTask = portsConfig.tasks[depName]; + const depTask = tasksConfig.tasks[depHash]; if (!depTask) { throw new Error(`specified dependency task doesn't exist`, { cause: { - depTaskName, + depHash, task, }, }); @@ -55,7 +54,7 @@ export function buildTaskGraph( } }; - const cycleSource = testCycle(name, depTaskName); + const cycleSource = testCycle(hash, depTaskHash); if ( cycleSource ) { @@ -69,12 +68,14 @@ export function buildTaskGraph( }, ); } - graph.revDepEdges[depTaskName] = [ - ...graph.revDepEdges[depTaskName] ?? [], - name, - ]; + const revDepSet = graph.revDepEdges[depTaskHash]; + if (revDepSet) { + revDepSet.push(hash); + } else { + graph.revDepEdges[depTaskHash] = [hash]; + } } - graph.depEdges[name] = task.dependsOn; + graph.depEdges[hash] = task.dependsOn; } } return graph; @@ -84,17 +85,17 @@ export async function execTask( gcx: GhjkCtx, tasksConfig: TasksModuleConfigX, taskGraph: TaskGraph, - targetName: string, + targetKey: string, args: string[], // taskEnv: TaskEnvX, // installGraph: InstallGraph, ): Promise { - let workSet = new Set([targetName]); + let workSet = new Set([targetKey]); { - const stack = [targetName]; + const stack = [targetKey]; while (stack.length > 0) { - const taskName = stack.pop()!; - const taskDef = tasksConfig.tasks[taskName]; + const taskHash = stack.pop()!; + const taskDef = tasksConfig.tasks[taskHash]; stack.push(...taskDef.dependsOn ?? []); workSet = new Set([...workSet.keys(), ...taskDef.dependsOn ?? []]); } @@ -102,58 +103,80 @@ export async function execTask( const pendingDepEdges = new Map( Object.entries(taskGraph.depEdges).map(([key, val]) => [key, val!]), ); - const pendingTasks = taskGraph.indie.filter((name) => workSet.has(name)); + const pendingTasks = taskGraph.indie.filter((hash) => workSet.has(hash)); if (pendingTasks.length == 0) { throw new Error("something went wrong, task graph starting set is empty"); } while (pendingTasks.length > 0) { - const taskName = pendingTasks.pop()!; - const taskDef = tasksConfig.tasks[taskName]; + const taskKey = pendingTasks.pop()!; + const taskDef = tasksConfig.tasks[taskKey]; const taskEnvDir = await Deno.makeTempDir({ - prefix: `ghjkTaskEnv_${taskName}_`, + prefix: `ghjkTaskEnv_${taskKey}_`, }); const { env: installEnvs } = await cookPosixEnv( { gcx, recipe: tasksConfig.envs[taskDef.envHash], - envName: `taskEnv_${taskName}`, + envName: `taskEnv_${taskKey}`, envDir: taskEnvDir, }, ); - logger.info("executing", taskName, args); - await execTaskDeno( - std_path.toFileUrl(gcx.ghjkfilePath).href, - { - name: taskName, - argv: args, - envVars: { - ...Deno.env.toObject(), - ...Object.fromEntries( - Object.entries(installEnvs).map( - ( - [key, val], - ) => [ - key, - key.match(/PATH/i) ? `${val}:${Deno.env.get(key) ?? ""}` : val, - ], - ), - ), - }, - workingDir: std_path.dirname(gcx.ghjkfilePath), - }, + logger.info( + "executing", + taskKey, + args, ); + + const envVars = { + ...Deno.env.toObject(), + ...Object.fromEntries( + Object.entries(installEnvs).map( + ( + [key, val], + ) => [ + key, + key.match(/PATH/i) ? `${val}:${Deno.env.get(key) ?? ""}` : val, + ], + ), + ), + }; + if (taskDef.ty == "denoFile@v1") { + if (!gcx.ghjkfilePath) { + throw new Error( + "denoFile task found but no ghjkfile. This occurs when ghjk is working just on a lockfile alone", + ); + } + await execTaskDeno( + $.path(gcx.ghjkfilePath).toFileUrl().toString(), + { + key: taskDef.key, + argv: args, + envVars, + workingDir: gcx.ghjkfilePath.parentOrThrow().toString(), + }, + ); + } else { + throw new Error( + `unsupported task type "${taskDef.ty}"`, + { + cause: { + taskDef, + }, + }, + ); + } $.removeIfExists(taskEnvDir); - workSet.delete(taskName); - const dependentTasks = (taskGraph.revDepEdges[taskName] ?? []) + workSet.delete(taskKey); + const dependentTasks = (taskGraph.revDepEdges[taskKey] ?? []) .filter((name) => workSet.has(name)); const readyTasks = []; for (const parentId of dependentTasks) { const parentDeps = pendingDepEdges.get(parentId)!; // swap remove from parent pending deps list - const idx = parentDeps.indexOf(taskName); + const idx = parentDeps.indexOf(taskKey); const last = parentDeps.pop()!; if (parentDeps.length > idx) { parentDeps[idx] = last; diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index b34ffdda..fd642bd5 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -50,24 +50,28 @@ export class TasksModule extends ModuleBase { gcx: GhjkCtx, tcx: TasksCtx, ) { + const namedSet = new Set(tcx.config.tasksNamed); const commands = Object.entries(tcx.config.tasks).map( - ([name, task]) => { - const cliffyCmd = new cliffy_cmd.Command() - .name(name) - .useRawArgs() + ([key, def]) => { + const cmd = new cliffy_cmd.Command() + .name(key) + .arguments("[argv...]") .action(async (_, ...args) => { await execTask( gcx, tcx.config, tcx.taskGraph, - name, + key, args, ); }); - if (task.desc) { - cliffyCmd.description(task.desc); + if (def.desc) { + cmd.description(def.desc); } - return cliffyCmd; + if (!namedSet.has(key)) { + cmd.hidden(); + } + return cmd; }, ); const root = new cliffy_cmd.Command() diff --git a/modules/tasks/types.ts b/modules/tasks/types.ts index 914e8bc6..eefd0519 100644 --- a/modules/tasks/types.ts +++ b/modules/tasks/types.ts @@ -6,28 +6,65 @@ import envsValidators from "../envs/types.ts"; const taskName = zod.string().regex(/[^\s]/); const taskDefBase = zod.object({ - name: zod.string(), - dependsOn: taskName.array().nullish(), + ty: zod.string(), desc: zod.string().nullish(), workingDir: zod.string().nullish(), + dependsOn: zod.string().array().nullish(), }); -const taskDef = taskDefBase.merge(zod.object({ +const taskDefFullBase = taskDefBase.merge(zod.object({ env: envsValidators.envRecipe, })); -const taskDefHashed = taskDefBase.merge(zod.object({ +const taskDefHashedBase = taskDefBase.merge(zod.object({ envHash: zod.string(), })); +const denoWorkerTaskDefBase = zod.object({ + ty: zod.literal("denoFile@v1"), + /** + * A single module might host multiple tasks so we need keys to identify + * each with. Names aren't enough since some tasks are anonymous. + */ + // This field primarily exists as an optimization actually. + // The tasksModuleConfig keys the tasks by their hash + // but we use a separate key when asking for exec from the denoFile. + // This is because the denoFile only constructs the hashes for the config + // laziliy but uses separate task keys internally due to different hashing concerns. + // This key will correspond to the internal keys used by the denoFile + // and not the config. + key: zod.string(), +}); + +const denoWorkerTaskDef = taskDefFullBase.merge(denoWorkerTaskDefBase); +const denoWorkerTaskDefHashed = taskDefHashedBase.merge(denoWorkerTaskDefBase); + +const taskDef = + // zod.discriminatedUnion("ty", [ + denoWorkerTaskDef; +// ]); + +const taskDefHashed = + // zod.discriminatedUnion("ty", [ + denoWorkerTaskDefHashed; +// ]); + const tasksModuleConfig = zod.object({ envs: zod.record(zod.string(), envsValidators.envRecipe), - tasks: zod.record(taskName, taskDefHashed), + /** + * Tasks can be keyed with any old string. The keys + * that also appear in {@field tasksNamed} will shown + * in the CLI. + */ + tasks: zod.record(zod.string(), taskDefHashed), + tasksNamed: taskName.array(), }); const validators = { taskDef, taskDefHashed, + denoWorkerTaskDefHashed, + denoWorkerTaskDef, tasksModuleConfig, }; export default validators; diff --git a/modules/types.ts b/modules/types.ts index e35b0606..03a6752a 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -1,4 +1,5 @@ import { zod } from "../deps/common.ts"; +import type { Path } from "../utils/mod.ts"; // TODO: better module ident/versioning const moduleId = zod.string().regex(/[^ @]*/); @@ -11,9 +12,9 @@ const moduleManifest = zod.object({ export type ModuleId = zod.infer; export type ModuleManifest = zod.infer; export type GhjkCtx = { - ghjkfilePath: string; - ghjkDir: string; - ghjkShareDir: string; + ghjkfilePath?: Path; + ghjkDir: Path; + ghjkShareDir: Path; blackboard: Map; }; diff --git a/ports/mold.ts b/ports/mold.ts index cc32e3da..3d0f7ce9 100644 --- a/ports/mold.ts +++ b/ports/mold.ts @@ -92,7 +92,7 @@ export class Port extends GithubReleasePort { ); if ((args.config as unknown as MoldInstallConfig).replaceLd) { await installPath.join("bin", "ld") - .createSymlinkTo(installPath.join("bin", "mold").toString(), { + .symlinkTo(installPath.join("bin", "mold").toString(), { kind: "relative", }); } diff --git a/ports/npmi.ts b/ports/npmi.ts index 835f191a..b05ba128 100644 --- a/ports/npmi.ts +++ b/ports/npmi.ts @@ -141,7 +141,7 @@ export class Port extends PortBase { await tmpDirPath.join("bin").ensureDir(); for (const [name] of bins) { await tmpDirPath.join("bin", name) - .createSymlinkTo( + .symlinkTo( installPath .join("node_modules", ".bin", name) .toString(), diff --git a/ports/pipi.ts b/ports/pipi.ts index 1e3c05bc..3079aa09 100644 --- a/ports/pipi.ts +++ b/ports/pipi.ts @@ -146,7 +146,7 @@ export class Port extends PortBase { // the cpy_bs port smuggles out the real path of it's python executable const realPyExecPath = args.depArts[std_ports.cpy_bs_ghrel.name].env.REAL_PYTHON_EXEC_PATH; - (await venvPath.join("bin", "python3").remove()).createSymlinkTo( + (await venvPath.join("bin", "python3").remove()).symlinkTo( realPyExecPath, ); diff --git a/tests/envHooks.ts b/tests/envHooks.ts new file mode 100644 index 00000000..860790cd --- /dev/null +++ b/tests/envHooks.ts @@ -0,0 +1,157 @@ +import "../setup_logger.ts"; +import { E2eTestCase, harness } from "./utils.ts"; + +const posixInteractiveScript = ` +set -eux +export GHJK_WD=$PWD + +# hook creates a marker file +[ $(cat "$GHJK_WD/marker") = 'remark' ] || exit 101 + +pushd ../ +# marker should be gone by now +[ ! -e "$GHJK_WD/marker" ] || exit 102 + +# cd back in +popd + +# marker should be avail now +[ $(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 + +# hook creates a marker file +test (cat "$GHJK_WD/marker") = 'remark'; or exit 101 + +pushd ../ +# marker should be gone by now +not test -e "$GHJK_WD/marker"; or exit 102 + +# cd back in +popd + +# marker should be avail now +test (cat $GHJK_WD/marker) = 'remark'; or exit 103 +`; + +type CustomE2eTestCase = Omit & { + ePoint: string; + stdin: string; +}; +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, + }, + { + name: "zsh_interactive", + ePoint: `zsh -sl`, + stdin: zshInteractiveScript, + }, + { + name: "zsh_scripting", + ePoint: `zsh -s`, + stdin: posixNonInteractiveScript, + }, + { + 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, + }, +]; + +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: ` +export { ghjk } from "$ghjk/mod.ts"; +import { task, env } from "$ghjk/mod.ts"; + +env("main") + .onEnter(task($ => $\`/bin/sh -c 'echo remark > marker'\`)) + .onExit(task($ => $\`/bin/sh -c 'rm marker'\`)) +`, + ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], + name: `envHooks/${testCase.name}`, + timeout_ms: 5 * 60 * 1000, +}))); diff --git a/tests/envs.ts b/tests/envs.ts index f4736b34..9c7882a9 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -1,18 +1,29 @@ import "../setup_logger.ts"; import { - dockerE2eTest, E2eTestCase, type EnvDefArgs, genTsGhjkFile, - localE2eTest, + harness, } from "./utils.ts"; +import { stdSecureConfig } from "../mod.ts"; import dummy from "../ports/dummy.ts"; +import type { DenoFileSecureConfig } from "../mod.ts"; -type CustomE2eTestCase = Omit & { - ePoint: string; - stdin: string; - envs: EnvDefArgs[]; -}; +type CustomE2eTestCase = + & Omit + & { + ePoint: string; + stdin: string; + } + & ( + | { + envs: EnvDefArgs[]; + secureConfig?: DenoFileSecureConfig; + } + | { + ghjkTs: string; + } + ); const envVarTestEnvs: EnvDefArgs[] = [ { @@ -39,6 +50,7 @@ const envVarTestsPosix = ` set -ex # by default, we should be in main [ "$SONG" = "ditto" ] || exit 101 +[ "$GHJK_ENV" = "main" ] || exit 1011 ghjk envs cook sss . .ghjk/envs/sss/activate.sh @@ -46,21 +58,26 @@ ghjk envs cook sss # so they should inherit it's env vars [ "$SONG" = "ditto" ] || exit 102 [ "$SING" = "Seoul Sonyo Sound" ] || exit 103 +[ "$GHJK_ENV" = "sss" ] || exit 1012 # go back to main and "sss" variables shouldn't be around . .ghjk/envs/main/activate.sh [ "$SONG" = "ditto" ] || exit 104 [ "$SING" = "Seoul Sonyo Sound" ] && exit 105 +[ "$GHJK_ENV" = "main" ] || exit 1013 # env base is false for "yuki" and thus no vars from "main" ghjk envs cook yuki . .ghjk/envs/yuki/activate.sh [ "$SONG" = "ditto" ] && exit 102 [ "$HUMM" = "Soul Lady" ] || exit 103 +[ "$GHJK_ENV" = "yuki" ] || exit 1014 `; const envVarTestsFish = ` +set fish_trace 1 # by default, we should be in main test "$SONG" = "ditto"; or exit 101; +test "$GHJK_ENV" = "main"; or exit 1010; ghjk envs cook sss . .ghjk/envs/sss/activate.fish @@ -68,17 +85,20 @@ ghjk envs cook sss # so they should inherit it's env vars test "$SONG" = "ditto"; or exit 103 test "$SING" = "Seoul Sonyo Sound"; or exit 104 +test "$GHJK_ENV" = "sss"; or exit 1011; # go back to main and "sss" variables shouldn't be around . .ghjk/envs/main/activate.fish test $SONG" = "ditto"; or exit 105 test $SING" = "Seoul Sonyo Sound"; and exit 106 +test "$GHJK_ENV" = "main"; or exit 1012; # env base is false for "yuki" and thus no vars from "main" ghjk envs cook yuki . .ghjk/envs/yuki/activate.fish test "$SONG" = "ditto"; and exit 107 test "$HUMM" = "Soul Lady"; or exit 108 +test "$GHJK_ENV" = "yuki"; or exit 1013; `; const installTestEnvs: EnvDefArgs[] = [ @@ -111,6 +131,7 @@ ghjk envs cook foo `; const installTestsFish = ` +set fish_trace 1 # by default, we should be in main test (dummy) = "main"; or exit 101; @@ -159,46 +180,27 @@ const cases: CustomE2eTestCase[] = [ envs: installTestEnvs, stdin: installTestsFish, }, + { + name: "default_env_loader", + ePoint: "fish", + envs: envVarTestEnvs, + secureConfig: stdSecureConfig({ defaultEnv: "yuki" }), + stdin: ` +set fish_trace 1 +# env base is false for "yuki" and thus no vars from "main" +test "$GHJK_ENV" = "yuki"; or exit 106 +test "$SONG" = "ditto"; and exit 107 +test "$HUMM" = "Soul Lady"; or exit 108 +`, + }, ]; -function testMany( - testGroup: string, - cases: CustomE2eTestCase[], - testFn: (inp: E2eTestCase) => Promise, - defaultEnvs: Record = {}, -) { - for (const testCase of cases) { - Deno.test( - `${testGroup} - ${testCase.name}`, - () => - testFn({ - ...testCase, - tsGhjkfileStr: genTsGhjkFile( - { envDefs: testCase.envs }, - ), - ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], - envVars: { - ...defaultEnvs, - ...testCase.envVars, - }, - }), - ); - } -} - -const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); -if (e2eType == "both") { - testMany("envsDockerE2eTest", cases, dockerE2eTest); - testMany(`envsLocalE2eTest`, cases, localE2eTest); -} else if (e2eType == "local") { - testMany("envsLocalE2eTest", cases, localE2eTest); -} else if ( - e2eType == "docker" || - !e2eType -) { - testMany("envsDockerE2eTest", cases, dockerE2eTest); -} else { - throw new Error( - `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, - ); -} +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: "ghjkTs" in testCase ? testCase.ghjkTs : genTsGhjkFile( + { envDefs: testCase.envs, secureConf: testCase.secureConfig }, + ), + ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], + name: `envs/${testCase.name}`, + timeout_ms: 5 * 60 * 1000, +}))); diff --git a/tests/ports.ts b/tests/ports.ts index 40cc5b42..b94f440f 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -1,24 +1,14 @@ import "../setup_logger.ts"; -import { std_async } from "../deps/dev.ts"; -import { stdSecureConfig } from "../mod.ts"; -import { - dockerE2eTest, - E2eTestCase, - genTsGhjkFile, - localE2eTest, -} from "./utils.ts"; +import { DenoFileSecureConfig, stdSecureConfig } from "../mod.ts"; +import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; import * as ports from "../ports/mod.ts"; import dummy from "../ports/dummy.ts"; -import type { - InstallConfigFat, - PortsModuleSecureConfig, -} from "../modules/ports/types.ts"; +import type { InstallConfigFat } from "../modules/ports/types.ts"; type CustomE2eTestCase = Omit & { ePoint: string; installConf: InstallConfigFat | InstallConfigFat[]; - secureConf?: PortsModuleSecureConfig; - ignore?: boolean; + secureConf?: DenoFileSecureConfig; }; // order tests by download size to make failed runs less expensive const cases: CustomE2eTestCase[] = [ @@ -206,33 +196,20 @@ const cases: CustomE2eTestCase[] = [ }, ]; -function testMany( - testGroup: string, - cases: CustomE2eTestCase[], - testFn: (inp: E2eTestCase) => Promise, - defaultEnvs: Record = {}, -) { - for (const testCase of cases) { - Deno.test( - { - name: `${testGroup} - ${testCase.name}`, - ignore: testCase.ignore, - fn: () => - std_async.deadline( - testFn({ - ...testCase, - tsGhjkfileStr: genTsGhjkFile( - { - installConf: testCase.installConf, - secureConf: testCase.secureConf, - taskDefs: [], - }, - ), - ePoints: [ - ...["bash -c", "fish -c", "zsh -c"].map((sh) => ({ - cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], - })), - /* // FIXME: better tests for the `InstallDb` +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: genTsGhjkFile( + { + installConf: testCase.installConf, + secureConf: testCase.secureConf, + taskDefs: [], + }, + ), + ePoints: [ + ...["bash -c", "fish -c", "zsh -c"].map((sh) => ({ + cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], + })), + /* // FIXME: better tests for the `InstallDb` // installs db means this shouldn't take too long // as it's the second sync { @@ -241,35 +218,10 @@ function testMany( "bash -c 'timeout 1 ghjk envs cook'", ], }, */ - ], - envVars: { - ...defaultEnvs, - ...testCase.envVars, - }, - }), - // building the test docker image might taka a while - // but we don't want some bug spinlocking the ci for - // an hour - 5 * 60 * 1000, - ), - }, - ); - } -} - -const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); -if (e2eType == "both") { - testMany("portsDockerE2eTest", cases, dockerE2eTest); - testMany(`portsLocalE2eTest`, cases, localE2eTest); -} else if (e2eType == "local") { - testMany("portsLocalE2eTest", cases, localE2eTest); -} else if ( - e2eType == "docker" || - !e2eType -) { - testMany("portsDockerE2eTest", cases, dockerE2eTest); -} else { - throw new Error( - `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, - ); -} + ], + // building the test docker image might taka a while + // but we don't want some bug spinlocking the ci for + // an hour + timeout_ms: 5 * 60 * 1000, + name: `ports/${testCase.name}`, +}))); diff --git a/tests/hooks.ts b/tests/reloadHooks.ts similarity index 66% rename from tests/hooks.ts rename to tests/reloadHooks.ts index 120153da..68d22036 100644 --- a/tests/hooks.ts +++ b/tests/reloadHooks.ts @@ -1,10 +1,5 @@ import "../setup_logger.ts"; -import { - dockerE2eTest, - E2eTestCase, - genTsGhjkFile, - localE2eTest, -} from "./utils.ts"; +import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; import dummy from "../ports/dummy.ts"; import type { InstallConfigFat } from "../port.ts"; @@ -97,6 +92,7 @@ dummy `; const fishScript = ` +set fish_trace 1 dummy; or exit 101 test $DUMMY_ENV = "dummy"; or exit 102 @@ -123,34 +119,34 @@ type CustomE2eTestCase = Omit & { }; const cases: CustomE2eTestCase[] = [ { - name: "hook_test_bash_interactive", + name: "bash_interactive", // -s: read from stdin // -l: login/interactive mode ePoint: `bash -sl`, stdin: bashInteractiveScript, }, { - name: "hook_test_bash_scripting", + name: "bash_scripting", ePoint: `bash -s`, stdin: posixNonInteractiveScript, }, { - name: "hook_test_zsh_interactive", + name: "zsh_interactive", ePoint: `zsh -sl`, stdin: zshInteractiveScript, }, { - name: "hook_test_zsh_scripting", + name: "zsh_scripting", ePoint: `zsh -s`, stdin: posixNonInteractiveScript, }, { - name: "hook_test_fish_interactive", + name: "fish_interactive", ePoint: `fish -l`, stdin: fishScript, }, { - name: "hook_test_fish_scripting", + name: "fish_scripting", ePoint: `fish`, // the fish implementation triggers changes // on any pwd changes so it's identical to @@ -159,44 +155,12 @@ const cases: CustomE2eTestCase[] = [ }, ]; -function testMany( - testGroup: string, - cases: CustomE2eTestCase[], - testFn: (inp: E2eTestCase) => Promise, - defaultEnvs: Record = {}, -) { - for (const testCase of cases) { - Deno.test( - `${testGroup} - ${testCase.name}`, - () => - testFn({ - ...testCase, - tsGhjkfileStr: genTsGhjkFile( - { installConf: testCase.installConf ?? dummy(), taskDefs: [] }, - ), - ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], - envVars: { - ...defaultEnvs, - ...testCase.envVars, - }, - }), - ); - } -} - -const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); -if (e2eType == "both") { - testMany("hooksDockerE2eTest", cases, dockerE2eTest); - testMany(`hooksLocalE2eTest`, cases, localE2eTest); -} else if (e2eType == "local") { - testMany("hooksLocalE2eTest", cases, localE2eTest); -} else if ( - e2eType == "docker" || - !e2eType -) { - testMany("hooksDockerE2eTest", cases, dockerE2eTest); -} else { - throw new Error( - `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, - ); -} +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: genTsGhjkFile( + { installConf: testCase.installConf ?? dummy(), taskDefs: [] }, + ), + ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], + name: `reloadHooks/${testCase.name}`, + timeout_ms: 5 * 60 * 1000, +}))); diff --git a/tests/tasks.ts b/tests/tasks.ts index 82ec3a91..cb8cf352 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -1,57 +1,59 @@ import "../setup_logger.ts"; -import { - dockerE2eTest, - E2eTestCase, - genTsGhjkFile, - localE2eTest, - type TaskDefArgs, -} from "./utils.ts"; +import { E2eTestCase, genTsGhjkFile, harness, type TaskDef } from "./utils.ts"; import * as ghjk from "../mod.ts"; import * as ports from "../ports/mod.ts"; -import { stdSecureConfig } from "../ghjkfiles/mod.ts"; +import { stdSecureConfig } from "../mod.ts"; -type CustomE2eTestCase = Omit & { - ePoint: string; - stdin: string; - tasks: TaskDefArgs[]; - enableRuntimesOnMasterPDAL?: boolean; -}; +type CustomE2eTestCase = + & Omit + & { + ePoint: string; + stdin: string; + enableRuntimesOnMasterPDAL?: boolean; + } + & ( + | { + tasks: TaskDef[]; + } + | { + ghjkTs: string; + } + ); const cases: CustomE2eTestCase[] = [ { name: "base", tasks: [{ name: "greet", - fn: async ({ $, argv: [name] }) => { - await $`echo Hello ${name}!`; + fn: async ($, { argv: [name], workingDir }) => { + await $`echo Hello ${name} from ${workingDir}!`; }, }], ePoint: `fish`, stdin: ` -cat ghjk.ts -test (ghjk x greet world) = 'Hello world!'`, +test (ghjk x greet world) = "Hello world from $PWD!"`, }, { name: "env_vars", tasks: [{ name: "greet", envVars: { - NAME: "moon", + LUNA: "moon", + SOL: "sun", }, - fn: async ({ $ }) => { - await $`echo Hello $NAME!`; + fn: async ($) => { + await $`echo "Hello $SOL & ${$.env["LUNA"]!}"!`; }, }], ePoint: `fish`, stdin: ` -cat ghjk.ts -test (ghjk x greet world) = 'Hello moon!'`, +test (ghjk x greet world) = 'Hello sun & moon!'`, }, { name: "ports", tasks: [{ name: "protoc", installs: [ports.protoc()], - fn: async ({ $ }) => { + fn: async ($) => { await $`protoc --version`; }, }], @@ -63,10 +65,10 @@ ghjk x protoc`, name: "port_deps", tasks: [{ name: "test", - // node depends on tar_aa + // pipi depends on cpy_bs installs: [...ports.pipi({ packageName: "pre-commit" })], allowedPortDeps: ghjk.stdDeps({ enableRuntimes: true }), - fn: async ({ $ }) => { + fn: async ($) => { await $`pre-commit --version`; }, }], @@ -80,7 +82,7 @@ ghjk x protoc`, name: "test", // node depends on tar_aa installs: [ports.node()], - fn: async ({ $ }) => { + fn: async ($) => { await $`node --version`; }, }], @@ -93,21 +95,21 @@ ghjk x protoc`, { name: "ed", dependsOn: [], - fn: async ({ $ }) => { + fn: async ($) => { await $`/bin/sh -c 'echo ed > ed'`; }, }, { name: "edd", dependsOn: ["ed"], - fn: async ({ $ }) => { + fn: async ($) => { await $`/bin/sh -c 'echo $(/bin/cat ed) edd > edd'`; }, }, { name: "eddy", dependsOn: ["edd"], - fn: async ({ $ }) => { + fn: async ($) => { await $`/bin/sh -c 'echo $(/bin/cat edd) eddy > eddy'`; }, }, @@ -118,51 +120,43 @@ ghjk x eddy test (cat eddy) = 'ed edd eddy' `, }, -]; + { + name: "anon", + ghjkTs: ` +export { ghjk } from "$ghjk/mod.ts"; +import { task } from "$ghjk/mod.ts"; -function testMany( - testGroup: string, - cases: CustomE2eTestCase[], - testFn: (inp: E2eTestCase) => Promise, - defaultEnvs: Record = {}, -) { - for (const testCase of cases) { - Deno.test( - `${testGroup} - ${testCase.name}`, - () => - testFn({ - ...testCase, - tsGhjkfileStr: genTsGhjkFile( - { - taskDefs: testCase.tasks, - secureConf: stdSecureConfig({ - enableRuntimes: testCase.enableRuntimesOnMasterPDAL, - }), - }, - ), - ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], - envVars: { - ...defaultEnvs, - ...testCase.envVars, - }, - }), - ); - } -} +task({ + dependsOn: [ + task({ + dependsOn: [ + task(($) => $\`/bin/sh -c 'echo ed > ed'\`), + ], + fn: ($) => $\`/bin/sh -c 'echo $(/bin/cat ed) edd > edd'\`, + }), + ], + name: "eddy", + fn: ($) => $\`/bin/sh -c 'echo $(/bin/cat edd) eddy > eddy'\` +}); +`, + ePoint: `fish`, + stdin: ` +ghjk x eddy +test (cat eddy) = 'ed edd eddy' +`, + }, +]; -const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); -if (e2eType == "both") { - testMany("tasksDockerE2eTest", cases, dockerE2eTest); - testMany(`tasksLocalE2eTest`, cases, localE2eTest); -} else if (e2eType == "local") { - testMany("tasksLocalE2eTest", cases, localE2eTest); -} else if ( - e2eType == "docker" || - !e2eType -) { - testMany("tasksDockerE2eTest", cases, dockerE2eTest); -} else { - throw new Error( - `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, - ); -} +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: "ghjkTs" in testCase ? testCase.ghjkTs : genTsGhjkFile( + { + taskDefs: testCase.tasks, + secureConf: stdSecureConfig({ + enableRuntimes: testCase.enableRuntimesOnMasterPDAL, + }), + }, + ), + ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], + name: `tasks/${testCase.name}`, +}))); diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 7bec733d..83bef2fe 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -1,4 +1,4 @@ -ARG DENO_VERSION=1.42.1 +ARG DENO_VERSION=1.43.1 FROM denoland/deno:bin-$DENO_VERSION AS deno diff --git a/tests/utils.ts b/tests/utils.ts index 707c32e6..e893dbe3 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,19 +1,24 @@ import { defaultInstallArgs, install } from "../install/mod.ts"; import { std_url } from "../deps/dev.ts"; +import { std_async } from "../deps/dev.ts"; import { $, dbg, importRaw } from "../utils/mod.ts"; -import type { - InstallConfigFat, - PortsModuleSecureConfig, -} from "../modules/ports/types.ts"; -import type { EnvDefArgs, TaskDefArgs } from "../mod.ts"; +import type { InstallConfigFat } from "../modules/ports/types.ts"; import logger from "../utils/logger.ts"; -export type { EnvDefArgs, TaskDefArgs } from "../mod.ts"; +import type { + DenoFileSecureConfig, + DenoTaskDefArgs, + EnvDefArgs, +} from "../mod.ts"; +export type { EnvDefArgs } from "../mod.ts"; export type E2eTestCase = { name: string; tsGhjkfileStr: string; envVars?: Record; ePoints: { cmd: string | string[]; stdin?: string }[]; + timeout_ms?: number; + ignore?: boolean; + only?: boolean; }; const dockerCmd = (Deno.env.get("DOCKER_CMD") ?? "docker").split(/\s/); @@ -21,10 +26,11 @@ const dFileTemplate = await importRaw(import.meta.resolve("./test.Dockerfile")); const templateStrings = { addConfig: `#{{CMD_ADD_CONFIG}}`, }; +const noRmi = Deno.env.get("DOCKER_NO_RMI"); export async function dockerE2eTest(testCase: E2eTestCase) { const { name, envVars: testEnvs, ePoints, tsGhjkfileStr } = testCase; - const tag = `ghjk_e2e_${name}`; + const tag = `ghjk_e2e_${name}`.toLowerCase(); const env = { ...testEnvs, }; @@ -71,9 +77,11 @@ export async function dockerE2eTest(testCase: E2eTestCase) { throw err; } } - await $ - .raw`${dockerCmd} rmi '${tag}'` - .env(env); + if (!noRmi) { + await $ + .raw`${dockerCmd} rmi '${tag}'` + .env(env); + } } export async function localE2eTest(testCase: E2eTestCase) { @@ -97,8 +105,6 @@ export async function localE2eTest(testCase: E2eTestCase) { ZDOTDIR: ghjkShareDir.toString(), GHJK_SHARE_DIR: ghjkShareDir.toString(), PATH: `${ghjkShareDir.toString()}:${Deno.env.get("PATH")}`, - // shield tests from external envs - GHJK_ENV: "main", HOME: tmpDir.toString(), }; // install ghjk @@ -116,9 +122,11 @@ export async function localE2eTest(testCase: E2eTestCase) { await $`${ghjkShareDir.join("ghjk").toString()} print config` .cwd(tmpDir.toString()) + .clearEnv() .env(env); await $`${ghjkShareDir.join("ghjk").toString()} envs cook` .cwd(tmpDir.toString()) + .clearEnv() .env(env); /* // print the contents of the ghjk dir for debugging purposes @@ -131,7 +139,7 @@ export async function localE2eTest(testCase: E2eTestCase) { { const confHome = await ghjkShareDir.join(".config").ensureDir(); const fishConfDir = await confHome.join("fish").ensureDir(); - await fishConfDir.join("config.fish").createSymlinkTo( + await fishConfDir.join("config.fish").symlinkTo( ghjkShareDir.join("env.fish").toString(), ); env["XDG_CONFIG_HOME"] = confHome.toString(); @@ -149,11 +157,14 @@ export async function localE2eTest(testCase: E2eTestCase) { await tmpDir.remove({ recursive: true }); } +export type TaskDef = + & Omit + & Required>; export function genTsGhjkFile( { installConf, secureConf, taskDefs, envDefs }: { installConf?: InstallConfigFat | InstallConfigFat[]; - secureConf?: PortsModuleSecureConfig; - taskDefs?: TaskDefArgs[]; + secureConf?: DenoFileSecureConfig; + taskDefs?: TaskDef[]; envDefs?: EnvDefArgs[]; }, ) { @@ -221,3 +232,38 @@ ${tasks} ${envs} `; } + +export function harness( + cases: E2eTestCase[], +) { + const e2eType = Deno.env.get("GHJK_TEST_E2E_TYPE"); + let runners = [[dockerE2eTest, "e2eDocker" as string] as const]; + if (e2eType == "both") { + runners.push([localE2eTest, "e2eLocal"]); + } else if (e2eType == "local") { + runners = [[localE2eTest, "e2eLocal"]]; + } else if ( + e2eType && e2eType != "docker" + ) { + throw new Error( + `unexpected GHJK_TEST_E2E_TYPE: ${e2eType}`, + ); + } + for (const [runner, group] of runners) { + for (const testCase of cases) { + Deno.test( + `${group}/${testCase.name}`, + { + ignore: testCase.ignore, + }, + () => + std_async.deadline( + runner({ + ...testCase, + }), + testCase.timeout_ms ?? 1 * 60 * 1000, + ), + ); + } + } +} diff --git a/utils/logger.ts b/utils/logger.ts index c65b0f4b..e6382011 100644 --- a/utils/logger.ts +++ b/utils/logger.ts @@ -164,7 +164,7 @@ Deno.permissions.query({ } }); -export function isColorfulTty(outFile = Deno.stdout) { +export function isColorfulTty(outFile = Deno.stderr) { if (colorEnvFlagSet) { return true; } diff --git a/utils/mod.ts b/utils/mod.ts index 77fdf4bf..62d782a1 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -1,11 +1,18 @@ import { + _DaxPath as Path, dax, - jsonHash, + json_canonicalize, + multibase32, + multihasher, + multisha2, std_fs, std_path, - std_url, + syncSha256, zod, } from "../deps/common.ts"; +// class re-exports are tricky. We want al importers +// of path to get it from here so we rename in common.ts +export { _DaxPath as Path } from "../deps/common.ts"; import logger, { isColorfulTty } from "./logger.ts"; // NOTE: only use type imports only when getting stuff from "./modules" import type { @@ -18,6 +25,7 @@ import type { } from "../modules/ports/types.ts"; export type DePromisify = T extends Promise ? Inner : T; +export type DeArrayify = T extends Array ? Inner : T; const literalSchema = zod.union([ zod.string(), zod.number(), @@ -46,13 +54,13 @@ export function pathsWithDepArts( const includesSet = new Set(); for (const [_, { execs, libs, includes }] of Object.entries(depArts)) { for (const [_, binPath] of Object.entries(execs)) { - pathSet.add(std_path.dirname(binPath)); + pathSet.add($.path(binPath).parentOrThrow()); } for (const [_, libPath] of Object.entries(libs)) { - libSet.add(std_path.dirname(libPath)); + libSet.add($.path(libPath).parentOrThrow()); } for (const [_, incPath] of Object.entries(includes)) { - includesSet.add(std_path.dirname(incPath)); + includesSet.add($.path(incPath).parentOrThrow()); } } @@ -122,55 +130,51 @@ export function tryDepExecShimPath( return path; } -/** - * Lifted from https://deno.land/x/hextools@v1.0.0 - * MIT License - * Copyright (c) 2020 Santiago Aguilar Hernández - */ -export function bufferToHex(buffer: ArrayBuffer): string { - return Array.prototype.map.call( - new Uint8Array(buffer), - (b) => b.toString(16).padStart(2, "0"), - ).join(""); +const syncSha256Hasher = multihasher.from({ + code: multisha2.sha256.code, + name: multisha2.sha256.name, + encode: (input) => syncSha256(input), +}); + +export async function bufferHashAsync( + buf: Uint8Array, +) { + const hashBuf = await multisha2.sha256.digest(buf); + const hashStr = multibase32.base32.encode(hashBuf.bytes); + return hashStr; } -export async function bufferHashHex( - buf: ArrayBuffer, - algo: AlgorithmIdentifier = "SHA-256", +export function bufferHash( + buf: Uint8Array, ) { - const hashBuf = await crypto.subtle.digest(algo, buf); - return bufferToHex(hashBuf); + const hashBuf = syncSha256Hasher.digest(buf); + if (hashBuf instanceof Promise) throw new Error("impossible"); + const hashStr = multibase32.base32.encode(hashBuf.bytes); + return hashStr; } -export async function stringHashHex( +export function stringHash( val: string, - algo: AlgorithmIdentifier = "SHA-256", ) { const arr = new TextEncoder().encode(val); - return await bufferHashHex(arr, algo); + return bufferHash(arr); } -export async function objectHashHex( - object: jsonHash.Tree, - algo: jsonHash.DigestAlgorithmType = "SHA-256", +export function objectHash( + object: Json, ) { - const hashBuf = await jsonHash.digest(algo, object); - const hashHex = bufferToHex(hashBuf); - return hashHex; + return stringHash(json_canonicalize(object)); } export function getPortRef(manifest: PortManifest) { return `${manifest.name}@${manifest.version}`; } -export async function getInstallHash(install: InstallConfigResolvedX) { - const fullHashHex = await objectHashHex(install as jsonHash.Tree); - const hashHex = fullHashHex.slice(0, 8); - return `${install.portRef}!${hashHex}`; +export function getInstallHash(install: InstallConfigResolvedX) { + const fullHashHex = objectHash(JSON.parse(JSON.stringify(install))); + return `${install.portRef}!${fullHashHex}`; } -export type Path = dax.Path; - export function defaultCommandBuilder() { const builder = new dax.CommandBuilder() .printCommand(true); @@ -189,11 +193,14 @@ export function defaultCommandBuilder() { export const $ = dax.build$( { commandBuilder: defaultCommandBuilder(), + requestBuilder: new dax.RequestBuilder() + .showProgress(Deno.stderr.isTerminal()), extras: { inspect(val: unknown) { return Deno.inspect(val, { colors: isColorfulTty(), iterableLimit: 500, + depth: 10, }); }, pathToString(path: Path) { @@ -214,20 +221,19 @@ export function inWorker() { self instanceof WorkerGlobalScope; } -export async function findConfig(path: string) { - let current = path; +export async function findEntryRecursive(path: string, name: string) { + let current = $.path(path); while (true) { - const location = `${current}/ghjk.ts`; - if (await std_fs.exists(location)) { + const location = `${current}/${name}`; + if (await $.path(location).exists()) { return location; } - const nextCurrent = std_path.dirname(current); - if (nextCurrent == "/" && current == "/") { + const nextCurrent = $.path(current).parent(); + if (!nextCurrent) { break; } current = nextCurrent; } - return null; } export function home_dir(): string | null { @@ -249,7 +255,7 @@ export function dirs() { } return { homeDir: home, - shareDir: std_path.resolve(home, ".local", "share"), + shareDir: $.path(home).resolve(".local", "share"), }; } @@ -261,19 +267,18 @@ if (Number.isNaN(AVAIL_CONCURRENCY)) { throw new Error(`Value of DENO_JOBS is NAN: ${Deno.env.get("DENO_JOBS")}`); } -export async function importRaw(spec: string) { +export async function importRaw(spec: string, timeout: dax.Delay = "1m") { const url = new URL(spec); if (url.protocol == "file:") { - return await Deno.readTextFile(url.pathname); + return await $.path(url.pathname).readText(); } if (url.protocol.match(/^http/)) { - const resp = await fetch(url); - if (!resp.ok) { - throw new Error( - `error importing raw using fetch from ${spec}: ${resp.status} - ${resp.statusText}`, - ); + let request = $.request(url).timeout(timeout); + const integrity = url.searchParams.get("integrity"); + if (integrity) { + request = request.integrity(integrity); } - return await resp.text(); + return await request.text(); } throw new Error( `error importing raw from ${spec}: unrecognized protocol ${url.protocol}`, @@ -351,7 +356,7 @@ export async function downloadFile( args: DownloadFileArgs, ) { const { name, mode, url, downloadPath, tmpDirPath, headers } = { - name: std_url.basename(args.url), + name: $.path(args.url).basename(), mode: 0o666, headers: {}, ...args, @@ -366,7 +371,6 @@ export async function downloadFile( await $.request(url) .header(headers) - .showProgress() .pipeToPath(tmpFilePath, { create: true, mode }); await $.path(downloadPath).ensureDir(); From fda407110e332cd309c19e95effbae582156a804 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 27 Apr 2024 15:41:22 +0300 Subject: [PATCH 08/21] docs: metatype ecosystem banner in README.md (#49) * docs: metatype ecosystem banner in README.md * fix: utm --------- Co-authored-by: Teo Stocco --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a36dc4cb..c62b66d1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ ghjk /jk/ is a programmable runtime manager. +> Ghjk is part of the +> [Metatype ecosystem](https://github.com/metatypedev/metatype). Consider +> checking out how this component integrates with the whole ecosystem and browse +> the +> [documentation](https://metatype.dev?utm_source=github&utm_medium=readme&utm_campaign=ghjk) +> to see more examples. + ## Features - install and manage tools (e.g. rustup, deno, node, etc.) @@ -15,8 +22,8 @@ ghjk /jk/ is a programmable runtime manager. - `x meta` -> `cargo run -p meta` (avoid conflicts and provide autocompletion) - [ ] load environment variables and prompt for missing ones - [ ] define build tasks with dependencies - - `task("build", {depends_on: [rust], if: Deno.build.os === "Macos" })` - - `task.bash("ls")` + - [x] `task("build", {depends_on: [rust], if: Deno.build.os === "Macos" })` + - [ ] `task.bash("ls")` - [x] compatible with continuous integration (e.g. github actions, gitlab) ## Getting started @@ -90,7 +97,10 @@ ghjk.env("dev", { ports.cargobi({ crateName: "cargo-insta" }), ports.act(), ], -}); +}) + // use env hooks to run code on activation/deactivation + .onEnter(ghjk.task(($) => $`echo dev activated`)) + .onExit(ghjk.task(($) => $`echo dev de-activated`)); ghjk.env({ name: "docker", From 7bc27e9228d2f1ad978fa862bcb15b8475c73b39 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 7 May 2024 09:16:22 +0300 Subject: [PATCH 09/21] fix(port): `cpy_bs` doesn't work on arm (#60) * wip: linux/arm in docker on mac-14 * wip: try `custom-macos` * wip: try inline docker * wip: try `custom-macos` * fix: remove unnecessary action * fix: move platform flags into tests * refactor: merge new job into matrix * fix: broken aarch64 ports * wip: log * fix: diambiguate platforms in harness * fix: silly ci bug --- .dockerignore | 1 - .github/workflows/tests.yml | 26 ++++++++++++++------------ .pre-commit-config.yaml | 2 +- ports/cpy_bs.ts | 6 +++++- ports/meta_cli_ghrel.ts | 3 +++ tests/ports.ts | 2 ++ tests/test.Dockerfile.dockerignore | 4 ++++ tests/utils.ts | 23 +++++++++++++++++++++-- 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 tests/test.Dockerfile.dockerignore diff --git a/.dockerignore b/.dockerignore index 1eb1bed0..1970c63b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ .git .vscode -tests/ *.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d05d849a..c14dbb4e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,10 +42,16 @@ jobs: matrix: include: - os: ubuntu-latest + platform: linux/x86_64 + e2eType: "docker" + - os: custom-macos + platform: linux/aarch64 e2eType: "docker" - os: macos-latest + platform: darwin/x86_64 e2eType: "local" - os: macos-14 + platform: darwin/aarch64 e2eType: "local" # - os: windows-latest # e2eType: "local" @@ -56,24 +62,20 @@ jobs: - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} - - - if: "${{ matrix.os == 'macos-latest' || matrix.os == 'macos-14' }}" - # we need coreutils on max for the `timeout` command - run: brew install fish zsh coreutils - name: Cache deno dir - if: "${{ matrix.os == 'macos-latest' || matrix.os == 'macos-14' }}" uses: actions/cache@v4 with: path: ${{ env.DENO_DIR }} - key: deno-mac-${{ hashFiles('**/deno.lock') }} + key: deno-${{ hashFiles('**/deno.lock') }} - if: "${{ matrix.e2eType == 'docker' }}" uses: docker/setup-buildx-action@v3 - - if: "${{ matrix.e2eType == 'docker' }}" - uses: actions-hub/docker/cli@master - env: - SKIP_LOGIN: true - - - run: deno task test + - if: "${{ matrix.os == 'macos-latest' || matrix.os == 'macos-14' }}" + # we need coreutils on max for the `timeout` command + run: brew install fish zsh coreutils + - env: + DOCKER_PLATFORM: ${{ matrix.platform }} + run: deno task test + test-action: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 69817abc..523a9176 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: gh_action/.*.js )$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.0 + rev: 0.28.2 hooks: - id: check-dependabot - id: check-github-workflows diff --git a/ports/cpy_bs.ts b/ports/cpy_bs.ts index 81cdf44d..1a6f050a 100644 --- a/ports/cpy_bs.ts +++ b/ports/cpy_bs.ts @@ -147,6 +147,7 @@ export class Port extends PortBase { } const { installVersion, platform } = args; const arch = platform.arch; + let postfix = "pgo+lto-full"; let os; switch (platform.os) { case "windows": @@ -157,6 +158,9 @@ export class Port extends PortBase { // but it breaks python extensions support so we // must use glibc os = "unknown-linux-gnu"; + if (arch == "aarch64") { + postfix = "lto-full"; + } break; case "darwin": os = "apple-darwin"; @@ -165,7 +169,7 @@ export class Port extends PortBase { throw new Error(`unsupported: ${platform}`); } const urls = [ - `https://github.com/${this.repoOwner}/${this.repoName}/releases/download/${tag}/cpython-${installVersion}+${tag}-${arch}-${os}-pgo+lto-full.tar.zst`, + `https://github.com/${this.repoOwner}/${this.repoName}/releases/download/${tag}/cpython-${installVersion}+${tag}-${arch}-${os}-${postfix}.tar.zst`, ]; await Promise.all( urls.map(dwnUrlOut) diff --git a/ports/meta_cli_ghrel.ts b/ports/meta_cli_ghrel.ts index 033d6d63..97e6c696 100644 --- a/ports/meta_cli_ghrel.ts +++ b/ports/meta_cli_ghrel.ts @@ -75,6 +75,9 @@ export class Port extends GithubReleasePort { default: throw new Error(`unsupported: ${platform}`); } + if (platform.os == "linux" && platform.arch == "aarch64") { + throw new Error(`unsupported: ${platform}`); + } return [ this.releaseArtifactUrl( installVersion, diff --git a/tests/ports.ts b/tests/ports.ts index b94f440f..c6231037 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -4,6 +4,7 @@ import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; import * as ports from "../ports/mod.ts"; import dummy from "../ports/dummy.ts"; import type { InstallConfigFat } from "../modules/ports/types.ts"; +import { testTargetPlatform } from "./utils.ts"; type CustomE2eTestCase = Omit & { ePoint: string; @@ -136,6 +137,7 @@ const cases: CustomE2eTestCase[] = [ // executrable ? `which meta && wasmedge --version` : `meta --version && wasmedge --version`, + ignore: testTargetPlatform == "linux/aarch64", }, // 77 meg + { diff --git a/tests/test.Dockerfile.dockerignore b/tests/test.Dockerfile.dockerignore new file mode 100644 index 00000000..1eb1bed0 --- /dev/null +++ b/tests/test.Dockerfile.dockerignore @@ -0,0 +1,4 @@ +.git +.vscode +tests/ +*.md diff --git a/tests/utils.ts b/tests/utils.ts index e893dbe3..c79fe8d2 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -10,6 +10,8 @@ import type { EnvDefArgs, } from "../mod.ts"; export type { EnvDefArgs } from "../mod.ts"; +import { ALL_OS } from "../port.ts"; +import { ALL_ARCH } from "../port.ts"; export type E2eTestCase = { name: string; @@ -21,7 +23,24 @@ export type E2eTestCase = { only?: boolean; }; +export const testTargetPlatform = Deno.env.get("DOCKER_PLATFORM") ?? + (Deno.build.os + "/" + Deno.build.arch); + +if ( + !([...ALL_OS] as string[]).includes(testTargetPlatform.split("/")[0]) || + !([...ALL_ARCH] as string[]).includes(testTargetPlatform.split("/")[1]) +) { + throw new Error(`unsupported test platform: ${testTargetPlatform}`); +} + +const dockerPlatform = `--platform=${ + testTargetPlatform + .replace("x86_64", "amd64") + .replace("aarch64", "arm64") +}`; + const dockerCmd = (Deno.env.get("DOCKER_CMD") ?? "docker").split(/\s/); + const dFileTemplate = await importRaw(import.meta.resolve("./test.Dockerfile")); const templateStrings = { addConfig: `#{{CMD_ADD_CONFIG}}`, @@ -52,14 +71,14 @@ export async function dockerE2eTest(testCase: E2eTestCase) { )); await $ - .raw`${dockerCmd} buildx build ${ + .raw`${dockerCmd} buildx build ${dockerPlatform} ${ Object.entries(env).map(([key, val]) => ["--build-arg", `${key}=${val}`]) } --tag '${tag}' --network=host --output type=docker -f- .` .env(env) .stdinText(dFile); for (const ePoint of ePoints) { - let cmd = $.raw`${dockerCmd} run --rm ${[ + let cmd = $.raw`${dockerCmd} run ${dockerPlatform} --rm ${[ /* we want to enable interactivity when piping in */ ePoint.stdin ? "-i " : "", ...Object.entries(env).map(([key, val]) => ["-e", `${key}=${val}`]) From 91d66da602b770a3fcbae57c9acb29652a4e308e Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 7 May 2024 07:31:27 +0000 Subject: [PATCH 10/21] fix: env depending on itself case --- .ghjk/lock.json | 21 +++++++++------------ files/mod.ts | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.ghjk/lock.json b/.ghjk/lock.json index e5e4dc9d..e4993d66 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -89,10 +89,7 @@ }, "ghjkEnvProvInstSet___test": { "installs": [ - "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa", - "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", - "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", - "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q" + "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa" ], "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" } @@ -126,6 +123,14 @@ "id": "envs", "config": { "envs": { + "test": { + "provides": [ + { + "ty": "ghjk.ports.InstallSetRef", + "setId": "ghjkEnvProvInstSet___test" + } + ] + }, "main": { "desc": "the default default environment.", "provides": [ @@ -150,14 +155,6 @@ "setId": "ghjkEnvProvInstSet___main" } ] - }, - "test": { - "provides": [ - { - "ty": "ghjk.ports.InstallSetRef", - "setId": "ghjkEnvProvInstSet___test" - } - ] } }, "defaultEnv": "main" diff --git a/files/mod.ts b/files/mod.ts index 2a476e56..0d657caa 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -333,7 +333,7 @@ export class Ghjkfile { const final = finalizer(); const envBaseResolved = typeof final.base === "string" ? final.base - : final.base + : final.base && defaultBaseEnv != final.name ? defaultBaseEnv : null; all[final.name] = { ...final, envBaseResolved }; From 30c591431f4d4167fe5216746342e58316f3faf5 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 7 May 2024 08:08:58 +0000 Subject: [PATCH 11/21] fix: lint issue --- tests/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.ts b/tests/utils.ts index 71d0ede8..c79fe8d2 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -9,7 +9,7 @@ import type { DenoTaskDefArgs, EnvDefArgs, } from "../mod.ts"; -import type { TaskDefNice } from "../mod.ts"; +export type { EnvDefArgs } from "../mod.ts"; import { ALL_OS } from "../port.ts"; import { ALL_ARCH } from "../port.ts"; From 4411ca16df1417294f8f13e99f889ec8df556335 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Wed, 8 May 2024 21:49:25 +0000 Subject: [PATCH 12/21] fix: use 5m timeout for everything --- tests/envHooks.ts | 1 - tests/envs.ts | 1 - tests/ports.ts | 4 ---- tests/reloadHooks.ts | 1 - tests/utils.ts | 5 ++++- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/envHooks.ts b/tests/envHooks.ts index 860790cd..ab399a79 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -153,5 +153,4 @@ env("main") `, ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], name: `envHooks/${testCase.name}`, - timeout_ms: 5 * 60 * 1000, }))); diff --git a/tests/envs.ts b/tests/envs.ts index 9c7882a9..4ffd0993 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -202,5 +202,4 @@ harness(cases.map((testCase) => ({ ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], name: `envs/${testCase.name}`, - timeout_ms: 5 * 60 * 1000, }))); diff --git a/tests/ports.ts b/tests/ports.ts index c6231037..93605ec6 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -221,9 +221,5 @@ harness(cases.map((testCase) => ({ ], }, */ ], - // building the test docker image might taka a while - // but we don't want some bug spinlocking the ci for - // an hour - timeout_ms: 5 * 60 * 1000, name: `ports/${testCase.name}`, }))); diff --git a/tests/reloadHooks.ts b/tests/reloadHooks.ts index 68d22036..376fdc8c 100644 --- a/tests/reloadHooks.ts +++ b/tests/reloadHooks.ts @@ -162,5 +162,4 @@ harness(cases.map((testCase) => ({ ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], name: `reloadHooks/${testCase.name}`, - timeout_ms: 5 * 60 * 1000, }))); diff --git a/tests/utils.ts b/tests/utils.ts index c79fe8d2..904f8efd 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -280,7 +280,10 @@ export function harness( runner({ ...testCase, }), - testCase.timeout_ms ?? 1 * 60 * 1000, + // building the test docker image might taka a while + // but we don't want some bug spinlocking the ci for + // an hour + testCase.timeout_ms ?? 5 * 60 * 1000, ), ); } From f26fcec19643780d7ebda7ad46c927a9d1278859 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sat, 25 May 2024 00:25:25 +0300 Subject: [PATCH 13/21] refactor(envs): `GHJK_NEXTFILE` based env reloading (#83) * wip: nextfile * tests: nextfile * refactor: pid based nextfile * refactor: replace with ghjkdir vars instead of $HOME * fix: env hook tests * fix: bashisms --- .ghjk/deno.lock | 47 +++++++++++- .ghjk/lock.json | 4 +- README.md | 6 +- deno.lock | 22 +++++- deps/cli.ts | 1 + deps/common.ts | 3 +- files/mod.ts | 36 ++++----- ghjk.ts | 2 +- host/mod.ts | 35 ++++----- install/ghjk.sh | 9 +++ install/hook.fish | 67 ++++++++++------- install/hook.sh | 67 ++++++++++------- install/mod.ts | 7 +- mod.ts | 2 +- modules/envs/mod.ts | 52 ++++++------- modules/envs/posix.ts | 166 +++++++++++++++++++++++++++-------------- modules/ports/ghrel.ts | 11 +-- ports/cpy_bs.ts | 5 +- tests/envHooks.ts | 108 ++++++--------------------- tests/envs.ts | 4 +- tests/reloadHooks.ts | 139 +++++++++++++++++++++++----------- tests/utils.ts | 5 ++ utils/mod.ts | 33 ++++---- 23 files changed, 479 insertions(+), 352 deletions(-) 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; From 47221292c72405f13cb645ead072acf17bfdf623 Mon Sep 17 00:00:00 2001 From: Estifanos Bireda <77430541+destifo@users.noreply.github.com> Date: Fri, 31 May 2024 10:56:36 +0300 Subject: [PATCH 14/21] feat(modules): ports outdated command (#62) * wip(modules): add installSetIds to * wip(modules-ports): read recipe.json and get cooked installSetIds * wip(ports): wip for version getter func * feat(modules): restrcutre ctx tree and recipe.json content * feat(ports): add table to show version diff * fix: fix unhandled prov type in envs/posix cook * wip: wip update outdated installs * wip: add ghjk file with multiple installs * chore(test): wip add test * refactor: refactor table structure and update cargo_binstall --- .ghjk/lock.json | 8 +- check.ts | 1 + deps/cli.ts | 1 + docs/available-commands.md | 15 ++ examples/cmake/ghjk.ts | 11 ++ examples/many_installs/ghjk.ts | 71 +++++++ files/mod.ts | 7 +- modules/envs/mod.ts | 13 +- modules/envs/posix.ts | 43 ++-- modules/envs/reducer.ts | 5 +- modules/envs/types.ts | 19 +- modules/ports/db.ts | 2 +- modules/ports/mod.ts | 240 +++++++++++++++++++++-- modules/ports/reducers.ts | 7 +- modules/ports/sync.ts | 38 ++-- modules/ports/types.ts | 10 + modules/ports/utils.ts | 140 +++++++++++++ modules/tasks/mod.ts | 13 +- modules/types.ts | 4 + modules/utils.ts | 93 +++++++++ ports/cargo-binstall.ts | 11 +- tests/modules/ports/portsOutdatedTest.ts | 75 +++++++ tests/ports.ts | 3 - 23 files changed, 754 insertions(+), 76 deletions(-) create mode 100644 docs/available-commands.md create mode 100644 examples/cmake/ghjk.ts create mode 100644 examples/many_installs/ghjk.ts create mode 100644 modules/ports/utils.ts create mode 100644 modules/utils.ts create mode 100644 tests/modules/ports/portsOutdatedTest.ts diff --git a/.ghjk/lock.json b/.ghjk/lock.json index e94f7ebf..a626417a 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -1,6 +1,6 @@ { "version": "0", - "platform": "x86_64-linux", + "platform": "aarch64-darwin", "moduleEntries": { "ports": { "version": "0", @@ -17,7 +17,7 @@ "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "3.5", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -37,7 +37,7 @@ "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "3.5", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, @@ -50,7 +50,7 @@ "portRef": "cpy_bs_ghrel@0.1.0" }, "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { - "version": "1.34", + "version": "3.5", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0" }, diff --git a/check.ts b/check.ts index 77a9dfd7..d2fd45a2 100755 --- a/check.ts +++ b/check.ts @@ -10,6 +10,7 @@ const files = (await Array.fromAsync( ".ghjk/**", ".deno-dir/**", "vendor/**", + ".git/**", // was throwing an error without this ], }), )).map((ref) => ref.path.toString()); diff --git a/deps/cli.ts b/deps/cli.ts index da6408eb..bd6a415f 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -3,4 +3,5 @@ export * from "./common.ts"; export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; +export { Table } from "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts"; export * as zod_val_err from "npm:zod-validation-error@3.3.0"; diff --git a/docs/available-commands.md b/docs/available-commands.md new file mode 100644 index 00000000..79353a4d --- /dev/null +++ b/docs/available-commands.md @@ -0,0 +1,15 @@ + + +| 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/examples/cmake/ghjk.ts b/examples/cmake/ghjk.ts new file mode 100644 index 00000000..289a7eda --- /dev/null +++ b/examples/cmake/ghjk.ts @@ -0,0 +1,11 @@ +export { ghjk } from "../../mod.ts"; +import { install } from "../../mod.ts"; +import * as ports from "../../ports/mod.ts"; + +install( + ports.asdf({ + pluginRepo: "https://github.com/asdf-community/asdf-cmake", + installType: "version", + version: "3.29.1", + }), +); diff --git a/examples/many_installs/ghjk.ts b/examples/many_installs/ghjk.ts new file mode 100644 index 00000000..570772a4 --- /dev/null +++ b/examples/many_installs/ghjk.ts @@ -0,0 +1,71 @@ +export { ghjk } from "../../mod.ts"; +import { install, stdDeps, stdSecureConfig } from "../../mod.ts"; +import * as ports from "../../ports/mod.ts"; + +// specify versions +const PROTOC_VERSION = "v24.1"; +const POETRY_VERSION = "1.7.0"; +const PYTHON_VERSION = "3.8.18"; +const CARGO_INSTA_VERSION = "1.33.0"; +const NODE_VERSION = "20.8.0"; +const PNPM_VERSION = "v9.0.5"; + +const installs = { + python: ports.cpy_bs({ version: PYTHON_VERSION, releaseTag: "20240224" }), + python_latest: ports.cpy_bs({ releaseTag: "20240224" }), + node: ports.node({ version: NODE_VERSION }), +}; + +const allowedPortDeps = [ + ...stdDeps(), + ...[installs.python_latest, installs.node], +]; + +export const secureConfig = stdSecureConfig({ + additionalAllowedPorts: allowedPortDeps, + enableRuntimes: true, +}); + +install( + //others + ports.act(), + ports.protoc({ version: PROTOC_VERSION }), + // cargo crate installs + ports.cargobi({ + crateName: "cargo-insta", + version: CARGO_INSTA_VERSION, + locked: true, + }), + ports.cargo_binstall({ + crateName: "regex-lite", + }), +); + +install( + // python package installs + installs.python_latest, + ports.pipi({ + packageName: "poetry", + version: POETRY_VERSION, + })[0], + ports.pipi({ + packageName: "requests", + version: "2.18.0", + })[0], + ports.pipi({ + packageName: "pre-commit", + })[0], +); + +install( + // npm packages + installs.node, + ports.pnpm({ version: PNPM_VERSION }), + ports.npmi({ + packageName: "yarn", + version: "1.9.1", + })[0], + ports.npmi({ + packageName: "readme", + })[0], +); diff --git a/files/mod.ts b/files/mod.ts index 24b04094..7a04da23 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -470,7 +470,12 @@ export class Ghjkfile { } // sanity checks if (revDeps.size > 0) { - throw new Error("working set empty but pending items found"); + throw new Error(`working set empty but pending items found`, { + cause: { + revDeps, + workingSet, + }, + }); } return moduleConfig; } diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 9ea3f4d7..8a8fddd8 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -8,7 +8,7 @@ import type { EnvsModuleConfigX, WellKnownProvision, } from "./types.ts"; -import type { GhjkCtx, ModuleManifest } from "../types.ts"; +import { type GhjkCtx, type ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import type { Blackboard } from "../../host/types.ts"; import { cookPosixEnv } from "./posix.ts"; @@ -18,6 +18,7 @@ import type { InstallSetRefProvision, } from "../ports/types.ts"; import { buildInstallGraph, syncCtxFromGhjk } from "../ports/sync.ts"; +import { getEnvsCtx } from "../utils.ts"; export type EnvsCtx = { activeEnv: string; @@ -50,10 +51,11 @@ export class EnvsModule extends ModuleBase { const setEnv = Deno.env.get("GHJK_ENV"); const activeEnv = setEnv && setEnv != "" ? setEnv : config.defaultEnv; - return Promise.resolve({ - activeEnv, - config, - }); + const envsCtx = getEnvsCtx(_ctx); + envsCtx.activeEnv = activeEnv; + envsCtx.config = config; + + return Promise.resolve(envsCtx); } commands( @@ -207,7 +209,6 @@ async function reduceAndCookEnv( return; } */ - await cookPosixEnv({ gcx, recipe, diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 767e6b82..6be27c6f 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -1,5 +1,9 @@ import { std_fs, std_path } from "../../deps/cli.ts"; -import type { EnvRecipeX } from "./types.ts"; +import { + type EnvRecipeX, + WellKnownProvision, + wellKnownProvisionTypes, +} from "./types.ts"; import { $, Path } from "../../utils/mod.ts"; import type { GhjkCtx } from "../types.ts"; import { reduceStrangeProvisions } from "./reducer.ts"; @@ -43,34 +47,45 @@ export async function cookPosixEnv( // FIXME: better support for multi installs await Promise.all(reducedRecipe.provides.map((item) => { - switch (item.ty) { + if (!wellKnownProvisionTypes.includes(item.ty)) { + return Promise.resolve(); + } + + const wellKnownProv = item as WellKnownProvision; + switch (wellKnownProv.ty) { case "posix.exec": - binPaths.push(item.absolutePath); + binPaths.push(wellKnownProv.absolutePath); break; case "posix.sharedLib": - libPaths.push(item.absolutePath); + libPaths.push(wellKnownProv.absolutePath); break; case "posix.headerFile": - includePaths.push(item.absolutePath); + includePaths.push(wellKnownProv.absolutePath); break; case "posix.envVar": - if (vars[item.key]) { + if (vars[wellKnownProv.key]) { throw new Error( - `env var conflict cooking unix env: key "${item.key}" has entries "${ - vars[item.key] - }" and "${item.val}"`, + `env var conflict cooking unix env: key "${wellKnownProv.key}" has entries "${ + vars[wellKnownProv.key] + }" and "${wellKnownProv.val}"`, ); } - vars[item.key] = item.val; + vars[wellKnownProv.key] = wellKnownProv.val; + // installSetIds.push(wellKnownProv.installSetIdProvision!.id); break; case "hook.onEnter.posixExec": - onEnterHooks.push([item.program, item.arguments]); + onEnterHooks.push([wellKnownProv.program, wellKnownProv.arguments]); break; case "hook.onExit.posixExec": - onExitHooks.push([item.program, item.arguments]); + onExitHooks.push([wellKnownProv.program, wellKnownProv.arguments]); + break; + case "ghjk.ports.Install": + // do nothing break; default: - throw Error(`unsupported provision type: ${(item as any).provision}`); + throw Error( + `unsupported provision type: ${(wellKnownProv as any).provision}`, + ); } })); void await Promise.all([ @@ -153,7 +168,7 @@ async function shimLinkPaths( if (shims[fileName]) { throw new Error( - `duplicate shim found when adding shim for file "${fileName}"`, + `duplicate shim found when adding shim for file: "${fileName}"`, ); } try { diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts index 864e9ef1..17db7793 100644 --- a/modules/envs/reducer.ts +++ b/modules/envs/reducer.ts @@ -10,7 +10,10 @@ import type { import { wellKnownProvisionTypes } from "./types.ts"; import validators from "./types.ts"; -export type ProvisionReducerStore = Map>; +export type ProvisionReducerStore = Map< + string, + ProvisionReducer +>; /** * In order to provide a means for other modules to define their own diff --git a/modules/envs/types.ts b/modules/envs/types.ts index 90194fd0..a5589ee1 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -1,4 +1,5 @@ import { std_path, zod } from "../../deps/common.ts"; +import { installProvisionTy } from "../ports/types.ts"; const absolutePath = zod.string().refine((path) => std_path.isAbsolute(path)); @@ -15,12 +16,17 @@ export const hookProvisionTypes = [ "hook.onExit.posixExec", ] as const; +export const installProvisionTypes = [ + installProvisionTy, +] as const; + // we separate the posix file types in a separate // array in the interest of type inference export const wellKnownProvisionTypes = [ "posix.envVar", ...posixFileProvisionTypes, ...hookProvisionTypes, + ...installProvisionTypes, ] as const; const wellKnownProvision = zod.discriminatedUnion( @@ -41,6 +47,15 @@ const wellKnownProvision = zod.discriminatedUnion( ...posixFileProvisionTypes.map((ty) => zod.object({ ty: zod.literal(ty), absolutePath }) ), + ...installProvisionTypes.map( + (ty) => + zod.object( + { + ty: zod.literal(ty), + instId: zod.string(), + }, + ), + ), ], ); @@ -90,6 +105,6 @@ export type WellKnownEnvRecipeX = zod.infer< /* * A function that batch convert strange provisions of a certain kind to well known ones. */ -export type ProvisionReducer

= ( +export type ProvisionReducer

= ( provisions: P[], -) => Promise; +) => Promise; diff --git a/modules/ports/db.ts b/modules/ports/db.ts index 1a9678a5..552400e6 100644 --- a/modules/ports/db.ts +++ b/modules/ports/db.ts @@ -13,7 +13,7 @@ import validators from "./types.ts"; const installRowValidator = zod.object({ // version: zod.string(), installId: zod.string(), - conf: validators.installConfigLite, + conf: validators.installConfigResolved, manifest: validators.portManifest, installArts: validators.installArtifacts.nullish(), downloadArts: validators.downloadArtifacts, diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index bdeb765f..6fa84b54 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -1,18 +1,30 @@ export * from "./types.ts"; -import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { Json, unwrapParseRes } from "../../utils/mod.ts"; +import { cliffy_cmd, Table, zod } from "../../deps/cli.ts"; +import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; import logger from "../../utils/logger.ts"; import validators, { + installProvisionTy, installSetProvisionTy, installSetRefProvisionTy, } from "./types.ts"; -import type { InstallSetX, PortsModuleConfigX } from "./types.ts"; -import type { GhjkCtx, ModuleManifest } from "../types.ts"; +import envsValidators from "../envs/types.ts"; +import type { + AllowedPortDep, + InstallConfigResolved, + InstallProvision, + InstallSetX, + PortsModuleConfigX, +} from "./types.ts"; +import { type GhjkCtx, type ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import { buildInstallGraph, + getDepConfig, + getPortImpl, getResolutionMemo, + getShimmedDepArts, + resolveAndInstall, syncCtxFromGhjk, } from "./sync.ts"; // TODO: rename to install.ts import type { Blackboard } from "../../host/types.ts"; @@ -20,6 +32,8 @@ import { getProvisionReducerStore } from "../envs/reducer.ts"; import { installSetReducer, installSetRefReducer } from "./reducers.ts"; import type { Provision, ProvisionReducer } from "../envs/types.ts"; import { getInstallSetStore } from "./inter.ts"; +import { getActiveEnvInstallSetId, getEnvsCtx, getPortsCtx } from "../utils.ts"; +import { updateInstall } from "./utils.ts"; export type PortsCtx = { config: PortsModuleConfigX; @@ -52,11 +66,9 @@ export class PortsModule extends ModuleBase { const hashedModConf = unwrapParseCurry( validators.portsModuleConfigHashed.safeParse(manifest.config), ); - const pcx: PortsCtx = { - config: { - sets: {}, - }, - }; + + const pcx: PortsCtx = getPortsCtx(gcx); + const setStore = getInstallSetStore(gcx); // pre-process the install sets found in the config for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { @@ -92,12 +104,13 @@ export class PortsModule extends ModuleBase { const reducerStore = getProvisionReducerStore(gcx); reducerStore.set( installSetRefProvisionTy, - installSetRefReducer(gcx, pcx) as ProvisionReducer, + installSetRefReducer(gcx, pcx) as ProvisionReducer, ); reducerStore.set( installSetProvisionTy, - installSetReducer(gcx) as ProvisionReducer, + installSetReducer(gcx) as ProvisionReducer, ); + return pcx; } @@ -131,9 +144,103 @@ export class PortsModule extends ModuleBase { .command( "outdated", new cliffy_cmd.Command() - .description("TODO") - .action(function () { - throw new Error("TODO"); + .description("Show a version table for installs") + .option( + "-u, --update-install ", + "Update specific install", + ) + .option("-n, --update-all", "Update all installs") + .action(async (_opts) => { + const envsCtx = getEnvsCtx(gcx); + const envName = envsCtx.activeEnv; + + const installSets = pcx.config.sets; + + const currInstallSetId = getActiveEnvInstallSetId(envsCtx); + const currInstallSet = installSets[currInstallSetId]; + const allowedDeps = currInstallSet.allowedDeps; + + const rows = []; + const { + installedPortsVersions: installed, + latestPortsVersions: latest, + installConfigs, + } = await getOldNewVersionComparison( + gcx, + envName, + allowedDeps, + ); + for (let [installId, installedVersion] of installed.entries()) { + let latestVersion = latest.get(installId); + if (!latestVersion) { + throw new Error( + `Couldn't find the latest version for install id: ${installId}`, + ); + } + + if (latestVersion[0] === "v") { + latestVersion = latestVersion.slice(1); + } + if (installedVersion[0] === "v") { + installedVersion = installedVersion.slice(1); + } + + const config = installConfigs.get(installId); + + if (!config) { + throw new Error( + `Config not found for install id: ${installId}`, + ); + } + + if (config["specifiedVersion"]) { + latestVersion = "=" + latestVersion; + } + + const presentableConfig = { ...config }; + ["buildDepConfigs", "version", "specifiedVersion"].map( + (key) => { + delete presentableConfig[key]; + }, + ); + const row = [ + $.inspect(presentableConfig), + installedVersion, + latestVersion, + ]; + rows.push(row); + } + + if (_opts.updateInstall) { + const installName = _opts.updateInstall; + // TODO: convert from install name to install id, after port module refactor + let installId!: string; + const newVersion = latest.get(installId); + if (!newVersion) { + logger().info( + `Error while fetching the latest version for: ${installName}`, + ); + return; + } + await updateInstall(gcx, installId, newVersion, allowedDeps); + return; + } + + if (_opts.updateAll) { + for (const [installId, newVersion] of latest.entries()) { + await updateInstall(gcx, installId, newVersion, allowedDeps); + } + return; + } + + const _versionTable = new Table() + .header(["Install Config", "Old Version", "New Version"]) + .body(rows) + .border() + .padding(1) + .indent(2) + .maxColWidth(30) + .render(); }), ) .command( @@ -180,3 +287,108 @@ export class PortsModule extends ModuleBase { }; } } + +async function getOldNewVersionComparison( + gcx: GhjkCtx, + envName: string, + allowedDeps: Record, +) { + await using scx = await syncCtxFromGhjk(gcx); + + const envDir = $.path(gcx.ghjkDir).join("envs", envName); + const recipePath = envDir.join("recipe.json").toString(); + + // read from `recipe.json` and get installSetIds + const recipeJson = JSON.parse(await Deno.readTextFile(recipePath)); + const reducedRecipe = unwrapParseRes( + envsValidators.envRecipe.safeParse(recipeJson), + { + envName, + recipePath, + }, + "error parsing recipe.json", + ); + + const installProvisions = reducedRecipe.provides.filter((prov) => + prov.ty === installProvisionTy + ) as InstallProvision[]; + + const db = scx.db.val; + + const installedPortsVersions = new Map(); + const latestPortsVersions = new Map(); + const installConfigs = new Map(); + + // get the current/installed version for the ports + for ( + const installProv of installProvisions + ) { + const installId = installProv.instId; + const install = await db.get(installId); + + if (!install) { + throw new Error("InstallId not found in InstallsDb", { + cause: { + installId, + }, + }); + } + + const manifest = install.manifest; + const config = install.conf; + + const resolvedResolutionDeps = [] as [string, string][]; + for (const dep of manifest.resolutionDeps ?? []) { + const { manifest: depManifest, config: depConf } = getDepConfig( + allowedDeps, + manifest, + config, + dep, + ); + + // TODO: avoid reinstall, infact just do a resolve + const depInstId = await resolveAndInstall( + scx, + allowedDeps, + depManifest, + depConf, + ); + resolvedResolutionDeps.push([depInstId.installId, depManifest.name]); + } + + const depShimsRootPath = await Deno.makeTempDir({ + dir: scx.tmpPath, + prefix: `shims_resDeps_${manifest.name}_`, + }); + const resolutionDepArts = await getShimmedDepArts( + scx, + depShimsRootPath, + resolvedResolutionDeps, + ); + + const port = getPortImpl(manifest); + const listAllArgs = { + depArts: resolutionDepArts, + config, + manifest, + }; + + // get the current Version + const currentVersion = config.version; + installedPortsVersions.set(installId, currentVersion); + + // get the latest version of the port + const latestStable = await port.latestStable(listAllArgs); + latestPortsVersions.set(installId, latestStable); + + installConfigs.set(installId, config); + + await $.removeIfExists(depShimsRootPath); + } + + return { + installedPortsVersions: installedPortsVersions, + latestPortsVersions: latestPortsVersions, + installConfigs: installConfigs, + }; +} diff --git a/modules/ports/reducers.ts b/modules/ports/reducers.ts index 9cbf58e2..214d211b 100644 --- a/modules/ports/reducers.ts +++ b/modules/ports/reducers.ts @@ -1,7 +1,7 @@ //! Integration between Ports and Envs module import { expandGlobsAndAbsolutize, unwrapParseRes } from "../../utils/mod.ts"; -import type { WellKnownProvision } from "../envs/types.ts"; +import { Provision } from "../envs/types.ts"; import { GhjkCtx } from "../types.ts"; // NOTE: mod.ts must always be a type import import type { PortsCtx } from "./mod.ts"; @@ -16,7 +16,7 @@ import type { InstallSetProvision, InstallSetRefProvision, } from "./types.ts"; -import validators from "./types.ts"; +import validators, { installProvisionTy } from "./types.ts"; export function installSetReducer(gcx: GhjkCtx) { return async (provisions: InstallSetProvision[]) => { @@ -64,7 +64,7 @@ async function reduceInstArts( installGraph: InstallGraph, installArts: Map, ) { - const out: WellKnownProvision[] = []; + const out = [] as Provision[]; // use this to track seen env vars to report conflicts const foundEnvVars: Record = {}; @@ -75,6 +75,7 @@ async function reduceInstArts( .get( instId, )!; + out.push({ ty: installProvisionTy, instId }); for (const [key, val] of Object.entries(env)) { const conflict = foundEnvVars[key]; diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index a54ad42d..9c565143 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -2,6 +2,7 @@ import { deep_eql, std_fs, std_path, zod } from "../../deps/cli.ts"; import getLogger from "../../utils/logger.ts"; import validators from "./types.ts"; import type { + AllowedPortDep, AmbientAccessPortManifestX, DenoWorkerPortManifestX, DepArts, @@ -337,7 +338,7 @@ export async function buildInstallGraph( }); const resolvedConfig = await resolveConfig( scx, - set, + set.allowedDeps, manifest, instLite, ); @@ -460,7 +461,7 @@ export async function buildInstallGraph( // It also resolves any dependencies that the config specifies function resolveConfig( scx: SyncCtx, - set: InstallSetX, + allowedDeps: Record, manifest: PortManifestX, config: InstallConfigLiteX, ) { @@ -477,7 +478,7 @@ function resolveConfig( const resolvedResolutionDeps = [] as [string, string][]; for (const dep of manifest.resolutionDeps ?? []) { const { manifest: depMan, config: depConf } = getDepConfig( - set, + allowedDeps, manifest, config, dep, @@ -487,7 +488,7 @@ function resolveConfig( // get the version resolved config of the dependency const depInstId = await resolveAndInstall( scx, - set, + allowedDeps, depMan, depConf, ); @@ -504,7 +505,7 @@ function resolveConfig( resolvedResolutionDeps, ); - // finally resolve the versino + // finally resolve the version let version; // TODO: fuzzy matching const port = getPortImpl(manifest); @@ -540,7 +541,7 @@ function resolveConfig( const buildDepConfigs = {} as Record; for (const dep of manifest.buildDeps ?? []) { const { manifest: depMan, config: depConf } = getDepConfig( - set, + allowedDeps, manifest, config, dep, @@ -548,7 +549,7 @@ function resolveConfig( // get the version resolved config of the dependency const depInstall = await resolveConfig( scx, - set, + allowedDeps, depMan, depConf, ); @@ -558,6 +559,7 @@ function resolveConfig( return validators.installConfigResolved.parse({ ...config, version, + specifiedVersion: !!config.version, buildDepConfigs, }); } @@ -567,15 +569,15 @@ function resolveConfig( // config.depPorts[depId] or the default InstallConfig specified // for the portsConfig.allowedDeps // No version resolution takes place -function getDepConfig( - set: InstallSetX, +export function getDepConfig( + allowedDeps: Record, manifest: PortManifestX, config: InstallConfigLiteX, depId: PortDep, resolutionDep = false, ) { const { manifest: depPort, defaultInst: defaultDepInstall } = - set.allowedDeps[depId.name]; + allowedDeps[depId.name]; if (!depPort) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, @@ -613,13 +615,13 @@ function getDepConfig( // FIXME: the usage of this function implies that resolution // will be redone if a config specfied by different resolutionDeps // TODO: consider introducing a memoization scheme -async function resolveAndInstall( +export async function resolveAndInstall( scx: SyncCtx, - set: InstallSetX, + allowedDeps: Record, manifest: PortManifestX, configLite: InstallConfigLiteX, ) { - const config = await resolveConfig(scx, set, manifest, configLite); + const config = await resolveConfig(scx, allowedDeps, manifest, configLite); const installId = getInstallHash(config); const cached = await scx.db.val.get(installId); @@ -639,11 +641,11 @@ async function resolveAndInstall( await Promise.all( manifest.buildDeps?.map( async (dep) => { - const depConfig = getDepConfig(set, manifest, config, dep); + const depConfig = getDepConfig(allowedDeps, manifest, config, dep); // we not only resolve but install the dep here const { installId } = await resolveAndInstall( scx, - set, + allowedDeps, depConfig.manifest, depConfig.config, ); @@ -711,7 +713,7 @@ async function resolveAndInstall( } // This assumes that the installs are already in the db -async function getShimmedDepArts( +export async function getShimmedDepArts( scx: SyncCtx, shimsRootPath: string, installs: [string, string][], @@ -835,7 +837,7 @@ type DownloadStageArgs = { depArts: DepArts; }; -async function doDownloadStage( +export async function doDownloadStage( { installId, installPath, @@ -884,7 +886,7 @@ async function doDownloadStage( type InstallStageArgs = DownloadStageArgs; -async function doInstallStage( +export async function doInstallStage( { installId, installPath, diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 368cae6a..922d8d65 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -117,6 +117,7 @@ const installConfigFat = stdInstallConfigFat; const installConfigResolved = installConfigLite.merge(zod.object({ // NOTE: version is no longer nullish version: zod.string(), + versionSpecified: zod.boolean().optional(), // buildDepConfigs: zod.record( // portName, // // FIXME: figure out cyclically putting `installConfigResolved` here @@ -178,6 +179,12 @@ const installSetRefProvision = zod.object({ setId: zod.string(), }); +export const installProvisionTy = "ghjk.ports.Install"; +export const installProvision = zod.object({ + ty: zod.literal(installProvisionTy), + instId: zod.string(), +}); + const downloadArtifacts = zod.object({ installVersion: zod.string(), downloadPath: zod.string(), @@ -220,6 +227,7 @@ const validators = { allowDepSetHashed, installSetProvision, installSetRefProvision, + installProvision, installSet, installSetHashed, string: zod.string(), @@ -322,6 +330,8 @@ export type InstallSetProvisionX = zod.infer< typeof validators.installSetProvision >; +export type InstallProvision = zod.infer; + /* * Provisions an [`InstallSet`] that's been pre-defined in the [`PortsModuleConfigX`]. */ diff --git a/modules/ports/utils.ts b/modules/ports/utils.ts new file mode 100644 index 00000000..73bc452d --- /dev/null +++ b/modules/ports/utils.ts @@ -0,0 +1,140 @@ +import { std_path } from "../../deps/cli.ts"; +import logger from "../../utils/logger.ts"; +import { $ } from "../../utils/mod.ts"; +import { GhjkCtx } from "../types.ts"; +import { AllowedPortDep } from "./mod.ts"; +import { + doDownloadStage, + doInstallStage, + getDepConfig, + getShimmedDepArts, + resolveAndInstall, + SyncCtx, + syncCtxFromGhjk, +} from "./sync.ts"; +import { InstallConfigResolvedX, PortManifestX } from "./types.ts"; + +export async function updateInstall( + gcx: GhjkCtx, + installId: string, + newVersion: string, + allowedDeps: Record, +) { + await using scx = await syncCtxFromGhjk(gcx); + + const db = scx.db.val; + + const install = await db.get(installId); + + if (!install) { + throw new Error("InstallSetId not found in InstallsDb", { + cause: { + installId, + }, + }); + } + + const config = install.conf; + + if (config.version === newVersion) { + logger().info("Skipping update. Install is already uptodate"); + return; + } + + // it's a user specified install, so skip + if (config.specifiedVersion) { + logger().info(`Skipping Version Specified Install: ${installId}`); + return; + } + + config.version = newVersion; + logger().info(`Updating installId ${installId} to version ${newVersion}...`); + await doInstall(installId, scx, install.manifest, allowedDeps, config); + logger().info( + `Successfully updated installId ${installId} to version ${newVersion}`, + ); +} + +async function doInstall( + installId: string, + scx: SyncCtx, + manifest: PortManifestX, + allowedDeps: Record, + config: InstallConfigResolvedX, +) { + const depShimsRootPath = await Deno.makeTempDir({ + dir: scx.tmpPath, + prefix: `shims_${installId}`, + }); + + // readies all the exports of the port's deps including + // shims for their exports + const totalDepArts = await getShimmedDepArts( + scx, + depShimsRootPath, + await Promise.all( + manifest.buildDeps?.map( + async (dep) => { + const depConfig = getDepConfig(allowedDeps, manifest, config, dep); + // we not only resolve but install the dep here + const { installId } = await resolveAndInstall( + scx, + allowedDeps, + depConfig.manifest, + depConfig.config, + ); + return [installId, dep.name]; + }, + ) ?? [], + ), + ); + + const stageArgs = { + installId, + installPath: std_path.resolve(scx.installsPath, installId), + downloadPath: std_path.resolve(scx.downloadsPath, installId), + tmpPath: scx.tmpPath, + config: config, + manifest, + depArts: totalDepArts, + }; + + const dbRow = { + installId, + conf: config, + manifest, + }; + let downloadArts; + + try { + downloadArts = await doDownloadStage({ + ...stageArgs, + }); + } catch (err) { + throw new Error(`error downloading ${installId}`, { cause: err }); + } + await scx.db.val.set(installId, { + ...dbRow, + progress: "downloaded", + downloadArts, + }); + + let installArtifacts; + try { + installArtifacts = await doInstallStage( + { + ...stageArgs, + ...downloadArts, + }, + ); + } catch (err) { + throw new Error(`error installing ${installId}`, { cause: err }); + } + await scx.db.val.set(installId, { + ...dbRow, + progress: "installed", + downloadArts, + installArts: installArtifacts, + }); + await $.removeIfExists(depShimsRootPath); +} diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index fd642bd5..3ca91a15 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -5,11 +5,12 @@ import { Json, unwrapParseRes } from "../../utils/mod.ts"; import validators from "./types.ts"; import type { TasksModuleConfigX } from "./types.ts"; -import type { GhjkCtx, ModuleManifest } from "../types.ts"; +import { type GhjkCtx, type ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import { buildTaskGraph, execTask, type TaskGraph } from "./exec.ts"; import { Blackboard } from "../../host/types.ts"; +import { getTasksCtx } from "../utils.ts"; export type TasksCtx = { config: TasksModuleConfigX; @@ -40,10 +41,12 @@ export class TasksModule extends ModuleBase { ); const taskGraph = buildTaskGraph(gcx, config); - return { - config, - taskGraph, - }; + + const tasksCtx = getTasksCtx(gcx); + tasksCtx.config = config; + tasksCtx.taskGraph = taskGraph; + + return tasksCtx; } commands( diff --git a/modules/types.ts b/modules/types.ts index 03a6752a..8ed5f4cd 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -4,6 +4,10 @@ import type { Path } from "../utils/mod.ts"; // TODO: better module ident/versioning const moduleId = zod.string().regex(/[^ @]*/); +export const envsCtxBlackboardKey = "ctx.envs"; +export const portsCtxBlackboardKey = "ctx.ports"; +export const tasksCtxBlackboardKey = "ctx.tasks"; + const moduleManifest = zod.object({ id: moduleId, config: zod.unknown(), diff --git a/modules/utils.ts b/modules/utils.ts new file mode 100644 index 00000000..49b18f56 --- /dev/null +++ b/modules/utils.ts @@ -0,0 +1,93 @@ +import { EnvsCtx } from "./envs/mod.ts"; +import { PortsCtx } from "./ports/mod.ts"; +import { + InstallSetRefProvision, + installSetRefProvisionTy, +} from "./ports/types.ts"; +import { TasksCtx } from "./tasks/mod.ts"; +import { + envsCtxBlackboardKey, + GhjkCtx, + portsCtxBlackboardKey, + tasksCtxBlackboardKey, +} from "./types.ts"; + +export function getEnvsCtx( + gcx: GhjkCtx, +): EnvsCtx { + let envsCtx = gcx.blackboard.get(envsCtxBlackboardKey) as + | EnvsCtx + | undefined; + + if (!envsCtx) { + envsCtx = { + activeEnv: "", + config: { + defaultEnv: "", + envs: {}, + }, + }; + gcx.blackboard.set(envsCtxBlackboardKey, envsCtx); + } + + return envsCtx; +} + +export function getPortsCtx( + gcx: GhjkCtx, +): PortsCtx { + let portsCtx = gcx.blackboard.get(portsCtxBlackboardKey) as + | PortsCtx + | undefined; + + if (!portsCtx) { + portsCtx = { + config: { + sets: {}, + }, + }; + gcx.blackboard.set(portsCtxBlackboardKey, portsCtx); + } + + return portsCtx; +} + +export function getTasksCtx( + gcx: GhjkCtx, +): TasksCtx { + let tasksCtx = gcx.blackboard.get(tasksCtxBlackboardKey) as + | TasksCtx + | undefined; + + if (!tasksCtx) { + tasksCtx = { + config: { + envs: {}, + tasks: {}, + tasksNamed: [], + }, + taskGraph: { + indie: [], + revDepEdges: {}, + depEdges: {}, + }, + }; + gcx.blackboard.set(tasksCtxBlackboardKey, tasksCtx); + } + + return tasksCtx; +} + +export function getActiveEnvInstallSetId(envsCtx: EnvsCtx): string { + const activeEnvName = envsCtx.activeEnv; + const activeEnv = envsCtx.config.envs[activeEnvName]; + if (!activeEnv) { + throw new Error(`No env found under given name "${activeEnvName}"`); + } + + const instSetRef = activeEnv.provides.filter((prov) => + prov.ty === installSetRefProvisionTy + )[0] as InstallSetRefProvision; + + return instSetRef.setId; +} diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts index 40c8ea6a..1be633b8 100644 --- a/ports/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -79,9 +79,12 @@ export class Port extends GithubReleasePort { if (await installPath.exists()) { await installPath.remove({ recursive: true }); } - await std_fs.copy( - args.tmpDirPath, - std_path.resolve(args.installPath, "bin"), - ); + + const neededFileNames = ["cargo-binstall", "detect-targets", "detect-wasi"]; + const destination = std_path.resolve(args.installPath, "bin"); + for (const fileName of neededFileNames) { + const sourceFile = std_path.resolve(args.tmpDirPath, fileName); + await std_fs.copy(sourceFile, destination); + } } } diff --git a/tests/modules/ports/portsOutdatedTest.ts b/tests/modules/ports/portsOutdatedTest.ts new file mode 100644 index 00000000..bc28e5f6 --- /dev/null +++ b/tests/modules/ports/portsOutdatedTest.ts @@ -0,0 +1,75 @@ +import "../../../setup_logger.ts"; +import { DenoFileSecureConfig, stdSecureConfig } from "../../../mod.ts"; +import { E2eTestCase, genTsGhjkFile, harness } from "../../utils.ts"; +import * as ports from "../../../ports/mod.ts"; +import type { InstallConfigFat } from "../../../modules/ports/types.ts"; + +type CustomE2eTestCase = Omit & { + ePoint: string; + installConf: InstallConfigFat | InstallConfigFat[]; + secureConf?: DenoFileSecureConfig; +}; + +const cases: CustomE2eTestCase[] = [ + // 0 megs + { + name: "check ports outdated", + installConf: [ + ports.jq_ghrel(), + ports.protoc(), + ports.ruff(), + ...ports.npmi({ packageName: "node-gyp" }), + ports.earthly(), + ...ports.pipi({ packageName: "poetry" }), + ], + ePoint: `ghjk p outdated`, + secureConf: stdSecureConfig({ + enableRuntimes: true, + }), + }, + { + name: "check ports outdated", + installConf: [ + ports.jq_ghrel(), + ports.protoc(), + ports.ruff(), + ...ports.npmi({ packageName: "node-gyp" }), + ports.earthly(), + ...ports.pipi({ packageName: "poetry" }), + ], + ePoint: `ghjk p outdated --update-all`, + secureConf: stdSecureConfig({ + enableRuntimes: true, + }), + }, +]; + +harness(cases.map((testCase) => ({ + ...testCase, + tsGhjkfileStr: genTsGhjkFile( + { + installConf: testCase.installConf, + secureConf: testCase.secureConf, + taskDefs: [], + }, + ), + ePoints: [ + ...["bash -c", "fish -c", "zsh -c"].map((sh) => ({ + cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], + })), + /* // FIXME: better tests for the `InstallDb` + // installs db means this shouldn't take too long + // as it's the second sync + { + cmd: [ + ..."env".split(" "), + "bash -c 'timeout 1 ghjk envs cook'", + ], + }, */ + ], + // building the test docker image might taka a while + // but we don't want some bug spinlocking the ci for + // an hour + timeout_ms: 5 * 60 * 1000, + name: `ports/${testCase.name}`, +}))); diff --git a/tests/ports.ts b/tests/ports.ts index 93605ec6..0a312dd7 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -159,9 +159,6 @@ const cases: CustomE2eTestCase[] = [ name: "pipi-poetry", installConf: ports.pipi({ packageName: "poetry" }), ePoint: `poetry --version`, - secureConf: stdSecureConfig({ - enableRuntimes: true, - }), }, // rustup + 600 megs { From 0c5f78519c7c8e90daad64ed9a984a1c703c78c3 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:30:20 +0300 Subject: [PATCH 15/21] refactor(ghjk.ts): replace `secureConfig` with `hack.ts` (#87) * refactor(ghjk.ts): replace secureConfig with hack.ts * docs: improve README * fix(ci): update action job * fix: address llm feedback * fix: cargo-binstall bug * fix: ports outdated test fix --- .ghjk/deno.lock | 643 ------------------ .ghjk/lock.json | 107 +-- .github/workflows/tests.yml | 4 +- README.md | 106 +-- check.ts | 1 + deno.jsonc | 1 + examples/cmake/ghjk.ts | 11 - examples/envs/ghjk.ts | 31 + examples/kitchen/ghjk.ts | 107 +++ examples/many_installs/ghjk.ts | 37 +- examples/protoc/ghjk.ts | 7 - examples/tasks/ghjk.ts | 10 +- files/deno/worker.ts | 2 +- files/mod.ts | 183 ++--- ghjk.ts | 22 +- hack.ts | 84 +++ mod.ts | 377 ++++++---- modules/ports/mod.ts | 6 +- modules/ports/sync.ts | 12 +- modules/ports/types.ts | 2 +- modules/tasks/deno.ts | 2 +- ports/cargo-binstall.ts | 17 +- tests/envHooks.ts | 8 +- tests/envs.ts | 14 +- tests/ports.ts | 40 +- .../{modules/ports => }/portsOutdatedTest.ts | 36 +- tests/reloadHooks.ts | 24 +- tests/tasks.ts | 15 +- tests/utils.ts | 70 +- utils/mod.ts | 13 + 30 files changed, 804 insertions(+), 1188 deletions(-) delete mode 100644 .ghjk/deno.lock delete mode 100644 examples/cmake/ghjk.ts create mode 100644 examples/envs/ghjk.ts create mode 100644 examples/kitchen/ghjk.ts delete mode 100644 examples/protoc/ghjk.ts create mode 100644 hack.ts rename tests/{modules/ports => }/portsOutdatedTest.ts (70%) diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock deleted file mode 100644 index 579f4faf..00000000 --- a/.ghjk/deno.lock +++ /dev/null @@ -1,643 +0,0 @@ -{ - "version": "3", - "packages": { - "specifiers": { - "jsr:@david/dax@0.41.0": "jsr:@david/dax@0.41.0", - "jsr:@david/which@0.3": "jsr:@david/which@0.3.0", - "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", - "jsr:@ghjk/dax@0.40.2-alpha-ghjk": "jsr:@ghjk/dax@0.40.2-alpha-ghjk", - "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", - "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", - "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", - "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", - "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", - "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", - "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", - "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", - "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", - "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.3.0": "npm:zod-validation-error@3.3.0_zod@3.22.4" - }, - "jsr": { - "@david/dax@0.41.0": { - "integrity": "9e1ecf66a0415962cc8ad3ba4e3fa93ce0f1a1cc797dd95c36fdfb6977dc7fc8", - "dependencies": [ - "jsr:@david/which@^0.4.1", - "jsr:@std/fmt@^0.221.0", - "jsr:@std/fs@0.221.0", - "jsr:@std/io@0.221.0", - "jsr:@std/path@0.221.0", - "jsr:@std/streams@0.221.0" - ] - }, - "@david/which@0.3.0": { - "integrity": "6bdb62c40ac90edcf328e854fa8103a8db21e7c326089cbe3c3a1cf7887d3204" - }, - "@david/which@0.4.1": { - "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" - }, - "@ghjk/dax@0.40.2-alpha-ghjk": { - "integrity": "87bc93e9947779cb2f3922fe277e21ea8c716de804b2627f80ba9e7bc3d0d019", - "dependencies": [ - "jsr:@david/which@0.3", - "jsr:@std/fmt@^0.221.0", - "jsr:@std/fs@0.221.0", - "jsr:@std/io@0.221.0", - "jsr:@std/path@0.221.0", - "jsr:@std/streams@0.221.0" - ] - }, - "@std/assert@0.221.0": { - "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" - }, - "@std/bytes@0.221.0": { - "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" - }, - "@std/fmt@0.221.0": { - "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" - }, - "@std/fs@0.221.0": { - "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", - "dependencies": [ - "jsr:@std/assert@^0.221.0", - "jsr:@std/path@^0.221.0" - ] - }, - "@std/io@0.221.0": { - "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", - "dependencies": [ - "jsr:@std/assert@^0.221.0", - "jsr:@std/bytes@^0.221.0" - ] - }, - "@std/path@0.221.0": { - "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", - "dependencies": [ - "jsr:@std/assert@^0.221.0" - ] - }, - "@std/streams@0.221.0": { - "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", - "dependencies": [ - "jsr:@std/io@^0.221.0" - ] - } - }, - "npm": { - "@noble/hashes@1.4.0": { - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dependencies": {} - }, - "multiformats@13.1.0": { - "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==", - "dependencies": {} - }, - "zod-validation-error@3.1.0_zod@3.22.4": { - "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", - "dependencies": { - "zod": "zod@3.22.4" - } - }, - "zod-validation-error@3.2.0_zod@3.22.4": { - "integrity": "sha512-cYlPR6zuyrgmu2wRTdumEAJGuwI7eHVHGT+VyneAQxmRAKtGRL1/7pjz4wfLhz4J05f5qoSZc3rGacswgyTjjw==", - "dependencies": { - "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": {} - } - } - }, - "remote": { - "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", - "https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", - "https://deno.land/std@0.116.0/fs/walk.ts": "31464d75099aa3fc7764212576a8772dfabb2692783e6eabb910f874a26eac54", - "https://deno.land/std@0.116.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", - "https://deno.land/std@0.116.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", - "https://deno.land/std@0.116.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", - "https://deno.land/std@0.116.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", - "https://deno.land/std@0.116.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820", - "https://deno.land/std@0.116.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", - "https://deno.land/std@0.116.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", - "https://deno.land/std@0.116.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", - "https://deno.land/std@0.116.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", - "https://deno.land/std@0.120.0/_wasm_crypto/crypto.js": "5c283a80e1059d16589b79fa026be5fb0a28424302a99487cadceef8c17f8afa", - "https://deno.land/std@0.120.0/_wasm_crypto/crypto.wasm.js": "0e6df3c18beb1187b442ec7f0a03df4d18b21212172d6b4a50ee4816404771d7", - "https://deno.land/std@0.120.0/_wasm_crypto/mod.ts": "7d02009ef3ddc953c8f90561d213e02fa0a6f3eaed9b8baf0c241c8cbeec1ed3", - "https://deno.land/std@0.120.0/crypto/mod.ts": "5760510eaa0b250f78cce81ce92d83cf8c40e9bb3c3efeedd4ef1a5bb0801ef4", - "https://deno.land/std@0.120.0/encoding/ascii85.ts": "b42b041e9c668afa356dd07ccf69a6b3ee49b9ae080fdf3b03f0ac3981f4d1e6", - "https://deno.land/std@0.120.0/encoding/base64.ts": "0b58bd6477214838bf711eef43eac21e47ba9e5c81b2ce185fe25d9ecab3ebb3", - "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", - "https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7", - "https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9", - "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", - "https://deno.land/std@0.213.0/archive/_common.ts": "85edd5cdd4324833f613c1bc055f8e2f935cc9229c6b3044421268d9959997ef", - "https://deno.land/std@0.213.0/archive/untar.ts": "7677c136f2188cd8c33363ccaaee6e77d4ca656cca3e2093d08de8f294d4353d", - "https://deno.land/std@0.213.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", - "https://deno.land/std@0.213.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", - "https://deno.land/std@0.213.0/bytes/concat.ts": "9cac3b4376afbef98ff03588eb3cf948e0d1eb6c27cfe81a7651ab6dd3adc54a", - "https://deno.land/std@0.213.0/bytes/copy.ts": "f29c03168853720dfe82eaa57793d0b9e3543ebfe5306684182f0f1e3bfd422a", - "https://deno.land/std@0.213.0/fmt/colors.ts": "aeaee795471b56fc62a3cb2e174ed33e91551b535f44677f6320336aabb54fbb", - "https://deno.land/std@0.213.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", - "https://deno.land/std@0.213.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", - "https://deno.land/std@0.213.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", - "https://deno.land/std@0.213.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", - "https://deno.land/std@0.213.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", - "https://deno.land/std@0.213.0/fs/copy.ts": "dc0f68c4b6c3b090bfdb909387e309f6169b746bd713927c9507c9ef545d71f6", - "https://deno.land/std@0.213.0/fs/empty_dir.ts": "4f01e6d56e2aa8d90ad60f20bc25601f516b00f6c3044cdf6863a058791d91aa", - "https://deno.land/std@0.213.0/fs/ensure_dir.ts": "dffff68de0d10799b5aa9e39dec4e327e12bbd29e762292193684542648c4aeb", - "https://deno.land/std@0.213.0/fs/ensure_file.ts": "ac5cfde94786b0284d2c8e9f7f9425269bea1b2140612b4aea1f20b508870f59", - "https://deno.land/std@0.213.0/fs/ensure_link.ts": "d42af2edefeaa9817873ec6e46dc5d209ac4d744f8c69c5ecc2dffade78465b6", - "https://deno.land/std@0.213.0/fs/ensure_symlink.ts": "aee3f1655700f60090b4a3037f5b6c07ab37c36807cccad746ce89987719e6d2", - "https://deno.land/std@0.213.0/fs/eol.ts": "c9807291f78361d49fd986a9be04654610c615c5e2ec63d748976197d30ff206", - "https://deno.land/std@0.213.0/fs/exists.ts": "d2757ef764eaf5c6c5af7228e8447db2de42ab084a2dae540097f905723d83f5", - "https://deno.land/std@0.213.0/fs/expand_glob.ts": "a64e4ab51f62780f764789c9cdeacc862d221e35207fb81170a980ccc22868e3", - "https://deno.land/std@0.213.0/fs/mod.ts": "107f5afa4424c2d3ce2f7e9266173198da30302c69af662c720115fe504dc5ee", - "https://deno.land/std@0.213.0/fs/move.ts": "39e0d7ccb88a566d20b949712020e766b15ef1ec19159573d11f949bd677909c", - "https://deno.land/std@0.213.0/fs/walk.ts": "f04cc83ad3b27b5a5d078c831a01c7406069474bf280d5db015d937149a60128", - "https://deno.land/std@0.213.0/internal/warn_on_deprecated_api.ts": "0708590b803a3c4462bbd89ee8b9a1b3fe941a7679ee3cfc332227a69b5c36f1", - "https://deno.land/std@0.213.0/io/_common.ts": "36705cdb4dfcd338d6131bca1b16e48a4d5bf0d1dada6ce397268e88c17a5835", - "https://deno.land/std@0.213.0/io/_constants.ts": "3c7ad4695832e6e4a32e35f218c70376b62bc78621ef069a4a0a3d55739f8856", - "https://deno.land/std@0.213.0/io/buf_reader.ts": "ccbd43ace0a9eebbd5e1b4765724b79da79d1e28b90c2b08537b99192da4a1f7", - "https://deno.land/std@0.213.0/io/buf_writer.ts": "bf68b9c74b1bccf51b9960c54db5eec60e7e3d922c7c62781b0d3971770021ba", - "https://deno.land/std@0.213.0/io/buffer.ts": "79182995c8340ece2fa8763a8da86d282c507e854921d0a4c2ba7425c63609ef", - "https://deno.land/std@0.213.0/io/copy.ts": "63c6a4acf71fb1e89f5e47a7b3b2972f9d2c56dd645560975ead72db7eb23f61", - "https://deno.land/std@0.213.0/io/copy_n.ts": "e4a169b8965b69e6a05175d06bf14565caa91266143ec895e54e95b6cdb27cf2", - "https://deno.land/std@0.213.0/io/limited_reader.ts": "2b3e6c2d134bbbabbc918584db5fd2f8b21091843357f75af0d9f262cb5c94c1", - "https://deno.land/std@0.213.0/io/mod.ts": "571384032c5f60530542a28f2e8b0e73e47e87eca77056ba7e2363f4d4a4573a", - "https://deno.land/std@0.213.0/io/multi_reader.ts": "ca8a7813208a3393dfaed75894d955fe58a38c21b880e69839a4e0547eadbf61", - "https://deno.land/std@0.213.0/io/read_all.ts": "876c1cb20adea15349c72afc86cecd3573335845ae778967aefb5e55fe5a8a4a", - "https://deno.land/std@0.213.0/io/read_delim.ts": "fb0884d97adc398877c6f59e1d1450be12e078790f52845fae7876dc119bb8f6", - "https://deno.land/std@0.213.0/io/read_int.ts": "6ada4e0eec5044982df530e4de804e32ae757a2c318b57eba622d893841ffe2a", - "https://deno.land/std@0.213.0/io/read_lines.ts": "34555eaa25269f6cfb9a842a03daedc9eae4f8295c8f933bd2b1639274ce89e3", - "https://deno.land/std@0.213.0/io/read_long.ts": "199cba44526464f8499e1f3d96008d513bcadc8e5665356a9b84425cac6b16ad", - "https://deno.land/std@0.213.0/io/read_range.ts": "a0c930ea61fdc3ea5520be4df34a7927fe8a2d6da9b04bfaa7b9588ef2e1a718", - "https://deno.land/std@0.213.0/io/read_short.ts": "73777709ad41b6faeff3638c275a329cc820c1082f4dad07909f48875a35a71d", - "https://deno.land/std@0.213.0/io/read_string_delim.ts": "8c604ceea5c3c7ab244583570b467ce194238ace6d49b1d47f25d4f75de86d59", - "https://deno.land/std@0.213.0/io/slice_long_to_bytes.ts": "9769174a8f3b4449f1e1af1a79f78e58ef84d0aaf2f457e1fdc31a01f92439b7", - "https://deno.land/std@0.213.0/io/string_reader.ts": "b0176211e61e235a684abef722e7ecc7a6481238ba264f1a7b199b8a1d2a62f5", - "https://deno.land/std@0.213.0/io/string_writer.ts": "4fe4dcbdadff11c726bf79b0239e14fa9b1e8468a795b465622e4dbd6c1f819c", - "https://deno.land/std@0.213.0/io/to_readable_stream.ts": "ed03a44a1ec1cc55a85a857acf6cac472035298f6f3b6207ea209f93b4aefb39", - "https://deno.land/std@0.213.0/io/to_writable_stream.ts": "ef422e0425963c8a1e0481674e66c3023da50f0acbe5ef51ec9789efc3c1e2ed", - "https://deno.land/std@0.213.0/io/types.ts": "748bbb3ac96abda03594ef5a0db15ce5450dcc6c0d841c8906f8b10ac8d32c96", - "https://deno.land/std@0.213.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038", - "https://deno.land/std@0.213.0/log/base_handler.ts": "924b370558d561f4e728295ebcb392224e36061542c7ad2e5b48c8e29614c27f", - "https://deno.land/std@0.213.0/log/console_handler.ts": "75653acd6932fb97c7121f63336b39de3f072e329874d66f05abcb2a7f514558", - "https://deno.land/std@0.213.0/log/file_handler.ts": "7b58c7017117ae290700b0e23f21573c1dc8ba8b5d4978d8aa0b8e05742d75e2", - "https://deno.land/std@0.213.0/log/formatters.ts": "5491ac778cf404a9379025bef33565644eb10b6a1f8083aba2595887d49edf15", - "https://deno.land/std@0.213.0/log/handlers.ts": "ff5b5d8293ca5d452acfb2e7c214f527ad953aaab4036219b818a3b859944b08", - "https://deno.land/std@0.213.0/log/levels.ts": "3746b311bc5cd28340fe7b563002f94508ace565592e9f4730f8b07916f189a6", - "https://deno.land/std@0.213.0/log/logger.ts": "32ad896c88182ee9cbe2a579afb09b48ed642a2bfaa3f3b2fb8009314ab18855", - "https://deno.land/std@0.213.0/log/mod.ts": "d8a8ebca268767a610d686d7f3c5c97096121ff3bc7ebec71ab7dd73cc52231b", - "https://deno.land/std@0.213.0/log/rotating_file_handler.ts": "dc0333959ff725ac1f43b741ababa9fef074f658a60c65235d12e44952085fbe", - "https://deno.land/std@0.213.0/path/_common/assert_path.ts": "2ca275f36ac1788b2acb60fb2b79cb06027198bc2ba6fb7e163efaedde98c297", - "https://deno.land/std@0.213.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", - "https://deno.land/std@0.213.0/path/_common/common.ts": "6157c7ec1f4db2b4a9a187efd6ce76dcaf1e61cfd49f87e40d4ea102818df031", - "https://deno.land/std@0.213.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", - "https://deno.land/std@0.213.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.213.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", - "https://deno.land/std@0.213.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", - "https://deno.land/std@0.213.0/path/_common/glob_to_reg_exp.ts": "2007aa87bed6eb2c8ae8381adcc3125027543d9ec347713c1ad2c68427330770", - "https://deno.land/std@0.213.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.213.0/path/_common/normalize_string.ts": "dfdf657a1b1a7db7999f7c575ee7e6b0551d9c20f19486c6c3f5ff428384c965", - "https://deno.land/std@0.213.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", - "https://deno.land/std@0.213.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", - "https://deno.land/std@0.213.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", - "https://deno.land/std@0.213.0/path/_interface.ts": "a1419fcf45c0ceb8acdccc94394e3e94f99e18cfd32d509aab514c8841799600", - "https://deno.land/std@0.213.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", - "https://deno.land/std@0.213.0/path/basename.ts": "5d341aadb7ada266e2280561692c165771d071c98746fcb66da928870cd47668", - "https://deno.land/std@0.213.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", - "https://deno.land/std@0.213.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", - "https://deno.land/std@0.213.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", - "https://deno.land/std@0.213.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", - "https://deno.land/std@0.213.0/path/format.ts": "98fad25f1af7b96a48efb5b67378fcc8ed77be895df8b9c733b86411632162af", - "https://deno.land/std@0.213.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", - "https://deno.land/std@0.213.0/path/glob.ts": "04510962905d4b1513b44da9cb195914e0fa46c24359f6feaca20848d797dcb0", - "https://deno.land/std@0.213.0/path/glob_to_regexp.ts": "83c5fd36a8c86f5e72df9d0f45317f9546afa2ce39acaafe079d43a865aced08", - "https://deno.land/std@0.213.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", - "https://deno.land/std@0.213.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", - "https://deno.land/std@0.213.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", - "https://deno.land/std@0.213.0/path/join_globs.ts": "e9589869a33dc3982101898ee50903db918ca00ad2614dbe3934d597d7b1fbea", - "https://deno.land/std@0.213.0/path/mod.ts": "ffeaccb713dbe6c72e015b7c767f753f8ec5fbc3b621ff5eeee486ffc2c0ddda", - "https://deno.land/std@0.213.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", - "https://deno.land/std@0.213.0/path/normalize_glob.ts": "98ee8268fad271193603271c203ae973280b5abfbdd2cbca1053fd2af71869ca", - "https://deno.land/std@0.213.0/path/parse.ts": "65e8e285f1a63b714e19ef24b68f56e76934c3df0b6e65fd440d3991f4f8aefb", - "https://deno.land/std@0.213.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", - "https://deno.land/std@0.213.0/path/posix/basename.ts": "39ee27a29f1f35935d3603ccf01d53f3d6e0c5d4d0f84421e65bd1afeff42843", - "https://deno.land/std@0.213.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.213.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", - "https://deno.land/std@0.213.0/path/posix/dirname.ts": "6535d2bdd566118963537b9dda8867ba9e2a361015540dc91f5afbb65c0cce8b", - "https://deno.land/std@0.213.0/path/posix/extname.ts": "8d36ae0082063c5e1191639699e6f77d3acf501600a3d87b74943f0ae5327427", - "https://deno.land/std@0.213.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", - "https://deno.land/std@0.213.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", - "https://deno.land/std@0.213.0/path/posix/glob_to_regexp.ts": "54d3ff40f309e3732ab6e5b19d7111d2d415248bcd35b67a99defcbc1972e697", - "https://deno.land/std@0.213.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", - "https://deno.land/std@0.213.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.213.0/path/posix/join.ts": "aef88d5fa3650f7516730865dbb951594d1a955b785e2450dbee93b8e32694f3", - "https://deno.land/std@0.213.0/path/posix/join_globs.ts": "ee2f4676c5b8a0dfa519da58b8ade4d1c4aa8dd3fe35619edec883ae9df1f8c9", - "https://deno.land/std@0.213.0/path/posix/mod.ts": "563a18c2b3ddc62f3e4a324ff0f583e819b8602a72ad880cb98c9e2e34f8db5b", - "https://deno.land/std@0.213.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", - "https://deno.land/std@0.213.0/path/posix/normalize_glob.ts": "65f0138fa518ef9ece354f32889783fc38cdf985fb02dcf1c3b14fa47d665640", - "https://deno.land/std@0.213.0/path/posix/parse.ts": "d5bac4eb21262ab168eead7e2196cb862940c84cee572eafedd12a0d34adc8fb", - "https://deno.land/std@0.213.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", - "https://deno.land/std@0.213.0/path/posix/resolve.ts": "bac20d9921beebbbb2b73706683b518b1d0c1b1da514140cee409e90d6b2913a", - "https://deno.land/std@0.213.0/path/posix/separator.ts": "c9ecae5c843170118156ac5d12dc53e9caf6a1a4c96fc8b1a0ab02dff5c847b0", - "https://deno.land/std@0.213.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", - "https://deno.land/std@0.213.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", - "https://deno.land/std@0.213.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", - "https://deno.land/std@0.213.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", - "https://deno.land/std@0.213.0/path/separator.ts": "c6c890507f944a1f5cb7d53b8d638d6ce3cf0f34609c8d84a10c1eaa400b77a9", - "https://deno.land/std@0.213.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", - "https://deno.land/std@0.213.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", - "https://deno.land/std@0.213.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", - "https://deno.land/std@0.213.0/path/windows/basename.ts": "e2dbf31d1d6385bfab1ce38c333aa290b6d7ae9e0ecb8234a654e583cf22f8fe", - "https://deno.land/std@0.213.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.213.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", - "https://deno.land/std@0.213.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", - "https://deno.land/std@0.213.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", - "https://deno.land/std@0.213.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", - "https://deno.land/std@0.213.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", - "https://deno.land/std@0.213.0/path/windows/glob_to_regexp.ts": "6dcd1242bd8907aa9660cbdd7c93446e6927b201112b0cba37ca5d80f81be51b", - "https://deno.land/std@0.213.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", - "https://deno.land/std@0.213.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.213.0/path/windows/join.ts": "e0b3356615c1a75c56ebb6a7311157911659e11fd533d80d724800126b761ac3", - "https://deno.land/std@0.213.0/path/windows/join_globs.ts": "ee2f4676c5b8a0dfa519da58b8ade4d1c4aa8dd3fe35619edec883ae9df1f8c9", - "https://deno.land/std@0.213.0/path/windows/mod.ts": "7d6062927bda47c47847ffb55d8f1a37b0383840aee5c7dfc93984005819689c", - "https://deno.land/std@0.213.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", - "https://deno.land/std@0.213.0/path/windows/normalize_glob.ts": "179c86ba89f4d3fe283d2addbe0607341f79ee9b1ae663abcfb3439db2e97810", - "https://deno.land/std@0.213.0/path/windows/parse.ts": "b9239edd892a06a06625c1b58425e199f018ce5649ace024d144495c984da734", - "https://deno.land/std@0.213.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", - "https://deno.land/std@0.213.0/path/windows/resolve.ts": "75b2e3e1238d840782cee3d8864d82bfaa593c7af8b22f19c6422cf82f330ab3", - "https://deno.land/std@0.213.0/path/windows/separator.ts": "e51c5522140eff4f8402617c5c68a201fdfa3a1a8b28dc23587cff931b665e43", - "https://deno.land/std@0.213.0/path/windows/to_file_url.ts": "1cd63fd35ec8d1370feaa4752eccc4cc05ea5362a878be8dc7db733650995484", - "https://deno.land/std@0.213.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", - "https://deno.land/std@0.213.0/semver/_constants.ts": "90879e4ea94a34c49c8ecea3d9c06e13e61ecb7caca80c8f289139440ca9835a", - "https://deno.land/std@0.213.0/semver/_shared.ts": "8d44684775cde4a64bd6bdb99b51f3bc59ed65f10af78ca136cc2eab3f6716f1", - "https://deno.land/std@0.213.0/semver/comparator_format.ts": "41f00b1275923317fa3f7c39df58fa7f62f541b0f231134c1655bc8a42965393", - "https://deno.land/std@0.213.0/semver/comparator_intersects.ts": "dc231c5ebded8e88b8355a33edfbd228e36a08384848d73c15d394833384ee8f", - "https://deno.land/std@0.213.0/semver/comparator_max.ts": "2038cded7cce886e2c81926acb97f625908707f2d66864b603493b9674e2bd58", - "https://deno.land/std@0.213.0/semver/comparator_min.ts": "453d3e449aaee4d59acc9b36fe77eddfcb0c4097ffe7efe11eb2a04a64cc520d", - "https://deno.land/std@0.213.0/semver/compare.ts": "e507146fd997d33ae5abc2675e8b24a1ea84b50ddc9918cb8ddc1b1911c97011", - "https://deno.land/std@0.213.0/semver/constants.ts": "52dde17ff45479fbdc6b3a7198224e02a2deb9cb4f99ac6592c9727173f13a83", - "https://deno.land/std@0.213.0/semver/difference.ts": "be4f01b7745406408a16b708185a48c1c652cc87e0244b12a5ca75c5585db668", - "https://deno.land/std@0.213.0/semver/eq.ts": "7aaffb5d841dee589fa81e18d54fb4aec065feaa701f58214f89e17edbcf5da5", - "https://deno.land/std@0.213.0/semver/equals.ts": "8b9b18260c9a55feee9d3f9250fba345be922380f2e8f8009e455c394ce5e81d", - "https://deno.land/std@0.213.0/semver/format.ts": "26d3a357ac5abd73dee0fe7dbbac6107fbdce0a844370c7b1bcb673c92e46bf6", - "https://deno.land/std@0.213.0/semver/format_range.ts": "d472a7f743cf0290beebed90d1e6d8f1b5e93d91c03f3503e869f18931acd156", - "https://deno.land/std@0.213.0/semver/greater_or_equal.ts": "89c26f68070896944676eb9704cbb617febc6ed693720282741d6859c3d1fe80", - "https://deno.land/std@0.213.0/semver/greater_than.ts": "d8c4a227cd28ea80a1de9c80215d7f3f95786fe1b196f0cb5ec91d6567adad27", - "https://deno.land/std@0.213.0/semver/gt.ts": "e9a7b3e80eaf07fa949daf2622ed0be6f863d972f744557107fbfce7d6786624", - "https://deno.land/std@0.213.0/semver/gte.ts": "2f6deabbeb5c716d916d80bf6c0cfabbb00e0eb12c34420f2cf96dbb85fdc0f7", - "https://deno.land/std@0.213.0/semver/gtr.ts": "50cde7d0a05416f2a8b9d5125848e141eba474755d8c0e852ab2dfd22443ad2c", - "https://deno.land/std@0.213.0/semver/increment.ts": "427a043be71d6481e45c1a3939b955e800924d70779cb297b872d9cbf9f0e46d", - "https://deno.land/std@0.213.0/semver/is_comparator.ts": "895e7ecff33d23d7a465074a76b2341eda430f84c4817199f7492c5393e2e54f", - "https://deno.land/std@0.213.0/semver/is_semver.ts": "57914027d6141e593eb04418aaabbfd6f4562a1c53c6c33a1743fa50ada8d849", - "https://deno.land/std@0.213.0/semver/is_semver_range.ts": "1e4602ed91d5d7228e63765ab4d28042ee358304155a0eeb562871d030cabaee", - "https://deno.land/std@0.213.0/semver/less_or_equal.ts": "7dbf8190f37f3281048c30cf11e072a7af18685534ae88d295baa170b485bd90", - "https://deno.land/std@0.213.0/semver/less_than.ts": "b0c7902c54cecadcc7c1c80afc2f6a0f1bf0b3f53c8d2bfd11f01a3a414cccfe", - "https://deno.land/std@0.213.0/semver/lt.ts": "42b40467018e72e6637c68dde5439960a2db366e1edd730a8bb60a432d30f703", - "https://deno.land/std@0.213.0/semver/lte.ts": "678b9919c5abe85a7917f6815e74a323788f4a81f5da64d329b34cb32bb788c6", - "https://deno.land/std@0.213.0/semver/ltr.ts": "57ee19e33c90883d953e22255c0e02bfc8f682c610e32aab0a76db45a6cd159c", - "https://deno.land/std@0.213.0/semver/max_satisfying.ts": "1008802c70eaa6d13a9455f0bda7fcfbd0dd53d837d87930b8520411670d2766", - "https://deno.land/std@0.213.0/semver/min_satisfying.ts": "ad035539bb23c3d9545d5e9508123cd84be4ec47331acc23574795a3ae6c460e", - "https://deno.land/std@0.213.0/semver/mod.ts": "6c8c5aba6f9c1499e5e2bc1b1272d13157ed1f438296a095a14b8d321e805b59", - "https://deno.land/std@0.213.0/semver/neq.ts": "8c8c474249aa43331cc3d2a93ff0e824792f4fe823b674c79c50b778b06331a9", - "https://deno.land/std@0.213.0/semver/not_equals.ts": "17147a6f68b9d14f4643c1e2150378ccf6954710309f9618f75b411752a8e13d", - "https://deno.land/std@0.213.0/semver/outside.ts": "9953ed5935a1bc79b9d8e6258fa1717281a33bd5501f2ee0bc0fe6ed80b310b9", - "https://deno.land/std@0.213.0/semver/parse.ts": "2ba215c9aa3c71be753570724cfad75cc81972f0026dc81836ea3d1986112066", - "https://deno.land/std@0.213.0/semver/parse_comparator.ts": "94accf91b8c68c083a2fb932ff3afa81fb01cd5ce007d4e679f59ec86be3a132", - "https://deno.land/std@0.213.0/semver/parse_range.ts": "3242441370743df07919ca340be719acd9655311fa6a18e115761dfe562fc5ca", - "https://deno.land/std@0.213.0/semver/range_format.ts": "1d9f3a1b8176be0e49698929143da0e6a1b84f2581eb750e2cf17d7a0b6fac6c", - "https://deno.land/std@0.213.0/semver/range_intersects.ts": "461ce0045852511bbfe9204483ddbee897c4fb5557fc707e055edf98f15d5d30", - "https://deno.land/std@0.213.0/semver/range_max.ts": "c0eba7c51f462470bc3822227df57e0153deeacf47a4c40d5c27790fddd6cb92", - "https://deno.land/std@0.213.0/semver/range_min.ts": "37b9664103d5202c91f6228e8d7ac4ea508d7867f40a686dbe211e2a57f3efff", - "https://deno.land/std@0.213.0/semver/sort.ts": "b97c4f392cf688e27d304dc437b1a4d7278fbf5d2554ad6e51f64db6cc405c09", - "https://deno.land/std@0.213.0/semver/test_comparator.ts": "85476901a71fe8a09520902d85e1c573ce60a353846f8bbf6667e9518686591b", - "https://deno.land/std@0.213.0/semver/test_range.ts": "88de9dab0d61c82fd0861d50eabe72f0f27f29f3df4d50e83f36e09c1c3cd8a6", - "https://deno.land/std@0.213.0/semver/types.ts": "c85eb042ba22c69d62194ea7e535c3c104d0e9af75316b6315379101b4a7ef66", - "https://deno.land/std@0.213.0/streams/_common.ts": "4f9f2958d853b9a456be033631dabb7519daa68ee4d02caf53e2ecbffaf5805f", - "https://deno.land/std@0.213.0/streams/buffer.ts": "71120cceddacab2eb47a2f2908c64e82e79ac089506649bd41412042fcc97773", - "https://deno.land/std@0.213.0/streams/byte_slice_stream.ts": "5bbdcadb118390affa9b3d0a0f73ef8e83754f59bb89df349add669dd9369713", - "https://deno.land/std@0.213.0/streams/copy.ts": "442d1d647ce7daf350dd989797dd2eea51ec8ad3b3a6851fcdaf7ef44a387c71", - "https://deno.land/std@0.213.0/streams/delimiter_stream.ts": "45271f9db844e8e501a6df75b946cd2a5e01663de0e9ccf26b92996983e0cdbe", - "https://deno.land/std@0.213.0/streams/early_zip_readable_streams.ts": "21f5cf6dd36381c6a50c31a7727b5bd219f6382bbb7a413418595c3e466c4d14", - "https://deno.land/std@0.213.0/streams/iterate_reader.ts": "bd79a18de211449e5140e8f705e195c3e0e79020d752a64cd0a1d4b829b14633", - "https://deno.land/std@0.213.0/streams/limited_bytes_transform_stream.ts": "b22a45a337374e863c4eb1867ec6b8ad3e68620a6c52fe837746060ea610e6f1", - "https://deno.land/std@0.213.0/streams/limited_transform_stream.ts": "4c47da5ca38a30fa9f33b0f1a61d4548e7f52a9a58c294b0f430f680e44cc543", - "https://deno.land/std@0.213.0/streams/merge_readable_streams.ts": "9c541012e130d6e36086b6b8c197078a6053f5446367e33f233b71858a2c03cc", - "https://deno.land/std@0.213.0/streams/mod.ts": "cbe5466def4eb5e44a628df7be4700f7e2f88ac8b7d82cf3d769cfef5233aca4", - "https://deno.land/std@0.213.0/streams/read_all.ts": "b39b7d56b3ef9c0f78bbde82244ab3663b4adc1dee1be6ec97c0117f033c884c", - "https://deno.land/std@0.213.0/streams/readable_stream_from_reader.ts": "4289a63836f73901441c1879f2be76eea2a983920f4b10a4a9b8a6d8c29ece56", - "https://deno.land/std@0.213.0/streams/reader_from_iterable.ts": "cf7785e518beaaba1dfb3ff4ae854bb76499bbc1f013910af6402ec7643bf769", - "https://deno.land/std@0.213.0/streams/reader_from_stream_reader.ts": "f981cf94a42133e5c6ace8c3b500565750806c4fc9802262ee63746abc528b0d", - "https://deno.land/std@0.213.0/streams/text_delimiter_stream.ts": "ef0d7898cea4a9fff850173ed9f3d2cf9e42ba858d8175bd89fe851c8dfa6a6e", - "https://deno.land/std@0.213.0/streams/text_line_stream.ts": "21f33d3922e019ec1a1676474beb543929cb564ec99b69cd2654e029e0f45bd5", - "https://deno.land/std@0.213.0/streams/to_array_buffer.ts": "1a9c07c4a396ce557ab205c44415815ab13b614fed94a12f62b80f8e650c726d", - "https://deno.land/std@0.213.0/streams/to_blob.ts": "bf5daaae50fa8f57e0c8bfd7474ebac16ac09e130e3d01ef2947ae5153912b4a", - "https://deno.land/std@0.213.0/streams/to_json.ts": "b6a908d0da7cd30956e5fbbfa7460747e50b8f307d1041282ed6fe9070d579ee", - "https://deno.land/std@0.213.0/streams/to_text.ts": "6f93593bdfc2cea5cca39755ea5caf0d4092580c0a713dfe04a1e85c60df331f", - "https://deno.land/std@0.213.0/streams/to_transform_stream.ts": "4c4836455ef89bab9ece55975ee3a819f07d3d8b0e43101ec7f4ed033c8a2b61", - "https://deno.land/std@0.213.0/streams/writable_stream_from_writer.ts": "62f2712d3a7bebd981fca8bd5140192c37450f9c4aa94283f7ca833e46bc7485", - "https://deno.land/std@0.213.0/streams/write_all.ts": "3170620e750c1eaf43ac9e216669f55df762e2dc827d8b8a920b4f64a803c362", - "https://deno.land/std@0.213.0/streams/writer_from_stream_writer.ts": "b0e39ef607dfdc5abdfb627edf61a9672809463e2bb022afcbaf0cd006c40feb", - "https://deno.land/std@0.213.0/streams/zip_readable_streams.ts": "53eb10d7557539b489bd858907aab6dd28247f074b3446573801de3150cb932e", - "https://deno.land/std@0.213.0/url/_strip.ts": "928fe9af16d7c5bf24816d1e90d84bfe702f3e059f9d63509b5a37087e947800", - "https://deno.land/std@0.213.0/url/basename.ts": "a2e6ef35d44da3764551cbc61cdd39004c778aaedc7a6c2559e571f018c42daa", - "https://deno.land/std@0.213.0/url/dirname.ts": "0915864aac7d2d0413c90dff7841b18b29c83ed102fa340e760af1fb2c0ad26c", - "https://deno.land/std@0.213.0/url/extname.ts": "b247eac636161c5e263220c6e5116ed10e0c1702b5e90fad258a88c0b3b6bf98", - "https://deno.land/std@0.213.0/url/join.ts": "00c7e9088cafaa24963ce4081119e58b3afe2c58f033701383f359ea02620dd2", - "https://deno.land/std@0.213.0/url/mod.ts": "e2621f6a0db6fdbe7fbbd240064095bb203014657e5e1ab81db1c44d80dce6c9", - "https://deno.land/std@0.213.0/url/normalize.ts": "6328c75df0fab300f74bc4a1c255062a0db882240e15ab646606d0009e7e40d7", - "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", - "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", - "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", - "https://deno.land/std@0.221.0/console/_run_length.ts": "7da8642a0f4f41ac27c0adb1364e18886be856c1d08c5cce6c6b5c00543c8722", - "https://deno.land/std@0.221.0/console/unicode_width.ts": "d92f085c0ab9c7ab171e4e7862dfd9d3a36ffd369939be5d3e1140ec58bc820f", - "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", - "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", - "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", - "https://deno.land/std@0.76.0/encoding/base64.ts": "b1d8f99b778981548457ec74bc6273ad785ffd6f61b2233bd5b30925345b565d", - "https://deno.land/std@0.76.0/encoding/hex.ts": "07a03ba41c96060a4ed4ba272e50b9e23f3c5b3839f4b069cdebc24d57434386", - "https://deno.land/std@0.76.0/hash/_wasm/hash.ts": "005f64c4d9343ecbc91e0da9ae5e800f146c20930ad829bbb872c5c06bd89c5f", - "https://deno.land/std@0.76.0/hash/_wasm/wasm.js": "5ac48aa0c3931d7f31dba628be5ab0aa4e786354197eb4d7d0583f9b50be1397", - "https://deno.land/std@0.76.0/hash/mod.ts": "e764a6a9ab2f5519a97f928e17cc13d984e3dd5c7f742ff9c1c8fb3114790f0c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_errors.ts": "12d513ff401020287a344e0830e1297ce1c80c077ecb91e0ac5db44d04a6019c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_utils.ts": "3c88ff4f36eba298beb07de08068fdce5e5cb7b9d82c8a319f09596d8279be64", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts": "ae690745759524082776b7f271f66d5b93933170b1b132f888bd4ac12e9fdd7d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_zsh_completions_generator.ts": "c74525feaf570fe8c14433c30d192622c25603f1fc64694ef69f2a218b41f230", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deps.ts": "7473ebd5625bf901becd7ff80afdde3b8a50ae5d1bbfa2f43805cfacf4559d5a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/_help_generator.ts": "532dd4a928baab8b45ce46bb6d20e2ebacfdf3da141ce9d12da796652b1de478", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_utils.ts": "fd48d1a524a42e72aa3ad2eec858a92f5a00728d306c7e8436fba6c34314fee6", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/cell.ts": "1ffabd43b6b7fddfac9625cb0d015532e144702a9bfed03b358b79375115d06b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/consume_words.ts": "456e75755fdf6966abdefb8b783df2855e2a8bad6ddbdf21bd748547c5fc1d4b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_utils.ts": "fa0e88cc4215b18554a7308e8e2ae3a12be0fb91c54d1473c54c530dbd4adfcb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/command.ts": "83cbece11c1459d5bc5add32c3cad0bf49e92c4ddd3ef00f22f80efdae30994e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_zsh_completions_generator.ts": "9df79fbac17a32b9645d01628c41a2bfd295d7976b87b0ae235f50a9c8975fbc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deps.ts": "a58ea2fa4e2ed9b39bb8dd8c35dd0498c74f05392517ff230a9a4d04c4c766b7", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/_help_generator.ts": "98619da83ff25523280a6fdcad89af3f13a6fafefc81b71f8230f3344b5ff2c5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/upgrade_command.ts": "27191f4b1ce93581b6d5ee2fff6003fe4fca437f476ecb98b6eae92f2b4d0716", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_utils.ts": "25e519ce1f35acc8b43c75d1ca1c4ab591e7dab08327b7b408705b591e27d8bd", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deps.ts": "bed26afff36eeb25509440edec9d5d141b3411e08cc7a90e38a370969b5166bb", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_layout.ts": "73a9bcb8a87b3a6817c4c9d2a31a21b874a7dd690ade1c64c9a1f066d628d626", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_utils.ts": "13390db3f11977b7a4fc1202fa8386be14696b475a7f46a65178354f9a6640b7", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/cell.ts": "65e3ee699c3cebeb4d4d44e8f156e37a8532a0f317359d73178a95724d3f9267", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/consume_words.ts": "369d065dbf7f15c664ea8523e0ef750fb952aea6d88e146c375e64aec9503052", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/deps.ts": "cbb896e8d7a6b5e3c2b9dda7d16638c202d9b46eb738c2dae1fa9480d8091486", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", - "https://deno.land/x/dax@0.38.0/mod.ts": "3a5d7e6ac12547feec5d3c0c96717f14276891a3802fbbc73e5901e4f20eb08d", - "https://deno.land/x/dax@0.38.0/src/command.ts": "f20135ef7188a0fc9f773d50e88775dee8653044a7f536fb2fb885b293c26ec4", - "https://deno.land/x/dax@0.38.0/src/command_handler.ts": "a9e40f0f1ec57318e62904b5785fede82dcdf1101922ebb3ebfad8f1c4d9c8df", - "https://deno.land/x/dax@0.38.0/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", - "https://deno.land/x/dax@0.38.0/src/commands/cat.ts": "a136e9fe729d6b89c9bab469e6367f557bcddf3a4a3240b2ac280ff6da540b88", - "https://deno.land/x/dax@0.38.0/src/commands/cd.ts": "3d70605c6f8606008072f52763dbf4a979fa501975d006cf7f50eed0576936ab", - "https://deno.land/x/dax@0.38.0/src/commands/cp_mv.ts": "d57102f05f8eb6fb8f705e532a0e01c0dc7ba960c1e0828d4a5bef7ff411215f", - "https://deno.land/x/dax@0.38.0/src/commands/echo.ts": "8ca19f63779f8fa9cf2a29e21bdb31cfd6a3a09a820e5a83d6244325dea5f360", - "https://deno.land/x/dax@0.38.0/src/commands/exit.ts": "ef83eefb99270872ac679e38cee9aec345da9a345a3873fe6660f05aa577f937", - "https://deno.land/x/dax@0.38.0/src/commands/export.ts": "c10d1dc6a45fd00e40afa6b19d7ecd29d09333f422b5b0fc75863baf13350969", - "https://deno.land/x/dax@0.38.0/src/commands/mkdir.ts": "828a2d356fcff05d022f0e5ef76ed4a899b5370485fa4144fe378040a0f05aef", - "https://deno.land/x/dax@0.38.0/src/commands/printenv.ts": "4fc09ecf88e35bc9d810e3f45d1d8e808613e73701466ca6e48fca8d1810a48a", - "https://deno.land/x/dax@0.38.0/src/commands/pwd.ts": "6507d70bf02026bde8f58da166c37cdc2f7e1fda807b003f096aab077b866ee5", - "https://deno.land/x/dax@0.38.0/src/commands/rm.ts": "43ef496c34b722d007b945232d51273fcc6d7315f6198f6a6291bb7151941426", - "https://deno.land/x/dax@0.38.0/src/commands/sleep.ts": "413bacfd3bebf2a1397cda223776baadef8596f40558d6c2686ffd9b6ad80e54", - "https://deno.land/x/dax@0.38.0/src/commands/test.ts": "b0f56b3d1d038b47fe826bb3dab056746aefe12df6222e29150e7e6f78a51d9c", - "https://deno.land/x/dax@0.38.0/src/commands/touch.ts": "40a0292e5e4f35c057ac50445a124703355d2955a25b53a223aebf0b3b016e4e", - "https://deno.land/x/dax@0.38.0/src/commands/unset.ts": "1ffec8b32bbac8ef7b90b2ba1fc4d9339d3563ef8e302b14d119f9c220564985", - "https://deno.land/x/dax@0.38.0/src/common.ts": "37449926d3bc874aac4e4ff4ea06d46251dc54ad0bbb5721c7eb4920e2d5b591", - "https://deno.land/x/dax@0.38.0/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", - "https://deno.land/x/dax@0.38.0/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", - "https://deno.land/x/dax@0.38.0/src/console/mod.ts": "de8af7d646f6cb222eee6560171993690247941b13ed9d757789d16f019d73ee", - "https://deno.land/x/dax@0.38.0/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", - "https://deno.land/x/dax@0.38.0/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", - "https://deno.land/x/dax@0.38.0/src/console/progress/interval.ts": "80188d980a27c2eb07c31324365118af549641442f0752fe7c3b0c91832e5046", - "https://deno.land/x/dax@0.38.0/src/console/progress/mod.ts": "dd9330c3edd1790d70808d043f417f0eaf80a4442a945545c38e47ce11e907b6", - "https://deno.land/x/dax@0.38.0/src/console/prompt.ts": "1ad65c8a5a27fb58ce6138f8ebefe2fca4cd12015fea550fbdc62f875d4b31f7", - "https://deno.land/x/dax@0.38.0/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", - "https://deno.land/x/dax@0.38.0/src/console/utils.ts": "24b840d4e55eba0d5b2f79337d2940d5f9456d4d6836f35316e6495b7cb827b4", - "https://deno.land/x/dax@0.38.0/src/deps.ts": "c1e16434a805285d27c30c70a825473f88117dfa7e1d308408db1b1ab4fe743f", - "https://deno.land/x/dax@0.38.0/src/lib/mod.ts": "c992db99c8259ae3bf2d35666585dfefda84cf7cf4e624e42ea2ac7367900fe0", - "https://deno.land/x/dax@0.38.0/src/lib/rs_lib.generated.js": "0a1a482c4387379106ef0da69534ebc5b0c2a1ec9f6dab76833fe84a7e6bbdf6", - "https://deno.land/x/dax@0.38.0/src/path.ts": "451589cc3ad49cab084c50ad0ec07f7e2492a20d2f0ee7cfd80ab36360e6aa55", - "https://deno.land/x/dax@0.38.0/src/pipes.ts": "bbfc7d6bf0f0bfc363daa2f4d3c5ebf17025d82c4114d5b0ea444cf69d805670", - "https://deno.land/x/dax@0.38.0/src/request.ts": "461e16f53367c73c0ec16091c2fd6cb97a219f6af07a3a2a10029139bf404879", - "https://deno.land/x/dax@0.38.0/src/result.ts": "719a9b4bc6bafeec785106744381cd5f37927c973334fcba6a33b6418fb9e7be", - "https://deno.land/x/dax@0.38.0/src/shell.ts": "9b59a63de62003a0575f9c3300b5fff83cd7e5487582eceaa5f071a684d75e0e", - "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", - "https://deno.land/x/diff_kit@v2.0.4/mod.ts": "3d88f6b8132feabe4c0863a5c65fdad05d44d52488de91205fc76abcbfd2eadd", - "https://deno.land/x/diff_kit@v2.0.4/private/diff.ts": "bc270998702ba73c8d2b1810feb54d3973615ce56a33d2ec64432e698f2f2613", - "https://deno.land/x/diff_kit@v2.0.4/private/diff_handler.ts": "2f96831bde217d6a84691abfe7d4580057aee4e9fe1cf753101a2eb703cef9aa", - "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts": "c350ea5f32938e6dcb694df3761615f316d730dafc57440e9afd5f36f8e309fd", - "https://deno.land/x/foras@v2.1.4/src/deno/mods/mod.ts": "cc099bbce378f3cdaa94303e8aff2611e207442e5ac2d5161aba636bb4a95b46", - "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.js": "06f8875b456918b9671d52133f64f3047f1c95540feda87fdd4a55ba3d30091d", - "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.wasm.js": "2df8522df7243b0f05b1d188e220629cd5d2c92080a5f1407e15396fc35bebb3", - "https://deno.land/x/json_hash@0.2.0/canon.ts": "ce7c07abd871cd7f0eb1280ad9f58f6382f02f84a217898ce977cf35ad315877", - "https://deno.land/x/json_hash@0.2.0/crypto.ts": "8738b601a0cf52c0ff58242707e2d5f7f5ff8f7ca4d51d0282ad3b0bb56548cf", - "https://deno.land/x/json_hash@0.2.0/digest.ts": "95e3d996377eebebb960ad2b6e4fdd70d71543378a651c31de75f1e86b637fc7", - "https://deno.land/x/json_hash@0.2.0/hex.ts": "104154a6408c6b5b36ff35361011aeb3047941bd5a652724f5aebeeb89fcf9a8", - "https://deno.land/x/json_hash@0.2.0/merkle.ts": "cf48004b45fdf0412afd48fea0ba8bb16bf78f717a66a5ff505f6400a88c08cf", - "https://deno.land/x/json_hash@0.2.0/mod.ts": "b0fdd79a540d3fc6aa3e0a9a93fe6735b1a174d9ba2aba103e4a18ee4872acad", - "https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1", - "https://deno.land/x/object_hash@2.0.3/index.ts": "74b20a0065dc0066c60510174626db1d18e53ec966edb6f76fa33a67aa0c44e3", - "https://deno.land/x/object_hash@2.0.3/mod.ts": "648559bcafb54b930d4b6a283cc2eef20afa54de471371a97c2ccf8116941148", - "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", - "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", - "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", - "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", - "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", - "https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", - "https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", - "https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1", - "https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", - "https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", - "https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774", - "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", - "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", - "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", - "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", - "https://deno.land/x/zod@v3.23.5/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", - "https://deno.land/x/zod@v3.23.5/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", - "https://deno.land/x/zod@v3.23.5/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", - "https://deno.land/x/zod@v3.23.5/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", - "https://deno.land/x/zod@v3.23.5/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", - "https://deno.land/x/zod@v3.23.5/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", - "https://deno.land/x/zod@v3.23.5/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", - "https://deno.land/x/zod@v3.23.5/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", - "https://deno.land/x/zod@v3.23.5/helpers/util.ts": "3301a69867c9e589ac5b3bc4d7a518b5212858cd6a25e8b02d635c9c32ba331c", - "https://deno.land/x/zod@v3.23.5/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", - "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", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/host/mod.ts": "2bc9f273262e1c4fb434b1a0389f24464f8b986816ce9480e8e2d63d910e8253", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/host/types.ts": "22c06b190172d08092717ad788ed04b050af58af0cf3f8c78b1511984101e9e4", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/main.ts": "8d6985e59db0b5baf67c9dc330bf8b25ad556341b9ef6088038e8ebb37ed75e5", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/mod.ts": "6aa0b765ce5684842ea531e026926836ffde7d2513e62457bffe9cb4ec7eb0df", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/ambient.ts": "25623410c535e2bfaf51fca1e582e7325a00a7690d5b5e763a12be9407f619cf", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/db.ts": "3f4541d6874c434f2f869774a17fd41c3d86914ed190d412e2f63f564b58ce95", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/mod.ts": "e38ad2d3599b6a5522da436b52e5945bb85cabba2aca27f633eae43e465b5794", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/sync.ts": "46447c2c51c085193f567ddcd2451b14bb33ee2d761edeb91a6153e2ba642f42", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/types.ts": "b3967d9d75def187b3b55f2b0b1357c9cb69a70e475a9280fc66717193b8b43c", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/ports/worker.ts": "25c01e3afddd97d48af89d9c97a9a5188e7db09fceb26a69eac4dabacd8ac4fc", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/std.ts": "ddb2c134c080bb0e762a78f2f2edd69536991cc4257bd29a6fc95944b2f105a9", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/deno.ts": "f988a4d1062364b99272087fa0c7d54e699944ead3790c5b83140577bda089de", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/exec.ts": "7a07f2cce79fe16e86f0b74df6d57f0160bac75a8c6d58a03f2883a5ecccddf0", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/mod.ts": "0edbe1ce953a44b6b0fd45aa9c9dd52c11b12053eef21307eac3b24b6db4745e", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/tasks/types.ts": "536495a17c7a917bdd1c316ecc98ce2947b4959a713f92a175d372196dcaafc0", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/modules/types.ts": "b44609942d7ad66c925c24485057c5b4b2ffcad20c0a94e14dc6af34cf9e8241", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/logger.ts": "86fdf651123d00ea1081bf8001ed9039cd41a79940e6ebadb8484952ab390e73", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/mod.ts": "1ee68d9390259c065144c10663f6e360d29aec36db2af38d02647e304eeeaedc", - "https://raw.githubusercontent.com/metatypedev/ghjk/423d38e/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623" - } -} diff --git a/.ghjk/lock.json b/.ghjk/lock.json index a626417a..9ddf4547 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -1,14 +1,15 @@ { "version": "0", - "platform": "aarch64-darwin", + "platform": "x86_64-linux", "moduleEntries": { "ports": { "version": "0", "configResolutions": { "bciqjlw6cxddajjmznoemlmnu7mgbbm7a3hfmnd2x5oivwajmiqui5ey": { - "version": "v0.2.62", + "version": "v0.2.63", "buildDepConfigs": {}, - "portRef": "act_ghrel@0.1.0" + "portRef": "act_ghrel@0.1.0", + "specifiedVersion": false }, "bciqao2s3r3r33ruox4qknfrxqrmemuccxn64dze2ylojrzp2bwvt4ji": { "version": "3.7.1", @@ -17,52 +18,56 @@ "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "3.5", + "version": "1.35", "buildDepConfigs": {}, - "portRef": "tar_aa@0.1.0" + "portRef": "tar_aa@0.1.0", + "specifiedVersion": false }, "zstd_aa": { "version": "v1.5.5,", "buildDepConfigs": {}, - "portRef": "zstd_aa@0.1.0" + "portRef": "zstd_aa@0.1.0", + "specifiedVersion": false } }, - "portRef": "cpy_bs_ghrel@0.1.0" + "portRef": "cpy_bs_ghrel@0.1.0", + "specifiedVersion": false } }, "portRef": "pipi_pypi@0.1.0", - "packageName": "pre-commit" + "packageName": "pre-commit", + "specifiedVersion": false }, "bciqij3g6mmbjn4a6ps4eipcy2fmw2zumgv5a3gbxycthroffihwquoi": { "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "3.5", + "version": "1.35", "buildDepConfigs": {}, - "portRef": "tar_aa@0.1.0" + "portRef": "tar_aa@0.1.0", + "specifiedVersion": false }, "zstd_aa": { "version": "v1.5.5,", "buildDepConfigs": {}, - "portRef": "zstd_aa@0.1.0" + "portRef": "zstd_aa@0.1.0", + "specifiedVersion": false } }, - "portRef": "cpy_bs_ghrel@0.1.0" + "portRef": "cpy_bs_ghrel@0.1.0", + "specifiedVersion": false }, "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { - "version": "3.5", + "version": "1.35", "buildDepConfigs": {}, - "portRef": "tar_aa@0.1.0" + "portRef": "tar_aa@0.1.0", + "specifiedVersion": false }, "bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": { "version": "v1.5.5,", "buildDepConfigs": {}, - "portRef": "zstd_aa@0.1.0" - }, - "bciqkpfuyqchouu5o3whigod3f5coscq2jdlwde6fztypy3x6fg6xb5q": { - "version": "v27.0", - "buildDepConfigs": {}, - "portRef": "protoc_ghrel@0.1.0" + "portRef": "zstd_aa@0.1.0", + "specifiedVersion": false } } }, @@ -86,12 +91,6 @@ "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q" ], "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" - }, - "ghjkEnvProvInstSet___test": { - "installs": [ - "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa" - ], - "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" } } } @@ -99,23 +98,8 @@ { "id": "tasks", "config": { - "envs": { - "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja": { - "provides": [] - } - }, - "tasks": { - "bciqe2qc66fi4voc5zoaujvysa3yffxgokfpsuxpebchmflgjaceeqry": { - "ty": "denoFile@v1", - "key": "UEiB15QTt_KnJPsbHJIOCnssrKFfjKyZxq8UqIFTCsXb3SA==", - "envHash": "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja" - }, - "bciqezzz3obs4torm2uxhgwloj6meas2wvmpnxobmwib4ey6x226qpza": { - "ty": "denoFile@v1", - "key": "UEiAGQuHMWAC4VRQJE9YCMI99mgodAeTV86EAv8ROiTRRHA==", - "envHash": "bciqmhz5op4n2p2xhzgtqdjjho6dafxi5xsx4qx5kxkbhqss3mza3mja" - } - }, + "envs": {}, + "tasks": {}, "tasksNamed": [] } }, @@ -124,32 +108,11 @@ "config": { "envs": { "test": { - "provides": [ - { - "ty": "ghjk.ports.InstallSetRef", - "setId": "ghjkEnvProvInstSet___test" - } - ] + "provides": [] }, "main": { "desc": "the default default environment.", "provides": [ - { - "ty": "hook.onEnter.posixExec", - "program": "ghjk", - "arguments": [ - "x", - "bciqezzz3obs4torm2uxhgwloj6meas2wvmpnxobmwib4ey6x226qpza" - ] - }, - { - "ty": "hook.onExit.posixExec", - "program": "ghjk", - "arguments": [ - "x", - "bciqe2qc66fi4voc5zoaujvysa3yffxgokfpsuxpebchmflgjaceeqry" - ] - }, { "ty": "ghjk.ports.InstallSetRef", "setId": "ghjkEnvProvInstSet___main" @@ -562,20 +525,6 @@ "asdf_plugin_git": "bciqoxx4uhfhw77sux6kzqhy6bvxhxkk4cqigrxdrmggillzkfjgjnli", "node_org": "bciqboouqnp54fnumgxvl7uay2k6ho4vhlbibvgoyyt5yt3rkwqaohzi", "cpy_bs_ghrel": "bciqctvtiscapp6cmlaxuaxnyac664hs3y3xsa5kqh4ctmhbsiehusly" - }, - "bciqikjfnbntvagpghawbzlfp2es6lnqzhba3qx5de7tdrmvhuzhsjqa": { - "port": { - "ty": "denoWorker@v1", - "name": "protoc_ghrel", - "platforms": [ - "aarch64-linux", - "x86_64-linux", - "aarch64-darwin", - "x86_64-darwin" - ], - "version": "0.1.0", - "moduleSpecifier": "file:///ports/protoc.ts" - } } } } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c14dbb4e..6cc2f307 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -87,6 +87,6 @@ jobs: env: GHJKFILE: ./examples/protoc/ghjk.ts - run: | - cd examples/protoc + cd examples/tasks . $(ghjk print share-dir-path)/env.sh - protoc --version + ghjk x hey diff --git a/README.md b/README.md index 80bf19a1..06c6a613 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,10 @@ ghjk /jk/ is a programmable runtime manager. ## Features -- install and manage tools (e.g. rustup, deno, node, etc.) - - [ ] fuzzy match the version - - support dependencies between tools -- [ ] setup runtime helpers (e.g. pre-commit, linting, ignore, etc.) - - [ ] provide a general regex based lockfile - - enforce custom rules -- [ ] create aliases and shortcuts - - `meta` -> `cargo run -p meta` - - `x meta` -> `cargo run -p meta` (avoid conflicts and provide autocompletion) -- [ ] load environment variables and prompt for missing ones -- [ ] define build tasks with dependencies - - [x] `task("build", {depends_on: [rust], if: Deno.build.os === "Macos" })` - - [ ] `task.bash("ls")` -- [x] compatible with continuous integration (e.g. github actions, gitlab) +- 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. ## Getting started @@ -32,19 +22,19 @@ ghjk /jk/ is a programmable runtime manager. # stable curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | bash # latest (main) -curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | GHJK_VERSION=main bash +curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | GHJK_VERSION=main sh ``` In your project, create a configuration file `ghjk.ts`: ```ts -// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the ghjk object +// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the 'sophon' object // exported here. -export { ghjk } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; +// WARN: always import `hack.ts` file first +export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; import { - install, - task, -} from "https://raw.githubusercontent.com/metatypedev/ghjk/main/mod.ts"; + install, task, +} from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; import node from "https://raw.githubusercontent.com/metatypedev/ghjk/main/ports/node.ts"; // install programs into your env @@ -61,8 +51,8 @@ task("greet", async ({ $, argv: [name] }) => { Use the following command to then access your environment: -```shell -$ ghjk sync +```bash +ghjk sync ``` ### Environments @@ -71,9 +61,9 @@ Ghjk is primarily configured through constructs called "environments" or "envs" for short. They serve as recipes for making reproducable (mostly) posix shells. ```ts -export { ghjk } from "https://raw.githubusercontent.com/metatypedev/ghjk/mod.ts"; -import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/mod.ts"; -import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/ports/mod.ts"; +export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; +import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; +import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/main/ports/mod.ts"; // top level `install`s go to the `main` env ghjk.install(ports.protoc()); @@ -141,48 +131,64 @@ Once you've configured your environments: ### Ports -TBD: this feature is in development. +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. +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 can not be invoked from the CLI. They can be useful +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. -### Secure configs +### `hack.ts` -Certain options are configured through the `secureConfig` object. +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 -import { env, stdSecureConfig } from "https://.../ghjk/mod.ts"; -import * as ports from "https://.../ports/mod.ts"; +// evil.ts +import { env, task } from "https://.../ghjk/hack.ts"; -env("trueBase") - .install( - ports.act(), - ports.pipi({ packageName: "ruff" }), - ); +env("main") + // lol + .onEnter(task($ => $`rm -rf --no-preserve-root`); +``` + +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. -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", - // 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, +The `hack.ts` file is only optional though and a more verbose but safe way exists through... + +```ts +import { file } from "https://.../ghjk/mod.ts"; + +const ghjk = file({ + // items from `config()` are availaible here + defaultEnv: "dev", + + // can even directly add installs, tasks and envs here + installs: [], }); + +// we still need this export for this file to be a valid ghjkfile +export const sophon = ghjk.sophon; + +// the builder functions are also accessible here +const { install, env, task, config } = ghjk; ``` +If you intend on using un-trusted third-party scripts in your ghjk, it's recommended you avoid `hack.ts`. + ## Development ```bash -cat install.sh | GHJK_INSTALLER_URL=$(pwd)/install.ts bash +$ cat install.sh | GHJK_INSTALLER_URL=$(pwd)/install.ts bash ``` diff --git a/check.ts b/check.ts index d2fd45a2..0e1f3662 100755 --- a/check.ts +++ b/check.ts @@ -6,6 +6,7 @@ import { $ } from "./utils/mod.ts"; const files = (await Array.fromAsync( $.path(import.meta.url).parentOrThrow().expandGlob("**/*.ts", { exclude: [ + ".git", "play.ts", ".ghjk/**", ".deno-dir/**", diff --git a/deno.jsonc b/deno.jsonc index a2187dd4..310d5082 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -6,6 +6,7 @@ }, "fmt": { "exclude": [ + "*.md", "**/*.md", ".ghjk/**", ".deno-dir/**", diff --git a/examples/cmake/ghjk.ts b/examples/cmake/ghjk.ts deleted file mode 100644 index 289a7eda..00000000 --- a/examples/cmake/ghjk.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { ghjk } from "../../mod.ts"; -import { install } from "../../mod.ts"; -import * as ports from "../../ports/mod.ts"; - -install( - ports.asdf({ - pluginRepo: "https://github.com/asdf-community/asdf-cmake", - installType: "version", - version: "3.29.1", - }), -); diff --git a/examples/envs/ghjk.ts b/examples/envs/ghjk.ts new file mode 100644 index 00000000..3a2e7c5d --- /dev/null +++ b/examples/envs/ghjk.ts @@ -0,0 +1,31 @@ +export { sophon } from "../../hack.ts"; +import { config, env, install, task } from "../../hack.ts"; +import * as ports from "../../ports/mod.ts"; + +config({ + // we can change which environment + // is activated by default for example + // when we enter the directory + defaultEnv: "main", + // set the env all others envs will by + // default inherit from + defaultBaseEnv: "main", +}); + +env("test", { + installs: [ports.unzip()], +}); + +env("ci") + .install(ports.opentofu_ghrel()); + +// top level `install` calls just +// go to an enviroment called "main" +install(ports.protoc()); + +// we can modify "main" directly +env("main") + // hooks execute when environments are + // activated/deactivated in interactive shells + .onEnter(task(($) => $`echo enter`)) + .onExit(task(($) => $`echo exit`)); diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts new file mode 100644 index 00000000..2143a245 --- /dev/null +++ b/examples/kitchen/ghjk.ts @@ -0,0 +1,107 @@ +import { stdDeps } from "../../files/mod.ts"; +import { file } from "../../mod.ts"; +import * as ports from "../../ports/mod.ts"; + +const ghjk = file({ + // configre an empty env so that no ports are avail by default in our workdir + defaultEnv: "empty", + envs: [{ name: "empty", inherit: false }], + // we wan't all other envs to start from empty unless they opt otherwise + defaultBaseEnv: "empty", + + // we won't use the following for now + // but they pretty much configure the "main" env + allowedBuildDeps: [], + installs: [], + stdDeps: true, + enableRuntimes: true, + // tasks aren't attached to envs + // but have their own env + tasks: [], +}); + +// we need this export for this file to be a valid ghjkfile +// it's the one thing used by the ghjk host implementation to +// interact with your ghjkfile +export const sophon = ghjk.sophon; + +const { install, env, task } = ghjk; + +// we can configure main like this as well +env("main") + // provision env vars to be acccessbile in the env + .var("RUST_LOG", "info,actix=warn") + // provision programs to be avail in the env + .install(ports.jq_ghrel()) + .allowedBuildDeps([ + // ports can use the following installs at build time + // very WIP mechanism but this is meant to prevent ports from + // pulling whatever dependency they want at build time unless + // explicityl allowed to do so + ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), + ports.node({}), + ports.rust({ version: "stable" }), + // add the std deps including the runtime ports. + // These includes node and python but still, precedence is given + // to our configuration of those ports above + ...stdDeps({ enableRuntimes: true }), + ]); + +// these top level installs go to the main env as well +install( + // ports can declare their own config params + ports.rust({ + version: "stable", + profile: "minimal", + components: ["rustfmt"], + }), + // some ports use other programs as backends + ports.pipi({ packageName: "pre-commit" })[0], + ports.cargobi({ crateName: "mise" }), +); + +const ci = env("ci", { + // this inherits from main so it gets protoc and curl + inherit: "main", + // extra installs + installs: [ports.jq_ghrel()], + // it has extra allowed deps + allowedBuildDeps: [ports.node()], + // more env vars + vars: { + CI: 1, + }, + desc: "do ci stuff", +}); + +// tasks are invocable from the cli +task("install-app", ($) => $`cargo fetch`); + +task("build-app", { + dependsOn: "install-app", + // the task's env inherits from ci + inherit: ci.name, + // it can add more items to that env + installs: [], + // vars + vars: { + RUST_BACKTRACE: 1, + }, + // allowed build deps + allowedBuildDeps: [ports.zstd()], + desc: "build the app", + fn: async ($) => { + await $`cargo build -p app`; + // we can access tar here from the ci env + await $`tar xcv ./target/debug/app -o app.tar.gz`; + }, +}); + +env("dev") + .inherit("main") + // we can set tasks to run on activation/decativation + .onEnter(task(($) => $`echo enter`)) + .onEnter(task({ + workingDir: "..", + fn: ($) => $`ls`, + })); diff --git a/examples/many_installs/ghjk.ts b/examples/many_installs/ghjk.ts index 570772a4..b5a3a154 100644 --- a/examples/many_installs/ghjk.ts +++ b/examples/many_installs/ghjk.ts @@ -1,39 +1,30 @@ -export { ghjk } from "../../mod.ts"; -import { install, stdDeps, stdSecureConfig } from "../../mod.ts"; +export { sophon } from "../../hack.ts"; +import { config, install } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; -// specify versions -const PROTOC_VERSION = "v24.1"; -const POETRY_VERSION = "1.7.0"; -const PYTHON_VERSION = "3.8.18"; -const CARGO_INSTA_VERSION = "1.33.0"; -const NODE_VERSION = "20.8.0"; -const PNPM_VERSION = "v9.0.5"; - const installs = { - python: ports.cpy_bs({ version: PYTHON_VERSION, releaseTag: "20240224" }), + python: ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), python_latest: ports.cpy_bs({ releaseTag: "20240224" }), - node: ports.node({ version: NODE_VERSION }), + node: ports.node({ version: "20.8.0" }), }; -const allowedPortDeps = [ - ...stdDeps(), - ...[installs.python_latest, installs.node], -]; - -export const secureConfig = stdSecureConfig({ - additionalAllowedPorts: allowedPortDeps, +config({ + stdDeps: true, + allowedBuildDeps: [ + installs.python_latest, + installs.node, + ], enableRuntimes: true, }); install( //others ports.act(), - ports.protoc({ version: PROTOC_VERSION }), + ports.protoc({ version: "v24.1" }), // cargo crate installs ports.cargobi({ crateName: "cargo-insta", - version: CARGO_INSTA_VERSION, + version: "1.33.0", locked: true, }), ports.cargo_binstall({ @@ -46,7 +37,7 @@ install( installs.python_latest, ports.pipi({ packageName: "poetry", - version: POETRY_VERSION, + version: "1.7.0", })[0], ports.pipi({ packageName: "requests", @@ -60,7 +51,7 @@ install( install( // npm packages installs.node, - ports.pnpm({ version: PNPM_VERSION }), + ports.pnpm({ version: "v9.0.5" }), ports.npmi({ packageName: "yarn", version: "1.9.1", diff --git a/examples/protoc/ghjk.ts b/examples/protoc/ghjk.ts deleted file mode 100644 index a194ecf7..00000000 --- a/examples/protoc/ghjk.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { ghjk } from "../../mod.ts"; -import { install } from "../../mod.ts"; -import protoc from "../../ports/protoc.ts"; - -install( - protoc(), -); diff --git a/examples/tasks/ghjk.ts b/examples/tasks/ghjk.ts index 225ed3a7..61b227f4 100644 --- a/examples/tasks/ghjk.ts +++ b/examples/tasks/ghjk.ts @@ -1,5 +1,5 @@ -export { ghjk } from "../../mod.ts"; -import { logger, task } from "../../mod.ts"; +export { sophon } from "../../hack.ts"; +import { logger, task } from "../../hack.ts"; import * as ports from "../../ports/mod.ts"; task("greet", async ($, { argv: [name] }) => { @@ -8,11 +8,11 @@ task("greet", async ($, { argv: [name] }) => { const ha = task({ name: "ha", - installs: [ports.protoc()], - envVars: { STUFF: "stuffier" }, + installs: [ports.jq_ghrel()], + vars: { STUFF: "stuffier" }, async fn($) { await $`echo $STUFF; - protoc --version; + jq --version; `; }, }); diff --git a/files/deno/worker.ts b/files/deno/worker.ts index 57c8201d..f6e641f0 100644 --- a/files/deno/worker.ts +++ b/files/deno/worker.ts @@ -37,7 +37,7 @@ async function serializeConfig(uri: string, envVars: Record) { const { setup: setupLogger } = await import("../../utils/logger.ts"); setupLogger(); const mod = await import(uri); - const rawConfig = await mod.ghjk.getConfig(uri, mod.secureConfig); + const rawConfig = await mod.sophon.getConfig(uri, mod.secureConfig); const config = JSON.parse(JSON.stringify(rawConfig)); return { config, diff --git a/files/mod.ts b/files/mod.ts index 7a04da23..44bfb4d7 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -46,7 +46,7 @@ import type { export type EnvDefArgs = { name: string; installs?: InstallConfigFat[]; - allowedPortDeps?: AllowedPortDep[]; + allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; /** * If true or not set, will base the task's env on top * of the default env (usually `main`). If false, will build on @@ -55,7 +55,7 @@ export type EnvDefArgs = { */ inherit?: string | boolean; desc?: string; - vars?: Record; + vars?: Record; /** * Task to execute when environment is activated. */ @@ -84,10 +84,10 @@ export type TaskFn = ( export type TaskDefArgs = { name?: string; desc?: string; - dependsOn?: string[]; + dependsOn?: string | string[]; workingDir?: string | Path; - envVars?: Record; - allowedPortDeps?: AllowedPortDep[]; + vars?: Record; + allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; installs?: InstallConfigFat[]; inherit?: string | boolean; }; @@ -160,10 +160,13 @@ export class Ghjkfile { logger(import.meta).debug("install added", config); } - setAllowedPortDeps(setId: string, deps: AllowedPortDep[]) { + setAllowedPortDeps( + setId: string, + deps: (InstallConfigFat | AllowedPortDep)[], + ) { const set = this.#getSet(setId); - set.allowedDeps = Object.fromEntries( - deps.map(( + set.allowedBuildDeps = Object.fromEntries( + reduceAllowedDeps(deps).map(( dep, ) => [dep.manifest.name, dep]), ); @@ -224,8 +227,8 @@ export class Ghjkfile { if (args.installs) { env.install(...args.installs); } - if (args.allowedPortDeps) { - env.allowedPortDeps(args.allowedPortDeps); + if (args.allowedBuildDeps) { + env.allowedBuildDeps(args.allowedBuildDeps); } if (args.desc) { env.desc(args.desc); @@ -259,22 +262,22 @@ export class Ghjkfile { } toConfig( - { defaultEnv, defaultBaseEnv, masterPortDepAllowList }: { + { defaultEnv, defaultBaseEnv }: { defaultEnv: string; defaultBaseEnv: string; ghjkfileUrl: string; - masterPortDepAllowList: AllowedPortDep[]; }, ) { + // make sure referenced envs exist + this.addEnv({ name: defaultEnv }); + this.addEnv({ name: defaultBaseEnv }); try { const envsConfig = this.#processEnvs(defaultEnv, defaultBaseEnv); const tasksConfig = this.#processTasks( envsConfig, defaultBaseEnv, ); - const portsConfig = this.#processInstalls( - masterPortDepAllowList ?? stdDeps(), - ); + const portsConfig = this.#processInstalls(); const config: SerializedConfig = { blackboard: Object.fromEntries(this.#bb.entries()), @@ -298,7 +301,7 @@ export class Ghjkfile { #getSet(setId: string) { let set = this.#installSets.get(setId); if (!set) { - set = { installs: [], allowedDeps: {} }; + set = { installs: [], allowedBuildDeps: {} }; this.#installSets.set(setId, set); } return set; @@ -333,7 +336,7 @@ export class Ghjkfile { const final = finalizer(); const envBaseResolved = typeof final.inherit === "string" ? final.inherit - : final.inherit && defaultBaseEnv != final.name + : (final.inherit !== false) && defaultBaseEnv != final.name ? defaultBaseEnv : null; all[final.name] = { ...final, envBaseResolved }; @@ -384,12 +387,12 @@ export class Ghjkfile { ]); installSet.installs = [...mergedInstallsSet.values()]; for ( - const [key, val] of Object.entries(baseSet.allowedDeps) + const [key, val] of Object.entries(baseSet.allowedBuildDeps) ) { // prefer the port dep config of the child over any // similar deps in the parent - if (!installSet.allowedDeps[key]) { - installSet.allowedDeps[key] = val; + if (!installSet.allowedBuildDeps[key]) { + installSet.allowedBuildDeps[key] = val; } } } @@ -408,10 +411,10 @@ export class Ghjkfile { }; const hooks = [ ...final.onEnterHookTasks.map( - (key) => [key, "hook.onEnter.posixExec"] as const, + (key) => [key, "hook.onEnter.ghjkTask"] as const, ), ...final.onExitHookTasks.map( - (key) => [key, "hook.onExit.posixExec"] as const, + (key) => [key, "hook.onExit.ghjkTask"] as const, ), ].map(([taskKey, ty]) => { const task = this.#tasks.get(taskKey); @@ -425,8 +428,7 @@ export class Ghjkfile { } if (task.ty == "denoFile@v1") { const prov: InlineTaskHookProvision = { - ty: "inline.hook.ghjkTask", - finalTy: ty, + ty, taskKey, }; return prov; @@ -494,9 +496,9 @@ export class Ghjkfile { ); for (const [key, args] of this.#tasks) { if (args.dependsOn && args.dependsOn.length > 0) { - const depKeys = args.dependsOn.map((nameOrKey) => - nameToKey[nameOrKey] ?? nameOrKey - ); + const depKeys = + (Array.isArray(args.dependsOn) ? args.dependsOn : [args.dependsOn]) + .map((nameOrKey) => nameToKey[nameOrKey] ?? nameOrKey); deps.set(key, depKeys); for (const depKey of depKeys) { const depRevDeps = revDeps.get(depKey); @@ -522,9 +524,21 @@ export class Ghjkfile { const args = this.#tasks.get(key)!; const { workingDir, desc, dependsOn, inherit } = args; + const taskEnvRecipe: EnvRecipe = { + provides: [], + }; + const taskInstallSet: InstallSet = { + installs: args.installs ?? [], + allowedBuildDeps: Object.fromEntries( + reduceAllowedDeps(args.allowedBuildDeps ?? []).map(( + dep, + ) => [dep.manifest.name, dep]), + ), + }; + const envBaseResolved = typeof inherit === "string" ? inherit - : inherit + : (inherit !== false) ? defaultBaseEnv : null; @@ -532,27 +546,23 @@ export class Ghjkfile { ? envsConfig.envs[envBaseResolved] : null; - const taskEnvRecipe: EnvRecipe = { - provides: [], - }; - - const taskInstallSet: InstallSet = { - installs: args.installs ?? [], - allowedDeps: Object.fromEntries( - (args.allowedPortDeps ?? []).map((dep) => [dep.manifest.name, dep]), - ), - }; - - const mergedEnvVars = args.envVars ?? {}; + const mergedEnvVars = args.vars ?? {}; if (envBaseRecipe) { for ( const prov of envBaseRecipe .provides as ( | WellKnownProvision | InstallSetRefProvision + | InlineTaskHookProvision )[] ) { - if (prov.ty == "posix.envVar") { + // task envs don't need hooks + if ( + prov.ty == "hook.onEnter.ghjkTask" || + prov.ty == "hook.onExit.ghjkTask" + ) { + continue; + } else if (prov.ty == "posix.envVar") { if (!mergedEnvVars[prov.key]) { mergedEnvVars[prov.key] = prov.val; } @@ -564,12 +574,12 @@ export class Ghjkfile { ]); taskInstallSet.installs = [...mergedInstallsSet.values()]; for ( - const [key, val] of Object.entries(baseSet.allowedDeps) + const [key, val] of Object.entries(baseSet.allowedBuildDeps) ) { // prefer the port dep config of the child over any // similar deps in the base - if (!taskInstallSet.allowedDeps[key]) { - taskInstallSet.allowedDeps[key] = val; + if (!taskInstallSet.allowedBuildDeps[key]) { + taskInstallSet.allowedBuildDeps[key] = val; } } } else { @@ -591,7 +601,11 @@ export class Ghjkfile { ...Object.entries(mergedEnvVars).map(( [key, val], ) => { - const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; + const prov: WellKnownProvision = { + ty: "posix.envVar", + key, + val: val.toString(), + }; return prov; }), ); @@ -606,9 +620,14 @@ export class Ghjkfile { ? workingDir.toString() : workingDir, desc, - dependsOn: dependsOn?.map((keyOrHash) => - localToFinalKey[nameToKey[keyOrHash] ?? keyOrHash] - ), + ...dependsOn + ? { + dependsOn: (Array.isArray(dependsOn) ? dependsOn : [dependsOn]) + ?.map((keyOrHash) => + localToFinalKey[nameToKey[keyOrHash] ?? keyOrHash] + ), + } + : {}, envHash, }; const taskHash = objectHash(def); @@ -670,12 +689,15 @@ export class Ghjkfile { env.provides = env.provides.map( (prov) => { if ( - prov.ty == "inline.hook.ghjkTask" + prov.ty == "hook.onEnter.ghjkTask" || + prov.ty == "hook.onExit.ghjkTask" ) { const inlineProv = prov as InlineTaskHookProvision; const taskKey = localToFinalKey[inlineProv.taskKey]; const out: WellKnownProvision = { - ty: inlineProv.finalTy, + ty: /onEnter/.test(prov.ty) + ? "hook.onEnter.posixExec" + : "hook.onExit.posixExec", program: "ghjk", arguments: ["x", taskKey], }; @@ -689,32 +711,17 @@ export class Ghjkfile { return moduleConfig; } - #processInstalls(masterAllowList: AllowedPortDep[]) { + #processInstalls() { const out: PortsModuleConfigHashed = { sets: {}, }; - const masterPortDepAllowList = Object.fromEntries( - masterAllowList.map((dep) => [dep.manifest.name, dep] as const), - ); for ( const [setId, set] of this.#installSets.entries() ) { - for (const [portName, _] of Object.entries(set.allowedDeps)) { - if (!masterPortDepAllowList[portName]) { - throw new Error( - `"${portName}" is in allowedPortDeps list of install set "${setId}" but not in the masterPortDepAllowList`, - ); - } - } - for (const [name, hash] of Object.entries(masterPortDepAllowList)) { - if (!set.allowedDeps[name]) { - set.allowedDeps[name] = hash; - } - } out.sets[setId] = { installs: set.installs.map((inst) => this.#addToBlackboard(inst)), allowedDeps: this.#addToBlackboard(Object.fromEntries( - Object.entries(set.allowedDeps).map( + Object.entries(set.allowedBuildDeps).map( ([key, dep]) => [key, this.#addToBlackboard(dep)], ), )), @@ -741,7 +748,7 @@ export class EnvBuilder { #installSetId: string; #file: Ghjkfile; #inherit: string | boolean = true; - #vars: Record = {}; + #vars: Record = {}; #desc?: string; #onEnterHookTasks: string[] = []; #onExitHookTasks: string[] = []; @@ -757,7 +764,9 @@ export class EnvBuilder { name: this.name, installSetId: this.#installSetId, inherit: this.#inherit, - vars: this.#vars, + vars: Object.fromEntries( + Object.entries(this.#vars).map(([key, val]) => [key, val.toString()]), + ), desc: this.#desc, onExitHookTasks: this.#onExitHookTasks, onEnterHookTasks: this.#onEnterHookTasks, @@ -780,9 +789,10 @@ export class EnvBuilder { } /** + * Configure the build time deps allowed to be used by ports. * This is treated as a single set and will replace previously any configured set. */ - allowedPortDeps(deps: AllowedPortDep[]) { + allowedBuildDeps(deps: (AllowedPortDep | InstallConfigFat)[]) { this.#file.setAllowedPortDeps(this.#installSetId, deps); return this; } @@ -798,7 +808,7 @@ export class EnvBuilder { /** * Add multiple environment variable. */ - vars(envVars: Record) { + vars(envVars: Record) { Object.assign(this.#vars, envVars); return this; } @@ -834,16 +844,10 @@ export function stdDeps(args = { enableRuntimes: false }) { ]; if (args.enableRuntimes) { out.push( - ...[ + ...reduceAllowedDeps([ node.default(), cpy.default(), - ].map((fatInst) => { - const out: AllowedPortDep = { - manifest: fatInst.port, - defaultInst: thinInstallConfig(fatInst), - }; - return portsValidators.allowedPortDep.parse(out); - }), + ]), ); } return out; @@ -870,9 +874,22 @@ function task$( } type InlineTaskHookProvision = Provision & { - ty: "inline.hook.ghjkTask"; - finalTy: - | "hook.onEnter.posixExec" - | "hook.onExit.posixExec"; + ty: "hook.onExit.ghjkTask" | "hook.onEnter.ghjkTask"; taskKey: string; }; + +export function reduceAllowedDeps( + deps: (AllowedPortDep | InstallConfigFat)[], +): AllowedPortDep[] { + return deps.map( + (dep: any) => { + const res = portsValidators.allowedPortDep.safeParse(dep); + if (res.success) return res.data; + const out: AllowedPortDep = { + manifest: dep.port, + defaultInst: thinInstallConfig(dep), + }; + return portsValidators.allowedPortDep.parse(out); + }, + ); +} diff --git a/ghjk.ts b/ghjk.ts index e5589430..8b41f5a4 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,7 +1,12 @@ -export { ghjk } from "./mod.ts"; -import { $, env, install, stdSecureConfig, task } from "./mod.ts"; +export { sophon } from "./hack.ts"; +import { config, env, install, stdDeps } from "./hack.ts"; import * as ports from "./ports/mod.ts"; +config({ + defaultBaseEnv: "test", + enableRuntimes: true, +}); + // these are just for quick testing install(); @@ -11,16 +16,3 @@ install( ports.pipi({ packageName: "pre-commit" })[0], ports.cpy_bs(), ); - -env("main") - .onEnter(task(($) => $`echo enter`)) - .onExit(task(($) => $`echo exit`)); - -env("test", { - installs: [ports.protoc()], -}); - -export const secureConfig = stdSecureConfig({ - enableRuntimes: true, - defaultBaseEnv: "test", -}); diff --git a/hack.ts b/hack.ts new file mode 100644 index 00000000..bb093358 --- /dev/null +++ b/hack.ts @@ -0,0 +1,84 @@ +//! This file allows an easy way to start with the typescript ghjkfile +//! but is generally insecure for serious usage. +//! +//! If your ghjkfile imports a malicious module, the module could +//! import the functions defined herin and mess with your ghjkfile. + +export * from "./mod.ts"; +import { file } from "./mod.ts"; +import logger from "./utils/logger.ts"; + +const ghjk = file(); + +export const sophon = Object.freeze(ghjk.sophon); +export const 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)); + +// capture exit fn to avoid malicous caller from +// changing it on Deno object +// WARN: the following capture only works if the +// hack.ts module is the first import +const exitFn = Deno.exit; +let firstCaller: string | undefined; + +/** + * The following wrapper kills the program if it detects callers to `fn` + * from more than one file. + * + * This is a weak hack to prevent malicous imported scripts from modify the ghjk config + * through the above functions. + */ +function firstCallerCheck any>(fn: F): F { + return ((...args) => { + const caller = getCaller(); + if (!caller) { + logger(import.meta).error( + `unable to detect \`hack.ts\` caller, no stack traces availaible`, + ); + // prefer exit of throw here since malicious user might catch it otherwise + exitFn(1); + } else if (firstCaller === undefined) { + firstCaller = caller; + } else if (caller !== firstCaller) { + logger(import.meta).error( + `new \`hack.ts\` caller detected: ${caller} != ${firstCaller}`, + ); + exitFn(1); + } + return fn(...args); + }) as F; +} + +// lifted from https://github.com/apiel/caller/blob/ead98/caller.ts +// MIT License 2020 Alexander Piel +interface Bind { + cb?: (file: string) => string; +} +function getCaller(this: Bind | any, levelUp = 3) { + const err = new Error(); + const stack = err.stack?.split("\n")[levelUp]; + if (stack) { + return getFile.bind(this)(stack); + } + function getFile(this: Bind | any, stack: string): string { + stack = stack.substring(stack.indexOf("at ") + 3); + if (!stack.startsWith("file://")) { + stack = stack.substring(stack.lastIndexOf("(") + 1); + } + const path = stack.split(":"); + let file; + if (Deno.build.os == "windows") { + file = `${path[0]}:${path[1]}:${path[2]}`; + } else { + file = `${path[0]}:${path[1]}`; + } + + if ((this as Bind)?.cb) { + const cb = (this as Bind).cb as any; + file = cb(file); + } + return file; + } +} diff --git a/mod.ts b/mod.ts index 23aa2837..275fe178 100644 --- a/mod.ts +++ b/mod.ts @@ -1,165 +1,264 @@ -//! This module is intended to be re-exported by `ghjk.ts` config scripts. Please -//! avoid importing elsewhere at it has side-effects. +//! This module is intended to be re-exported by `ghjk.ts` config scripts. // TODO: harden most of the items in here import "./setup_logger.ts"; -import { zod } from "./deps/common.ts"; // ports specific imports -import portsValidators from "./modules/ports/types.ts"; import type { AllowedPortDep, InstallConfigFat, } from "./modules/ports/types.ts"; import logger from "./utils/logger.ts"; -import { $, thinInstallConfig } from "./utils/mod.ts"; -import { EnvBuilder, Ghjkfile, stdDeps } from "./files/mod.ts"; +import { $ } from "./utils/mod.ts"; +import { + EnvBuilder, + Ghjkfile, + reduceAllowedDeps, + stdDeps, +} from "./files/mod.ts"; import type { DenoTaskDefArgs, EnvDefArgs, TaskFn } from "./files/mod.ts"; // WARN: this module has side-effects and only ever import // types from it import type { ExecTaskArgs } from "./modules/tasks/deno.ts"; -const DEFAULT_BASE_ENV_NAME = "main"; - -const file = new Ghjkfile(); -const mainEnv = file.addEnv({ - name: DEFAULT_BASE_ENV_NAME, - inherit: false, - allowedPortDeps: stdDeps(), - desc: "the default default environment.", -}); - export type { DenoTaskDefArgs, EnvDefArgs, TaskFn } from "./files/mod.ts"; -export { $, logger, stdDeps, stdSecureConfig }; - -// FIXME: ses.lockdown to freeze primoridials -// freeze the object to prevent malicious tampering of the secureConfig -export const ghjk = Object.freeze({ - getConfig: Object.freeze( - ( - ghjkfileUrl: string, - secureConfig: DenoFileSecureConfig | undefined, - ) => { - const defaultEnv = secureConfig?.defaultEnv ?? DEFAULT_BASE_ENV_NAME; - const defaultBaseEnv = secureConfig?.defaultBaseEnv ?? - DEFAULT_BASE_ENV_NAME; - return file.toConfig({ - defaultEnv, - defaultBaseEnv, - ghjkfileUrl, - masterPortDepAllowList: secureConfig?.masterPortDepAllowList ?? - stdDeps(), - }); - }, - ), - execTask: Object.freeze( - // TODO: do we need to source the default base env from - // the secure config here? - (args: ExecTaskArgs) => file.execTask(args), - ), -}); +export { $, logger, stdDeps }; -/* - * Provision a port install in the `main` environment. +export type AddEnv = { + (args: EnvDefArgs): EnvBuilder; + (name: string, args?: Omit): EnvBuilder; +}; + +/** + * Provision a port install in the `main` env. */ -export function install(...configs: InstallConfigFat[]) { - mainEnv.install(...configs); -} +export type AddInstall = { + (...configs: InstallConfigFat[]): void; +}; /** * Define and register a task. */ -export function task(args: DenoTaskDefArgs): string; -export function task(name: string, args: Omit): string; -export function task( - name: string, - fn: TaskFn, - args?: Omit, -): string; -export function task(fn: TaskFn, args?: Omit): string; -export function task( - nameOrArgsOrFn: string | DenoTaskDefArgs | TaskFn, - argsOrFn?: Omit | TaskFn, - argsMaybe?: Omit, -): string { - 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 file.addTask({ ...args, ty: "denoFile@v1" }); -} - -export function env(args: EnvDefArgs): EnvBuilder; -export function env(name: string, args?: Omit): EnvBuilder; -export function env( - nameOrArgs: string | EnvDefArgs, - argsMaybe?: Omit, -): EnvBuilder { - const args = typeof nameOrArgs == "object" - ? nameOrArgs - : { ...argsMaybe, name: nameOrArgs }; - return file.addEnv(args); -} - -const denoFileSecureConfig = zod.object({ - masterPortDepAllowList: zod.array(portsValidators.allowedPortDep).nullish(), - // TODO: move into envs/types - defaultEnv: zod.string().nullish(), - defaultBaseEnv: zod.string().nullish(), -}); -/* - * This is a secure sections of the config intended to be direct exports - * from the config script instead of the global variable approach the - * main [`GhjkConfig`] can take. - */ -export type DenoFileSecureConfig = zod.input< - typeof denoFileSecureConfig ->; -export type DenoFileSecureConfigX = zod.input< - typeof denoFileSecureConfig +export type AddTask = { + (args: DenoTaskDefArgs): string; + (name: string, args: Omit): string; + (fn: TaskFn, args?: Omit): string; + ( + name: string, + fn: TaskFn, + args?: Omit, + ): string; +}; + +export type FileArgs = { + /** + * The env to activate by default. When entering the working + * directory for example. + */ + defaultEnv?: string; + /** + * The default env all envs inherit from. + */ + defaultBaseEnv?: string; + /** + * Additional ports that can be used as build time dependencies. + * + * This applies to the "main" env. + */ + allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; + /** + * Wether or not use the default set of allowed build dependencies. + * If set, {@link enableRuntimes} is ignored but {@link allowedBuildDeps} + * is still respected. + * True by default. + * + * This applies to the "main" env. + */ + stdDeps?: boolean; + /** + * (unstable) Allow runtimes from std deps to be used as build time dependencies. + * + * This applies to the "main" env. + */ + enableRuntimes?: boolean; + /** + * Installs to add to the main env. + */ + installs?: InstallConfigFat[]; + /** + * Tasks to expose to the CLI. + */ + tasks?: DenoTaskDefArgs[]; + /** + * Different envs availaible to the CLI. + */ + envs?: EnvDefArgs[]; +}; + +type SecureConfigArgs = Omit< + FileArgs, + "envs" | "tasks" | "installs" >; -function stdSecureConfig( - args: { - additionalAllowedPorts?: (InstallConfigFat | AllowedPortDep)[]; - enableRuntimes?: boolean; - } & Pick, -) { - const { additionalAllowedPorts, enableRuntimes = false } = args; - const out: DenoFileSecureConfig = { - ...args, - masterPortDepAllowList: [ - ...stdDeps({ enableRuntimes }), - ...additionalAllowedPorts?.map( - (dep: any) => { - const res = portsValidators.allowedPortDep.safeParse(dep); - if (res.success) return res.data; - const out: AllowedPortDep = { - manifest: dep.port, - defaultInst: thinInstallConfig(dep), - }; - return portsValidators.allowedPortDep.parse(out); - }, - ) ?? [], - ], +type DenoFileKnobs = { + sophon: Readonly; + /** + * {@inheritdoc AddInstall} + */ + install: AddInstall; + /** + * {@inheritdoc AddTask} + */ + task: AddTask; + /** + * {@inheritDoc AddEnv} + */ + env: AddEnv; + /** + * Configure global and miscallenous ghjk settings. + */ + config(args: SecureConfigArgs): void; +}; + +export const file = Object.freeze(function file( + args: FileArgs = {}, +): DenoFileKnobs { + const defaultBuildDepsSet: AllowedPortDep[] = []; + + const DEFAULT_BASE_ENV_NAME = "main"; + + const builder = new Ghjkfile(); + const mainEnv = builder.addEnv({ + name: DEFAULT_BASE_ENV_NAME, + inherit: false, + installs: args.installs, + desc: "the default default environment.", + }); + + // this replaces the allowedBuildDeps contents according to the + // args. Written to be called multilple times to allow + // replacement. + const replaceDefaultBuildDeps = (args: SecureConfigArgs) => { + // empty out the array first + defaultBuildDepsSet.length = 0; + defaultBuildDepsSet.push( + ...reduceAllowedDeps(args.allowedBuildDeps ?? []), + ); + const seenPorts = new Set( + defaultBuildDepsSet.map((dep) => dep.manifest.name), + ); + // if the user explicitly passes a port config, we let + // it override any ports of the same kind from the std library + for ( + const dep of args.stdDeps !== false // note: this is true if it's undefined + ? stdDeps({ enableRuntimes: args.enableRuntimes ?? false }) + : [] + ) { + if (seenPorts.has(dep.manifest.name)) { + continue; + } + defaultBuildDepsSet.push(dep); + } + mainEnv.allowedBuildDeps(defaultBuildDepsSet); }; - return out; -} + + // populate the bulid deps by the default args first + replaceDefaultBuildDeps(args); + + for (const env of args.envs ?? []) { + builder.addEnv(env); + } + for (const task of args.tasks ?? []) { + builder.addTask({ ...task, ty: "denoFile@v1" }); + } + + // FIXME: ses.lockdown to freeze primoridials + // freeze the object to prevent malicious tampering of the secureConfig + const sophon = Object.freeze({ + getConfig: Object.freeze( + ( + ghjkfileUrl: string, + ) => { + return builder.toConfig({ + ghjkfileUrl, + defaultEnv: args.defaultEnv ?? DEFAULT_BASE_ENV_NAME, + defaultBaseEnv: args.defaultBaseEnv ?? + DEFAULT_BASE_ENV_NAME, + }); + }, + ), + execTask: Object.freeze( + // TODO: do we need to source the default base env from + // the secure config here? + (args: ExecTaskArgs) => builder.execTask(args), + ), + }); + + // we return a bunch of functions here + // to ease configuring the main environment + // including overloads + return { + sophon, + + install(...configs: InstallConfigFat[]) { + mainEnv.install(...configs); + }, + + 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" }); + }, + + env( + nameOrArgs: string | EnvDefArgs, + argsMaybe?: Omit, + ) { + const args = typeof nameOrArgs == "object" + ? nameOrArgs + : { ...argsMaybe, name: nameOrArgs }; + return builder.addEnv(args); + }, + + config( + { defaultBaseEnv, defaultEnv, ...rest }: SecureConfigArgs, + ) { + if ( + rest.enableRuntimes !== undefined || + rest.allowedBuildDeps !== undefined || + rest.stdDeps !== undefined + ) { + replaceDefaultBuildDeps(rest); + } + // NOTE:we're deep mutating the first args from above + args = { + ...rest, + ...{ defaultEnv, defaultBaseEnv }, + }; + }, + }; +}); diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index 6fa84b54..fad8d558 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -83,7 +83,7 @@ export class PortsModule extends ModuleBase { bb[hashedSet.allowedDeps], ), ); - const allowedDeps = Object.fromEntries( + const allowedBuildDeps = Object.fromEntries( Object.entries(allowedDepSetHashed).map(( [key, hash], ) => [ @@ -93,7 +93,7 @@ export class PortsModule extends ModuleBase { ); const set: InstallSetX = { installs, - allowedDeps, + allowedBuildDeps, }; pcx.config.sets[id] = set; setStore.set(id, set); @@ -158,7 +158,7 @@ export class PortsModule extends ModuleBase { const currInstallSetId = getActiveEnvInstallSetId(envsCtx); const currInstallSet = installSets[currInstallSetId]; - const allowedDeps = currInstallSet.allowedDeps; + const allowedDeps = currInstallSet.allowedBuildDeps; const rows = []; const { diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index 9c565143..f3cedaf9 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -338,7 +338,7 @@ export async function buildInstallGraph( }); const resolvedConfig = await resolveConfig( scx, - set.allowedDeps, + set.allowedBuildDeps, manifest, instLite, ); @@ -386,7 +386,7 @@ export async function buildInstallGraph( // this goes into graph.depEdges const deps: [string, string][] = []; for (const depId of manifest.buildDeps) { - const { manifest: depPort } = set.allowedDeps[depId.name]; + const { manifest: depPort } = set.allowedBuildDeps[depId.name]; if (!depPort) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, @@ -570,19 +570,19 @@ function resolveConfig( // for the portsConfig.allowedDeps // No version resolution takes place export function getDepConfig( - allowedDeps: Record, + allowedBuildDeps: Record, manifest: PortManifestX, config: InstallConfigLiteX, depId: PortDep, resolutionDep = false, ) { - const { manifest: depPort, defaultInst: defaultDepInstall } = - allowedDeps[depId.name]; - if (!depPort) { + const dep = allowedBuildDeps[depId.name]; + if (!dep) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, ); } + const { manifest: depPort, defaultInst: defaultDepInstall } = dep; // install configuration of an allowed dep port // can be overriden by dependent ports const res = validators.installConfigLite.safeParse( diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 922d8d65..1ca43480 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -156,7 +156,7 @@ const installSetHashed = zod.object({ const installSet = zod.object({ installs: zod.array(installConfigFat), - allowedDeps: allowDepSet, + allowedBuildDeps: allowDepSet, }); const portsModuleConfigHashed = zod.object({ diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index e3c0003d..fe246012 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -61,7 +61,7 @@ async function importAndExec( args: ExecTaskArgs, ) { const mod = await import(uri); - await mod.ghjk.execTask(args); + await mod.sophon.execTask(args); return true; } diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts index 1be633b8..3056abbe 100644 --- a/ports/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -7,7 +7,6 @@ import { InstallArgs, InstallConfigSimple, osXarch, - std_fs, std_path, unarchive, } from "../port.ts"; @@ -75,16 +74,18 @@ export class Port extends GithubReleasePort { await unarchive(fileDwnPath, args.tmpDirPath); + const tmpDir = $.path(args.tmpDirPath); + await tmpDir.join("bin").ensureDir(); + for ( + const fileName of ["cargo-binstall", "detect-targets", "detect-wasi"] + ) { + await tmpDir.join(fileName).renameToDir(tmpDir.join("bin")); + } + const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); } - - const neededFileNames = ["cargo-binstall", "detect-targets", "detect-wasi"]; - const destination = std_path.resolve(args.installPath, "bin"); - for (const fileName of neededFileNames) { - const sourceFile = std_path.resolve(args.tmpDirPath, fileName); - await std_fs.copy(sourceFile, destination); - } + await tmpDir.rename(installPath); } } diff --git a/tests/envHooks.ts b/tests/envHooks.ts index cf20e562..be0e1d60 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -59,7 +59,9 @@ const cases: CustomE2eTestCase[] = [ // -s: read from stdin // -l: login mode // -i: make it interactive - ePoint: `bash -sil`, + ePoint: Deno.env.get("GHJK_TEST_E2E_TYPE") == "local" + ? `bash --rcfile $BASH_ENV -si` // we don't want to use the system rcfile + : `bash -sil`, stdin: posixInteractiveScript, }, { @@ -78,8 +80,8 @@ const cases: CustomE2eTestCase[] = [ harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: ` -export { ghjk } from "$ghjk/mod.ts"; -import { task, env } from "$ghjk/mod.ts"; +export { sophon } from "$ghjk/hack.ts"; +import { task, env } from "$ghjk/hack.ts"; env("main") .onEnter(task($ => $\`/bin/sh -c 'echo remark > marker'\`)) diff --git a/tests/envs.ts b/tests/envs.ts index 0be9b369..343c1b7e 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -5,9 +5,8 @@ import { genTsGhjkFile, harness, } from "./utils.ts"; -import { stdSecureConfig } from "../mod.ts"; import dummy from "../ports/dummy.ts"; -import type { DenoFileSecureConfig } from "../mod.ts"; +import type { FileArgs } from "../mod.ts"; type CustomE2eTestCase = & Omit @@ -18,7 +17,7 @@ type CustomE2eTestCase = & ( | { envs: EnvDefArgs[]; - secureConfig?: DenoFileSecureConfig; + secureConfig?: FileArgs; } | { ghjkTs: string; @@ -184,7 +183,7 @@ const cases: CustomE2eTestCase[] = [ name: "default_env_loader", ePoint: "fish", envs: envVarTestEnvs, - secureConfig: stdSecureConfig({ defaultEnv: "yuki" }), + secureConfig: { defaultEnv: "yuki" }, stdin: ` set fish_trace 1 # env base is false for "yuki" and thus no vars from "main" @@ -198,7 +197,12 @@ test "$HUMM" = "Soul Lady"; or exit 108 harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: "ghjkTs" in testCase ? testCase.ghjkTs : genTsGhjkFile( - { envDefs: testCase.envs, secureConf: testCase.secureConfig }, + { + secureConf: { + ...testCase.secureConfig, + envs: testCase.envs, + }, + }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], name: `envs/${testCase.name}`, diff --git a/tests/ports.ts b/tests/ports.ts index 0a312dd7..9e5f34a7 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -1,5 +1,5 @@ import "../setup_logger.ts"; -import { DenoFileSecureConfig, stdSecureConfig } from "../mod.ts"; +import { FileArgs } from "../mod.ts"; import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; import * as ports from "../ports/mod.ts"; import dummy from "../ports/dummy.ts"; @@ -9,7 +9,7 @@ import { testTargetPlatform } from "./utils.ts"; type CustomE2eTestCase = Omit & { ePoint: string; installConf: InstallConfigFat | InstallConfigFat[]; - secureConf?: DenoFileSecureConfig; + secureConf?: FileArgs; }; // order tests by download size to make failed runs less expensive const cases: CustomE2eTestCase[] = [ @@ -97,18 +97,18 @@ const cases: CustomE2eTestCase[] = [ name: "npmi-node-gyp", installConf: ports.npmi({ packageName: "node-gyp" }), ePoint: `node-gyp --version`, - secureConf: stdSecureConfig({ + secureConf: { enableRuntimes: true, - }), + }, }, // node + more megs { name: "npmi-jco", installConf: ports.npmi({ packageName: "@bytecodealliance/jco" }), ePoint: `jco --version`, - secureConf: stdSecureConfig({ + secureConf: { enableRuntimes: true, - }), + }, }, // 42 megs { @@ -159,6 +159,9 @@ const cases: CustomE2eTestCase[] = [ name: "pipi-poetry", installConf: ports.pipi({ packageName: "poetry" }), ePoint: `poetry --version`, + secureConf: { + enableRuntimes: true, + }, }, // rustup + 600 megs { @@ -199,9 +202,12 @@ harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: genTsGhjkFile( { - installConf: testCase.installConf, - secureConf: testCase.secureConf, - taskDefs: [], + secureConf: { + ...testCase.secureConf, + installs: Array.isArray(testCase.installConf) + ? testCase.installConf + : [testCase.installConf], + }, }, ), ePoints: [ @@ -209,14 +215,14 @@ harness(cases.map((testCase) => ({ cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], })), /* // FIXME: better tests for the `InstallDb` - // installs db means this shouldn't take too long - // as it's the second sync - { - cmd: [ - ..."env".split(" "), - "bash -c 'timeout 1 ghjk envs cook'", - ], - }, */ + // installs db means this shouldn't take too long + // as it's the second sync + { + cmd: [ + ..."env".split(" "), + "bash -c 'timeout 1 ghjk envs cook'", + ], + }, */ ], name: `ports/${testCase.name}`, }))); diff --git a/tests/modules/ports/portsOutdatedTest.ts b/tests/portsOutdatedTest.ts similarity index 70% rename from tests/modules/ports/portsOutdatedTest.ts rename to tests/portsOutdatedTest.ts index bc28e5f6..ef9eaadf 100644 --- a/tests/modules/ports/portsOutdatedTest.ts +++ b/tests/portsOutdatedTest.ts @@ -1,19 +1,18 @@ -import "../../../setup_logger.ts"; -import { DenoFileSecureConfig, stdSecureConfig } from "../../../mod.ts"; -import { E2eTestCase, genTsGhjkFile, harness } from "../../utils.ts"; -import * as ports from "../../../ports/mod.ts"; -import type { InstallConfigFat } from "../../../modules/ports/types.ts"; +import "../setup_logger.ts"; +import { E2eTestCase, genTsGhjkFile, harness } from "./utils.ts"; +import * as ports from "../ports/mod.ts"; +import type { InstallConfigFat } from "../modules/ports/types.ts"; +import { FileArgs } from "../mod.ts"; type CustomE2eTestCase = Omit & { ePoint: string; installConf: InstallConfigFat | InstallConfigFat[]; - secureConf?: DenoFileSecureConfig; + secureConf?: FileArgs; }; const cases: CustomE2eTestCase[] = [ - // 0 megs { - name: "check ports outdated", + name: "ports_outdated", installConf: [ ports.jq_ghrel(), ports.protoc(), @@ -23,12 +22,12 @@ const cases: CustomE2eTestCase[] = [ ...ports.pipi({ packageName: "poetry" }), ], ePoint: `ghjk p outdated`, - secureConf: stdSecureConfig({ + secureConf: { enableRuntimes: true, - }), + }, }, { - name: "check ports outdated", + name: "ports_outdated_update_all", installConf: [ ports.jq_ghrel(), ports.protoc(), @@ -38,9 +37,9 @@ const cases: CustomE2eTestCase[] = [ ...ports.pipi({ packageName: "poetry" }), ], ePoint: `ghjk p outdated --update-all`, - secureConf: stdSecureConfig({ + secureConf: { enableRuntimes: true, - }), + }, }, ]; @@ -48,9 +47,12 @@ harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: genTsGhjkFile( { - installConf: testCase.installConf, - secureConf: testCase.secureConf, - taskDefs: [], + secureConf: { + ...testCase.secureConf, + installs: Array.isArray(testCase.installConf) + ? testCase.installConf + : [testCase.installConf], + }, }, ), ePoints: [ @@ -71,5 +73,5 @@ harness(cases.map((testCase) => ({ // but we don't want some bug spinlocking the ci for // an hour timeout_ms: 5 * 60 * 1000, - name: `ports/${testCase.name}`, + name: `portsOutdated/${testCase.name}`, }))); diff --git a/tests/reloadHooks.ts b/tests/reloadHooks.ts index b26f1e51..71482f9e 100644 --- a/tests/reloadHooks.ts +++ b/tests/reloadHooks.ts @@ -164,7 +164,9 @@ const cases: CustomE2eTestCase[] = [ // -s: read from stdin // -l: login mode // -i: interactive mode - ePoint: `bash -sli`, + ePoint: Deno.env.get("GHJK_TEST_E2E_TYPE") == "local" + ? `bash --rcfile $BASH_ENV -si` // we don't want to use the system rcfile + : `bash -sil`, stdin: posixInteractiveScript, }, { @@ -202,15 +204,17 @@ harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: genTsGhjkFile( { - envDefs: [ - { - name: "main", - installs: testCase.installConf ? testCase.installConf : [dummy()], - }, - { - name: "test", - }, - ], + secureConf: { + envs: [ + { + name: "main", + installs: testCase.installConf ? testCase.installConf : [dummy()], + }, + { + name: "test", + }, + ], + }, }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], diff --git a/tests/tasks.ts b/tests/tasks.ts index cb8cf352..c85f8bf1 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -2,7 +2,6 @@ import "../setup_logger.ts"; import { E2eTestCase, genTsGhjkFile, harness, type TaskDef } from "./utils.ts"; import * as ghjk from "../mod.ts"; import * as ports from "../ports/mod.ts"; -import { stdSecureConfig } from "../mod.ts"; type CustomE2eTestCase = & Omit @@ -36,7 +35,7 @@ test (ghjk x greet world) = "Hello world from $PWD!"`, name: "env_vars", tasks: [{ name: "greet", - envVars: { + vars: { LUNA: "moon", SOL: "sun", }, @@ -67,7 +66,7 @@ ghjk x protoc`, name: "test", // pipi depends on cpy_bs installs: [...ports.pipi({ packageName: "pre-commit" })], - allowedPortDeps: ghjk.stdDeps({ enableRuntimes: true }), + allowedBuildDeps: ghjk.stdDeps({ enableRuntimes: true }), fn: async ($) => { await $`pre-commit --version`; }, @@ -123,8 +122,8 @@ test (cat eddy) = 'ed edd eddy' { name: "anon", ghjkTs: ` -export { ghjk } from "$ghjk/mod.ts"; -import { task } from "$ghjk/mod.ts"; +export { sophon } from "$ghjk/hack.ts"; +import { task } from "$ghjk/hack.ts"; task({ dependsOn: [ @@ -151,10 +150,10 @@ harness(cases.map((testCase) => ({ ...testCase, tsGhjkfileStr: "ghjkTs" in testCase ? testCase.ghjkTs : genTsGhjkFile( { - taskDefs: testCase.tasks, - secureConf: stdSecureConfig({ + secureConf: { + tasks: testCase.tasks, enableRuntimes: testCase.enableRuntimesOnMasterPDAL, - }), + }, }, ), ePoints: [{ cmd: testCase.ePoint, stdin: testCase.stdin }], diff --git a/tests/utils.ts b/tests/utils.ts index ad15166a..dc67c40a 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -2,13 +2,8 @@ import { defaultInstallArgs, install } from "../install/mod.ts"; import { std_url } from "../deps/dev.ts"; import { std_async } from "../deps/dev.ts"; import { $, dbg, importRaw } from "../utils/mod.ts"; -import type { InstallConfigFat } from "../modules/ports/types.ts"; import logger from "../utils/logger.ts"; -import type { - DenoFileSecureConfig, - DenoTaskDefArgs, - EnvDefArgs, -} from "../mod.ts"; +import type { DenoTaskDefArgs, FileArgs } from "../mod.ts"; export type { EnvDefArgs } from "../mod.ts"; import { ALL_OS } from "../port.ts"; import { ALL_ARCH } from "../port.ts"; @@ -181,79 +176,52 @@ export type TaskDef = & Required>; export function genTsGhjkFile( - { installConf, secureConf, taskDefs, envDefs }: { - installConf?: InstallConfigFat | InstallConfigFat[]; - secureConf?: DenoFileSecureConfig; - taskDefs?: TaskDef[]; - envDefs?: EnvDefArgs[]; + { secureConf }: { + secureConf?: FileArgs; }, ) { - const installConfArray = installConf - ? Array.isArray(installConf) ? installConf : [installConf] - : []; - - const serializedPortsInsts = JSON.stringify( - installConfArray, - (_, val) => - typeof val == "string" - // we need to escape a json string embedded in a js string - // 2x - ? val.replaceAll(/\\/g, "\\\\") - : val, - ); - const serializedSecConf = JSON.stringify( // undefined is not recognized by JSON.parse // so we stub it with null - secureConf ?? null, + { + ...secureConf, + tasks: [], + }, + // we need to escape a json string embedded in a js string + // 2x (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, + 2, ); - const tasks = (taskDefs ?? []).map( + const tasks = (secureConf?.tasks ?? []).map( (def) => { const stringifiedSection = JSON.stringify( def, (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, + 2, ); return $.dedent` ghjk.task({ ...JSON.parse(\`${stringifiedSection}\`), - fn: ${def.fn.toString()} - })`; - }, - ).join("\n"); - - const envs = (envDefs ?? []).map( - (def) => { - const stringifiedSection = JSON.stringify( - def, - (_, val) => - typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, - ); - return $.dedent` - ghjk.env({ - ...JSON.parse(\`${stringifiedSection}\`), + fn: ${def.fn?.toString()} })`; }, ).join("\n"); return ` -export { ghjk } from "$ghjk/mod.ts"; -import * as ghjk from "$ghjk/mod.ts"; +import { file } from "$ghjk/mod.ts"; + const confStr = \` -${serializedPortsInsts} +${serializedSecConf} \`; const confObj = JSON.parse(confStr); -ghjk.install(...confObj) +const ghjk = file(confObj); -const secConfStr = \` -${serializedSecConf} -\`; -export const secureConfig = JSON.parse(secConfStr); +export const sophon = ghjk.sophon; ${tasks} -${envs} + `; } diff --git a/utils/mod.ts b/utils/mod.ts index df1f8a6b..f9659be4 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -196,6 +196,19 @@ export const $ = dax.build$( requestBuilder: new dax.RequestBuilder() .showProgress(Deno.stderr.isTerminal()), extras: { + mapObject< + O, + V2, + >( + obj: O, + map: (key: keyof O, val: O[keyof O]) => [string, V2], + ): Record { + return Object.fromEntries( + Object.entries(obj as object).map(([key, val]) => + map(key as keyof O, val as O[keyof O]) + ), + ); + }, exponentialBackoff(initialDelayMs: number) { let delay = initialDelayMs; let attempt = 0; From 8cbcb3d321d3dacb82f8f682a9f6fa919c1a74b5 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 02:21:54 +0300 Subject: [PATCH 16/21] feat!: metatype integration polish (#91) * feat: multipl env inheritance * refactor: handle GHJK_CLEANUP_POSIX edgecases * feat: task on task inheritance * fix: bugs! * feat(port): `deno_ghrel` * fix: `portsOutdated` tests * fix: diamond inheritance bug * small stuff * fix: apply llm feedback * fix: minor fixes * fix: path var merging * feat: `std.ts` and `sedLock` * fix: task path combinations * fix: task path combinations 2 --- .ghjk/deno.lock | 495 ++++++++++++++ .ghjk/lock.json | 79 ++- .github/workflows/nightly.yml | 2 +- .github/workflows/tests.yml | 4 +- deno.lock | 156 ++++- deps/cli.ts | 1 - deps/common.ts | 3 +- examples/kitchen/ghjk.ts | 24 +- files/deno/worker.ts | 155 +---- files/mod.ts | 641 ++++++++++++------ ghjk.ts | 45 +- host/mod.ts | 108 ++- host/types.ts | 1 - install.sh | 4 +- install/hook.fish | 8 +- install/hook.sh | 3 + main.ts | 6 + mod.ts | 54 +- modules/envs/inter.ts | 26 + modules/envs/mod.ts | 161 ++++- modules/envs/posix.ts | 118 +++- modules/envs/reducer.ts | 4 +- modules/envs/types.ts | 7 +- modules/ports/inter.ts | 24 +- modules/ports/mod.ts | 50 +- modules/ports/reducers.ts | 6 +- modules/ports/sync.ts | 18 +- modules/ports/types.ts | 5 +- modules/tasks/deno.ts | 2 + modules/tasks/exec.ts | 42 +- modules/tasks/inter.ts | 25 + modules/tasks/mod.ts | 55 +- modules/tasks/types.ts | 5 +- modules/types.ts | 5 +- modules/utils.ts | 93 --- ports/act.ts | 16 +- ports/cargo-binstall.ts | 4 +- ports/deno_ghrel.ts | 76 +++ ports/mod.ts | 1 + ports/temporal_cli.ts | 16 +- std.ts | 2 + std/copyLock.ts | 46 ++ std/sedLock.ts | 111 +++ tests/envHooks.ts | 1 + tests/envs.ts | 126 +++- tests/ports.ts | 6 + ...{portsOutdatedTest.ts => portsOutdated.ts} | 33 +- tests/reloadHooks.ts | 34 +- tests/tasks.ts | 4 +- tests/test.Dockerfile | 5 +- tests/utils.ts | 9 +- utils/mod.ts | 58 +- utils/worker.ts | 153 +++++ 53 files changed, 2344 insertions(+), 792 deletions(-) create mode 100644 .ghjk/deno.lock create mode 100644 modules/envs/inter.ts create mode 100644 modules/tasks/inter.ts delete mode 100644 modules/utils.ts create mode 100644 ports/deno_ghrel.ts create mode 100644 std.ts create mode 100644 std/copyLock.ts create mode 100644 std/sedLock.ts rename tests/{portsOutdatedTest.ts => portsOutdated.ts} (55%) create mode 100644 utils/worker.ts diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock new file mode 100644 index 00000000..7d4c9995 --- /dev/null +++ b/.ghjk/deno.lock @@ -0,0 +1,495 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@david/dax@0.41.0": "jsr:@david/dax@0.41.0", + "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", + "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@3.3.0": "npm:zod-validation-error@3.3.0_zod@3.23.8", + "npm:zod@3.23.8": "npm:zod@3.23.8" + }, + "jsr": { + "@david/dax@0.41.0": { + "integrity": "9e1ecf66a0415962cc8ad3ba4e3fa93ce0f1a1cc797dd95c36fdfb6977dc7fc8", + "dependencies": [ + "jsr:@david/which@^0.4.1", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@david/which@0.4.1": { + "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/bytes@0.221.0": { + "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/io@0.221.0": { + "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/bytes@^0.221.0" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + }, + "@std/streams@0.221.0": { + "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", + "dependencies": [ + "jsr:@std/io@^0.221.0" + ] + } + }, + "npm": { + "@noble/hashes@1.4.0": { + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dependencies": {} + }, + "multiformats@13.1.0": { + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==", + "dependencies": {} + }, + "zod-validation-error@3.3.0_zod@3.23.8": { + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "dependencies": { + "zod": "zod@3.23.8" + } + }, + "zod@3.23.8": { + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dependencies": {} + } + } + }, + "remote": { + "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", + "https://deno.land/std@0.116.0/fs/walk.ts": "31464d75099aa3fc7764212576a8772dfabb2692783e6eabb910f874a26eac54", + "https://deno.land/std@0.116.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", + "https://deno.land/std@0.116.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", + "https://deno.land/std@0.116.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", + "https://deno.land/std@0.116.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", + "https://deno.land/std@0.116.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820", + "https://deno.land/std@0.116.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", + "https://deno.land/std@0.116.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", + "https://deno.land/std@0.116.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", + "https://deno.land/std@0.116.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", + "https://deno.land/std@0.213.0/archive/_common.ts": "85edd5cdd4324833f613c1bc055f8e2f935cc9229c6b3044421268d9959997ef", + "https://deno.land/std@0.213.0/archive/untar.ts": "7677c136f2188cd8c33363ccaaee6e77d4ca656cca3e2093d08de8f294d4353d", + "https://deno.land/std@0.213.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", + "https://deno.land/std@0.213.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", + "https://deno.land/std@0.213.0/bytes/concat.ts": "9cac3b4376afbef98ff03588eb3cf948e0d1eb6c27cfe81a7651ab6dd3adc54a", + "https://deno.land/std@0.213.0/bytes/copy.ts": "f29c03168853720dfe82eaa57793d0b9e3543ebfe5306684182f0f1e3bfd422a", + "https://deno.land/std@0.213.0/fmt/colors.ts": "aeaee795471b56fc62a3cb2e174ed33e91551b535f44677f6320336aabb54fbb", + "https://deno.land/std@0.213.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", + "https://deno.land/std@0.213.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.213.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", + "https://deno.land/std@0.213.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", + "https://deno.land/std@0.213.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", + "https://deno.land/std@0.213.0/fs/copy.ts": "dc0f68c4b6c3b090bfdb909387e309f6169b746bd713927c9507c9ef545d71f6", + "https://deno.land/std@0.213.0/fs/empty_dir.ts": "4f01e6d56e2aa8d90ad60f20bc25601f516b00f6c3044cdf6863a058791d91aa", + "https://deno.land/std@0.213.0/fs/ensure_dir.ts": "dffff68de0d10799b5aa9e39dec4e327e12bbd29e762292193684542648c4aeb", + "https://deno.land/std@0.213.0/fs/ensure_file.ts": "ac5cfde94786b0284d2c8e9f7f9425269bea1b2140612b4aea1f20b508870f59", + "https://deno.land/std@0.213.0/fs/ensure_link.ts": "d42af2edefeaa9817873ec6e46dc5d209ac4d744f8c69c5ecc2dffade78465b6", + "https://deno.land/std@0.213.0/fs/ensure_symlink.ts": "aee3f1655700f60090b4a3037f5b6c07ab37c36807cccad746ce89987719e6d2", + "https://deno.land/std@0.213.0/fs/eol.ts": "c9807291f78361d49fd986a9be04654610c615c5e2ec63d748976197d30ff206", + "https://deno.land/std@0.213.0/fs/exists.ts": "d2757ef764eaf5c6c5af7228e8447db2de42ab084a2dae540097f905723d83f5", + "https://deno.land/std@0.213.0/fs/expand_glob.ts": "a64e4ab51f62780f764789c9cdeacc862d221e35207fb81170a980ccc22868e3", + "https://deno.land/std@0.213.0/fs/mod.ts": "107f5afa4424c2d3ce2f7e9266173198da30302c69af662c720115fe504dc5ee", + "https://deno.land/std@0.213.0/fs/move.ts": "39e0d7ccb88a566d20b949712020e766b15ef1ec19159573d11f949bd677909c", + "https://deno.land/std@0.213.0/fs/walk.ts": "f04cc83ad3b27b5a5d078c831a01c7406069474bf280d5db015d937149a60128", + "https://deno.land/std@0.213.0/internal/warn_on_deprecated_api.ts": "0708590b803a3c4462bbd89ee8b9a1b3fe941a7679ee3cfc332227a69b5c36f1", + "https://deno.land/std@0.213.0/io/_common.ts": "36705cdb4dfcd338d6131bca1b16e48a4d5bf0d1dada6ce397268e88c17a5835", + "https://deno.land/std@0.213.0/io/_constants.ts": "3c7ad4695832e6e4a32e35f218c70376b62bc78621ef069a4a0a3d55739f8856", + "https://deno.land/std@0.213.0/io/buf_reader.ts": "ccbd43ace0a9eebbd5e1b4765724b79da79d1e28b90c2b08537b99192da4a1f7", + "https://deno.land/std@0.213.0/io/buf_writer.ts": "bf68b9c74b1bccf51b9960c54db5eec60e7e3d922c7c62781b0d3971770021ba", + "https://deno.land/std@0.213.0/io/buffer.ts": "79182995c8340ece2fa8763a8da86d282c507e854921d0a4c2ba7425c63609ef", + "https://deno.land/std@0.213.0/io/copy.ts": "63c6a4acf71fb1e89f5e47a7b3b2972f9d2c56dd645560975ead72db7eb23f61", + "https://deno.land/std@0.213.0/io/copy_n.ts": "e4a169b8965b69e6a05175d06bf14565caa91266143ec895e54e95b6cdb27cf2", + "https://deno.land/std@0.213.0/io/limited_reader.ts": "2b3e6c2d134bbbabbc918584db5fd2f8b21091843357f75af0d9f262cb5c94c1", + "https://deno.land/std@0.213.0/io/mod.ts": "571384032c5f60530542a28f2e8b0e73e47e87eca77056ba7e2363f4d4a4573a", + "https://deno.land/std@0.213.0/io/multi_reader.ts": "ca8a7813208a3393dfaed75894d955fe58a38c21b880e69839a4e0547eadbf61", + "https://deno.land/std@0.213.0/io/read_all.ts": "876c1cb20adea15349c72afc86cecd3573335845ae778967aefb5e55fe5a8a4a", + "https://deno.land/std@0.213.0/io/read_delim.ts": "fb0884d97adc398877c6f59e1d1450be12e078790f52845fae7876dc119bb8f6", + "https://deno.land/std@0.213.0/io/read_int.ts": "6ada4e0eec5044982df530e4de804e32ae757a2c318b57eba622d893841ffe2a", + "https://deno.land/std@0.213.0/io/read_lines.ts": "34555eaa25269f6cfb9a842a03daedc9eae4f8295c8f933bd2b1639274ce89e3", + "https://deno.land/std@0.213.0/io/read_long.ts": "199cba44526464f8499e1f3d96008d513bcadc8e5665356a9b84425cac6b16ad", + "https://deno.land/std@0.213.0/io/read_range.ts": "a0c930ea61fdc3ea5520be4df34a7927fe8a2d6da9b04bfaa7b9588ef2e1a718", + "https://deno.land/std@0.213.0/io/read_short.ts": "73777709ad41b6faeff3638c275a329cc820c1082f4dad07909f48875a35a71d", + "https://deno.land/std@0.213.0/io/read_string_delim.ts": "8c604ceea5c3c7ab244583570b467ce194238ace6d49b1d47f25d4f75de86d59", + "https://deno.land/std@0.213.0/io/slice_long_to_bytes.ts": "9769174a8f3b4449f1e1af1a79f78e58ef84d0aaf2f457e1fdc31a01f92439b7", + "https://deno.land/std@0.213.0/io/string_reader.ts": "b0176211e61e235a684abef722e7ecc7a6481238ba264f1a7b199b8a1d2a62f5", + "https://deno.land/std@0.213.0/io/string_writer.ts": "4fe4dcbdadff11c726bf79b0239e14fa9b1e8468a795b465622e4dbd6c1f819c", + "https://deno.land/std@0.213.0/io/to_readable_stream.ts": "ed03a44a1ec1cc55a85a857acf6cac472035298f6f3b6207ea209f93b4aefb39", + "https://deno.land/std@0.213.0/io/to_writable_stream.ts": "ef422e0425963c8a1e0481674e66c3023da50f0acbe5ef51ec9789efc3c1e2ed", + "https://deno.land/std@0.213.0/io/types.ts": "748bbb3ac96abda03594ef5a0db15ce5450dcc6c0d841c8906f8b10ac8d32c96", + "https://deno.land/std@0.213.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038", + "https://deno.land/std@0.213.0/log/base_handler.ts": "924b370558d561f4e728295ebcb392224e36061542c7ad2e5b48c8e29614c27f", + "https://deno.land/std@0.213.0/log/console_handler.ts": "75653acd6932fb97c7121f63336b39de3f072e329874d66f05abcb2a7f514558", + "https://deno.land/std@0.213.0/log/file_handler.ts": "7b58c7017117ae290700b0e23f21573c1dc8ba8b5d4978d8aa0b8e05742d75e2", + "https://deno.land/std@0.213.0/log/formatters.ts": "5491ac778cf404a9379025bef33565644eb10b6a1f8083aba2595887d49edf15", + "https://deno.land/std@0.213.0/log/handlers.ts": "ff5b5d8293ca5d452acfb2e7c214f527ad953aaab4036219b818a3b859944b08", + "https://deno.land/std@0.213.0/log/levels.ts": "3746b311bc5cd28340fe7b563002f94508ace565592e9f4730f8b07916f189a6", + "https://deno.land/std@0.213.0/log/logger.ts": "32ad896c88182ee9cbe2a579afb09b48ed642a2bfaa3f3b2fb8009314ab18855", + "https://deno.land/std@0.213.0/log/mod.ts": "d8a8ebca268767a610d686d7f3c5c97096121ff3bc7ebec71ab7dd73cc52231b", + "https://deno.land/std@0.213.0/log/rotating_file_handler.ts": "dc0333959ff725ac1f43b741ababa9fef074f658a60c65235d12e44952085fbe", + "https://deno.land/std@0.213.0/path/_common/assert_path.ts": "2ca275f36ac1788b2acb60fb2b79cb06027198bc2ba6fb7e163efaedde98c297", + "https://deno.land/std@0.213.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.213.0/path/_common/common.ts": "6157c7ec1f4db2b4a9a187efd6ce76dcaf1e61cfd49f87e40d4ea102818df031", + "https://deno.land/std@0.213.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.213.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.213.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.213.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.213.0/path/_common/glob_to_reg_exp.ts": "2007aa87bed6eb2c8ae8381adcc3125027543d9ec347713c1ad2c68427330770", + "https://deno.land/std@0.213.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.213.0/path/_common/normalize_string.ts": "dfdf657a1b1a7db7999f7c575ee7e6b0551d9c20f19486c6c3f5ff428384c965", + "https://deno.land/std@0.213.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.213.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.213.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.213.0/path/_interface.ts": "a1419fcf45c0ceb8acdccc94394e3e94f99e18cfd32d509aab514c8841799600", + "https://deno.land/std@0.213.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.213.0/path/basename.ts": "5d341aadb7ada266e2280561692c165771d071c98746fcb66da928870cd47668", + "https://deno.land/std@0.213.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.213.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.213.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.213.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.213.0/path/format.ts": "98fad25f1af7b96a48efb5b67378fcc8ed77be895df8b9c733b86411632162af", + "https://deno.land/std@0.213.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.213.0/path/glob.ts": "04510962905d4b1513b44da9cb195914e0fa46c24359f6feaca20848d797dcb0", + "https://deno.land/std@0.213.0/path/glob_to_regexp.ts": "83c5fd36a8c86f5e72df9d0f45317f9546afa2ce39acaafe079d43a865aced08", + "https://deno.land/std@0.213.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.213.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.213.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.213.0/path/join_globs.ts": "e9589869a33dc3982101898ee50903db918ca00ad2614dbe3934d597d7b1fbea", + "https://deno.land/std@0.213.0/path/mod.ts": "ffeaccb713dbe6c72e015b7c767f753f8ec5fbc3b621ff5eeee486ffc2c0ddda", + "https://deno.land/std@0.213.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.213.0/path/normalize_glob.ts": "98ee8268fad271193603271c203ae973280b5abfbdd2cbca1053fd2af71869ca", + "https://deno.land/std@0.213.0/path/parse.ts": "65e8e285f1a63b714e19ef24b68f56e76934c3df0b6e65fd440d3991f4f8aefb", + "https://deno.land/std@0.213.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.213.0/path/posix/basename.ts": "39ee27a29f1f35935d3603ccf01d53f3d6e0c5d4d0f84421e65bd1afeff42843", + "https://deno.land/std@0.213.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.213.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.213.0/path/posix/dirname.ts": "6535d2bdd566118963537b9dda8867ba9e2a361015540dc91f5afbb65c0cce8b", + "https://deno.land/std@0.213.0/path/posix/extname.ts": "8d36ae0082063c5e1191639699e6f77d3acf501600a3d87b74943f0ae5327427", + "https://deno.land/std@0.213.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.213.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.213.0/path/posix/glob_to_regexp.ts": "54d3ff40f309e3732ab6e5b19d7111d2d415248bcd35b67a99defcbc1972e697", + "https://deno.land/std@0.213.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.213.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.213.0/path/posix/join.ts": "aef88d5fa3650f7516730865dbb951594d1a955b785e2450dbee93b8e32694f3", + "https://deno.land/std@0.213.0/path/posix/join_globs.ts": "ee2f4676c5b8a0dfa519da58b8ade4d1c4aa8dd3fe35619edec883ae9df1f8c9", + "https://deno.land/std@0.213.0/path/posix/mod.ts": "563a18c2b3ddc62f3e4a324ff0f583e819b8602a72ad880cb98c9e2e34f8db5b", + "https://deno.land/std@0.213.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.213.0/path/posix/normalize_glob.ts": "65f0138fa518ef9ece354f32889783fc38cdf985fb02dcf1c3b14fa47d665640", + "https://deno.land/std@0.213.0/path/posix/parse.ts": "d5bac4eb21262ab168eead7e2196cb862940c84cee572eafedd12a0d34adc8fb", + "https://deno.land/std@0.213.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.213.0/path/posix/resolve.ts": "bac20d9921beebbbb2b73706683b518b1d0c1b1da514140cee409e90d6b2913a", + "https://deno.land/std@0.213.0/path/posix/separator.ts": "c9ecae5c843170118156ac5d12dc53e9caf6a1a4c96fc8b1a0ab02dff5c847b0", + "https://deno.land/std@0.213.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.213.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.213.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.213.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.213.0/path/separator.ts": "c6c890507f944a1f5cb7d53b8d638d6ce3cf0f34609c8d84a10c1eaa400b77a9", + "https://deno.land/std@0.213.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.213.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.213.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.213.0/path/windows/basename.ts": "e2dbf31d1d6385bfab1ce38c333aa290b6d7ae9e0ecb8234a654e583cf22f8fe", + "https://deno.land/std@0.213.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.213.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.213.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.213.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.213.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.213.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.213.0/path/windows/glob_to_regexp.ts": "6dcd1242bd8907aa9660cbdd7c93446e6927b201112b0cba37ca5d80f81be51b", + "https://deno.land/std@0.213.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.213.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.213.0/path/windows/join.ts": "e0b3356615c1a75c56ebb6a7311157911659e11fd533d80d724800126b761ac3", + "https://deno.land/std@0.213.0/path/windows/join_globs.ts": "ee2f4676c5b8a0dfa519da58b8ade4d1c4aa8dd3fe35619edec883ae9df1f8c9", + "https://deno.land/std@0.213.0/path/windows/mod.ts": "7d6062927bda47c47847ffb55d8f1a37b0383840aee5c7dfc93984005819689c", + "https://deno.land/std@0.213.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.213.0/path/windows/normalize_glob.ts": "179c86ba89f4d3fe283d2addbe0607341f79ee9b1ae663abcfb3439db2e97810", + "https://deno.land/std@0.213.0/path/windows/parse.ts": "b9239edd892a06a06625c1b58425e199f018ce5649ace024d144495c984da734", + "https://deno.land/std@0.213.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.213.0/path/windows/resolve.ts": "75b2e3e1238d840782cee3d8864d82bfaa593c7af8b22f19c6422cf82f330ab3", + "https://deno.land/std@0.213.0/path/windows/separator.ts": "e51c5522140eff4f8402617c5c68a201fdfa3a1a8b28dc23587cff931b665e43", + "https://deno.land/std@0.213.0/path/windows/to_file_url.ts": "1cd63fd35ec8d1370feaa4752eccc4cc05ea5362a878be8dc7db733650995484", + "https://deno.land/std@0.213.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.213.0/semver/_constants.ts": "90879e4ea94a34c49c8ecea3d9c06e13e61ecb7caca80c8f289139440ca9835a", + "https://deno.land/std@0.213.0/semver/_shared.ts": "8d44684775cde4a64bd6bdb99b51f3bc59ed65f10af78ca136cc2eab3f6716f1", + "https://deno.land/std@0.213.0/semver/comparator_format.ts": "41f00b1275923317fa3f7c39df58fa7f62f541b0f231134c1655bc8a42965393", + "https://deno.land/std@0.213.0/semver/comparator_intersects.ts": "dc231c5ebded8e88b8355a33edfbd228e36a08384848d73c15d394833384ee8f", + "https://deno.land/std@0.213.0/semver/comparator_max.ts": "2038cded7cce886e2c81926acb97f625908707f2d66864b603493b9674e2bd58", + "https://deno.land/std@0.213.0/semver/comparator_min.ts": "453d3e449aaee4d59acc9b36fe77eddfcb0c4097ffe7efe11eb2a04a64cc520d", + "https://deno.land/std@0.213.0/semver/compare.ts": "e507146fd997d33ae5abc2675e8b24a1ea84b50ddc9918cb8ddc1b1911c97011", + "https://deno.land/std@0.213.0/semver/constants.ts": "52dde17ff45479fbdc6b3a7198224e02a2deb9cb4f99ac6592c9727173f13a83", + "https://deno.land/std@0.213.0/semver/difference.ts": "be4f01b7745406408a16b708185a48c1c652cc87e0244b12a5ca75c5585db668", + "https://deno.land/std@0.213.0/semver/eq.ts": "7aaffb5d841dee589fa81e18d54fb4aec065feaa701f58214f89e17edbcf5da5", + "https://deno.land/std@0.213.0/semver/equals.ts": "8b9b18260c9a55feee9d3f9250fba345be922380f2e8f8009e455c394ce5e81d", + "https://deno.land/std@0.213.0/semver/format.ts": "26d3a357ac5abd73dee0fe7dbbac6107fbdce0a844370c7b1bcb673c92e46bf6", + "https://deno.land/std@0.213.0/semver/format_range.ts": "d472a7f743cf0290beebed90d1e6d8f1b5e93d91c03f3503e869f18931acd156", + "https://deno.land/std@0.213.0/semver/greater_or_equal.ts": "89c26f68070896944676eb9704cbb617febc6ed693720282741d6859c3d1fe80", + "https://deno.land/std@0.213.0/semver/greater_than.ts": "d8c4a227cd28ea80a1de9c80215d7f3f95786fe1b196f0cb5ec91d6567adad27", + "https://deno.land/std@0.213.0/semver/gt.ts": "e9a7b3e80eaf07fa949daf2622ed0be6f863d972f744557107fbfce7d6786624", + "https://deno.land/std@0.213.0/semver/gte.ts": "2f6deabbeb5c716d916d80bf6c0cfabbb00e0eb12c34420f2cf96dbb85fdc0f7", + "https://deno.land/std@0.213.0/semver/gtr.ts": "50cde7d0a05416f2a8b9d5125848e141eba474755d8c0e852ab2dfd22443ad2c", + "https://deno.land/std@0.213.0/semver/increment.ts": "427a043be71d6481e45c1a3939b955e800924d70779cb297b872d9cbf9f0e46d", + "https://deno.land/std@0.213.0/semver/is_comparator.ts": "895e7ecff33d23d7a465074a76b2341eda430f84c4817199f7492c5393e2e54f", + "https://deno.land/std@0.213.0/semver/is_semver.ts": "57914027d6141e593eb04418aaabbfd6f4562a1c53c6c33a1743fa50ada8d849", + "https://deno.land/std@0.213.0/semver/is_semver_range.ts": "1e4602ed91d5d7228e63765ab4d28042ee358304155a0eeb562871d030cabaee", + "https://deno.land/std@0.213.0/semver/less_or_equal.ts": "7dbf8190f37f3281048c30cf11e072a7af18685534ae88d295baa170b485bd90", + "https://deno.land/std@0.213.0/semver/less_than.ts": "b0c7902c54cecadcc7c1c80afc2f6a0f1bf0b3f53c8d2bfd11f01a3a414cccfe", + "https://deno.land/std@0.213.0/semver/lt.ts": "42b40467018e72e6637c68dde5439960a2db366e1edd730a8bb60a432d30f703", + "https://deno.land/std@0.213.0/semver/lte.ts": "678b9919c5abe85a7917f6815e74a323788f4a81f5da64d329b34cb32bb788c6", + "https://deno.land/std@0.213.0/semver/ltr.ts": "57ee19e33c90883d953e22255c0e02bfc8f682c610e32aab0a76db45a6cd159c", + "https://deno.land/std@0.213.0/semver/max_satisfying.ts": "1008802c70eaa6d13a9455f0bda7fcfbd0dd53d837d87930b8520411670d2766", + "https://deno.land/std@0.213.0/semver/min_satisfying.ts": "ad035539bb23c3d9545d5e9508123cd84be4ec47331acc23574795a3ae6c460e", + "https://deno.land/std@0.213.0/semver/mod.ts": "6c8c5aba6f9c1499e5e2bc1b1272d13157ed1f438296a095a14b8d321e805b59", + "https://deno.land/std@0.213.0/semver/neq.ts": "8c8c474249aa43331cc3d2a93ff0e824792f4fe823b674c79c50b778b06331a9", + "https://deno.land/std@0.213.0/semver/not_equals.ts": "17147a6f68b9d14f4643c1e2150378ccf6954710309f9618f75b411752a8e13d", + "https://deno.land/std@0.213.0/semver/outside.ts": "9953ed5935a1bc79b9d8e6258fa1717281a33bd5501f2ee0bc0fe6ed80b310b9", + "https://deno.land/std@0.213.0/semver/parse.ts": "2ba215c9aa3c71be753570724cfad75cc81972f0026dc81836ea3d1986112066", + "https://deno.land/std@0.213.0/semver/parse_comparator.ts": "94accf91b8c68c083a2fb932ff3afa81fb01cd5ce007d4e679f59ec86be3a132", + "https://deno.land/std@0.213.0/semver/parse_range.ts": "3242441370743df07919ca340be719acd9655311fa6a18e115761dfe562fc5ca", + "https://deno.land/std@0.213.0/semver/range_format.ts": "1d9f3a1b8176be0e49698929143da0e6a1b84f2581eb750e2cf17d7a0b6fac6c", + "https://deno.land/std@0.213.0/semver/range_intersects.ts": "461ce0045852511bbfe9204483ddbee897c4fb5557fc707e055edf98f15d5d30", + "https://deno.land/std@0.213.0/semver/range_max.ts": "c0eba7c51f462470bc3822227df57e0153deeacf47a4c40d5c27790fddd6cb92", + "https://deno.land/std@0.213.0/semver/range_min.ts": "37b9664103d5202c91f6228e8d7ac4ea508d7867f40a686dbe211e2a57f3efff", + "https://deno.land/std@0.213.0/semver/sort.ts": "b97c4f392cf688e27d304dc437b1a4d7278fbf5d2554ad6e51f64db6cc405c09", + "https://deno.land/std@0.213.0/semver/test_comparator.ts": "85476901a71fe8a09520902d85e1c573ce60a353846f8bbf6667e9518686591b", + "https://deno.land/std@0.213.0/semver/test_range.ts": "88de9dab0d61c82fd0861d50eabe72f0f27f29f3df4d50e83f36e09c1c3cd8a6", + "https://deno.land/std@0.213.0/semver/types.ts": "c85eb042ba22c69d62194ea7e535c3c104d0e9af75316b6315379101b4a7ef66", + "https://deno.land/std@0.213.0/streams/_common.ts": "4f9f2958d853b9a456be033631dabb7519daa68ee4d02caf53e2ecbffaf5805f", + "https://deno.land/std@0.213.0/streams/buffer.ts": "71120cceddacab2eb47a2f2908c64e82e79ac089506649bd41412042fcc97773", + "https://deno.land/std@0.213.0/streams/byte_slice_stream.ts": "5bbdcadb118390affa9b3d0a0f73ef8e83754f59bb89df349add669dd9369713", + "https://deno.land/std@0.213.0/streams/copy.ts": "442d1d647ce7daf350dd989797dd2eea51ec8ad3b3a6851fcdaf7ef44a387c71", + "https://deno.land/std@0.213.0/streams/delimiter_stream.ts": "45271f9db844e8e501a6df75b946cd2a5e01663de0e9ccf26b92996983e0cdbe", + "https://deno.land/std@0.213.0/streams/early_zip_readable_streams.ts": "21f5cf6dd36381c6a50c31a7727b5bd219f6382bbb7a413418595c3e466c4d14", + "https://deno.land/std@0.213.0/streams/iterate_reader.ts": "bd79a18de211449e5140e8f705e195c3e0e79020d752a64cd0a1d4b829b14633", + "https://deno.land/std@0.213.0/streams/limited_bytes_transform_stream.ts": "b22a45a337374e863c4eb1867ec6b8ad3e68620a6c52fe837746060ea610e6f1", + "https://deno.land/std@0.213.0/streams/limited_transform_stream.ts": "4c47da5ca38a30fa9f33b0f1a61d4548e7f52a9a58c294b0f430f680e44cc543", + "https://deno.land/std@0.213.0/streams/merge_readable_streams.ts": "9c541012e130d6e36086b6b8c197078a6053f5446367e33f233b71858a2c03cc", + "https://deno.land/std@0.213.0/streams/mod.ts": "cbe5466def4eb5e44a628df7be4700f7e2f88ac8b7d82cf3d769cfef5233aca4", + "https://deno.land/std@0.213.0/streams/read_all.ts": "b39b7d56b3ef9c0f78bbde82244ab3663b4adc1dee1be6ec97c0117f033c884c", + "https://deno.land/std@0.213.0/streams/readable_stream_from_reader.ts": "4289a63836f73901441c1879f2be76eea2a983920f4b10a4a9b8a6d8c29ece56", + "https://deno.land/std@0.213.0/streams/reader_from_iterable.ts": "cf7785e518beaaba1dfb3ff4ae854bb76499bbc1f013910af6402ec7643bf769", + "https://deno.land/std@0.213.0/streams/reader_from_stream_reader.ts": "f981cf94a42133e5c6ace8c3b500565750806c4fc9802262ee63746abc528b0d", + "https://deno.land/std@0.213.0/streams/text_delimiter_stream.ts": "ef0d7898cea4a9fff850173ed9f3d2cf9e42ba858d8175bd89fe851c8dfa6a6e", + "https://deno.land/std@0.213.0/streams/text_line_stream.ts": "21f33d3922e019ec1a1676474beb543929cb564ec99b69cd2654e029e0f45bd5", + "https://deno.land/std@0.213.0/streams/to_array_buffer.ts": "1a9c07c4a396ce557ab205c44415815ab13b614fed94a12f62b80f8e650c726d", + "https://deno.land/std@0.213.0/streams/to_blob.ts": "bf5daaae50fa8f57e0c8bfd7474ebac16ac09e130e3d01ef2947ae5153912b4a", + "https://deno.land/std@0.213.0/streams/to_json.ts": "b6a908d0da7cd30956e5fbbfa7460747e50b8f307d1041282ed6fe9070d579ee", + "https://deno.land/std@0.213.0/streams/to_text.ts": "6f93593bdfc2cea5cca39755ea5caf0d4092580c0a713dfe04a1e85c60df331f", + "https://deno.land/std@0.213.0/streams/to_transform_stream.ts": "4c4836455ef89bab9ece55975ee3a819f07d3d8b0e43101ec7f4ed033c8a2b61", + "https://deno.land/std@0.213.0/streams/writable_stream_from_writer.ts": "62f2712d3a7bebd981fca8bd5140192c37450f9c4aa94283f7ca833e46bc7485", + "https://deno.land/std@0.213.0/streams/write_all.ts": "3170620e750c1eaf43ac9e216669f55df762e2dc827d8b8a920b4f64a803c362", + "https://deno.land/std@0.213.0/streams/writer_from_stream_writer.ts": "b0e39ef607dfdc5abdfb627edf61a9672809463e2bb022afcbaf0cd006c40feb", + "https://deno.land/std@0.213.0/streams/zip_readable_streams.ts": "53eb10d7557539b489bd858907aab6dd28247f074b3446573801de3150cb932e", + "https://deno.land/std@0.213.0/url/_strip.ts": "928fe9af16d7c5bf24816d1e90d84bfe702f3e059f9d63509b5a37087e947800", + "https://deno.land/std@0.213.0/url/basename.ts": "a2e6ef35d44da3764551cbc61cdd39004c778aaedc7a6c2559e571f018c42daa", + "https://deno.land/std@0.213.0/url/dirname.ts": "0915864aac7d2d0413c90dff7841b18b29c83ed102fa340e760af1fb2c0ad26c", + "https://deno.land/std@0.213.0/url/extname.ts": "b247eac636161c5e263220c6e5116ed10e0c1702b5e90fad258a88c0b3b6bf98", + "https://deno.land/std@0.213.0/url/join.ts": "00c7e9088cafaa24963ce4081119e58b3afe2c58f033701383f359ea02620dd2", + "https://deno.land/std@0.213.0/url/mod.ts": "e2621f6a0db6fdbe7fbbd240064095bb203014657e5e1ab81db1c44d80dce6c9", + "https://deno.land/std@0.213.0/url/normalize.ts": "6328c75df0fab300f74bc4a1c255062a0db882240e15ab646606d0009e7e40d7", + "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", + "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", + "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", + "https://deno.land/std@0.221.0/console/_run_length.ts": "7da8642a0f4f41ac27c0adb1364e18886be856c1d08c5cce6c6b5c00543c8722", + "https://deno.land/std@0.221.0/console/unicode_width.ts": "d92f085c0ab9c7ab171e4e7862dfd9d3a36ffd369939be5d3e1140ec58bc820f", + "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", + "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", + "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_utils.ts": "fa0e88cc4215b18554a7308e8e2ae3a12be0fb91c54d1473c54c530dbd4adfcb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/command.ts": "83cbece11c1459d5bc5add32c3cad0bf49e92c4ddd3ef00f22f80efdae30994e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/_zsh_completions_generator.ts": "9df79fbac17a32b9645d01628c41a2bfd295d7976b87b0ae235f50a9c8975fbc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/deps.ts": "a58ea2fa4e2ed9b39bb8dd8c35dd0498c74f05392517ff230a9a4d04c4c766b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/_help_generator.ts": "98619da83ff25523280a6fdcad89af3f13a6fafefc81b71f8230f3344b5ff2c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/upgrade_command.ts": "27191f4b1ce93581b6d5ee2fff6003fe4fca437f476ecb98b6eae92f2b4d0716", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_utils.ts": "25e519ce1f35acc8b43c75d1ca1c4ab591e7dab08327b7b408705b591e27d8bd", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/deps.ts": "bed26afff36eeb25509440edec9d5d141b3411e08cc7a90e38a370969b5166bb", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", + "https://deno.land/x/cliffy@v1.0.0-rc.4/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_layout.ts": "73a9bcb8a87b3a6817c4c9d2a31a21b874a7dd690ade1c64c9a1f066d628d626", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/_utils.ts": "13390db3f11977b7a4fc1202fa8386be14696b475a7f46a65178354f9a6640b7", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/cell.ts": "65e3ee699c3cebeb4d4d44e8f156e37a8532a0f317359d73178a95724d3f9267", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/consume_words.ts": "369d065dbf7f15c664ea8523e0ef750fb952aea6d88e146c375e64aec9503052", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/deps.ts": "cbb896e8d7a6b5e3c2b9dda7d16638c202d9b46eb738c2dae1fa9480d8091486", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", + "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", + "https://deno.land/x/foras@v2.1.4/src/deno/mod.ts": "c350ea5f32938e6dcb694df3761615f316d730dafc57440e9afd5f36f8e309fd", + "https://deno.land/x/foras@v2.1.4/src/deno/mods/mod.ts": "cc099bbce378f3cdaa94303e8aff2611e207442e5ac2d5161aba636bb4a95b46", + "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.js": "06f8875b456918b9671d52133f64f3047f1c95540feda87fdd4a55ba3d30091d", + "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.wasm.js": "2df8522df7243b0f05b1d188e220629cd5d2c92080a5f1407e15396fc35bebb3", + "https://deno.land/x/json_hash@0.2.0/canon.ts": "ce7c07abd871cd7f0eb1280ad9f58f6382f02f84a217898ce977cf35ad315877", + "https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1", + "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/0c5f78/deps/cli.ts": "22fdbfe7f39dc2caa9dd056a57a57051deec6b2a7ba9381e20e2ce7ab6af07e1", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/deps/common.ts": "5d676e006bb1485056935c263a967eee9fcfc1517249d1ca05a7645dca5e2e68", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/files/deno/worker.ts": "71f3cee9dba3c2bd59c85d2909eac325da556a9917ed6ea01222f6c217638dd9", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/host/mod.ts": "faea10bf051dc22443e0bb3cadb74599d2ef5e4543f065a75777bb57b818c022", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/host/types.ts": "359ceb8a800c5acd9ef4778e40ccfe039fd7724c06205ae3998398641a9b2370", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/install/mod.ts": "f78083efd15e82c8cc302dd801565f39c947497cfaa039fde1023f7e0d5ab368", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/main.ts": "fb82696926c97ea6749151275cafce049c35c2d500188661ac8b1d205a3b9939", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/envs/mod.ts": "33ddee364795c1f22028e74071063fe85211949682bb94f1ca38396314cbd01e", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/envs/posix.ts": "3193141953de1bfe2d73549e651711f4e1a1d05f5fcc7655ab74160b25be09d0", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/envs/reducer.ts": "853347377f4b265792da2ece78dfde7602c2555341bbd9f8dfd7ac5fd7d989ad", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/envs/types.ts": "a03173fe013a41163471446f41c636bd23acc6e4956ea910e12cb203dc449a9e", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/ghrel.ts": "a1bf0e244080b8b2a62093f536bb7eff0b5a9c596f7eef9f516c11a80aad0be1", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/inter.ts": "62ddc0dede33b059dbd84d18411d0b0acceb145ff96b076401a96c980ae9bfc0", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/mod.ts": "6d4b907ad70a9946299bc5931c976fad1cb1b405466cf4cc8d2acb7d0ba3310c", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/reducers.ts": "eaabbc2cf5d16a55cff5b3f95180f3e1ddb09b1a755776da2931f8817f28a0df", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/sync.ts": "6bbaca38024fd1f6c6ba5811abe65052d2061527539f1893f768ace40016ab5f", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/types.ts": "1adbe5a901f765de106db6513eb770356eed156c435e94d51b7432dce401530e", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/tasks/deno.ts": "15d5bb6379f3add73cb0d8aa4b578998d87bcfaa939518166c30a7f906ea5750", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/tasks/exec.ts": "cc5db628d85a84b6193f59d7f5d98868f22a59f038716dc3d4fc5ac70494d625", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/tasks/mod.ts": "71a16751895ce8bb687c565602938773ac276ccb62d28a793db0b1715438ee9a", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/tasks/types.ts": "0bf2cf9ac1f5735dc95ac348175866abf602bd90d01c9275c708f767baa976c1", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/types.ts": "53de8906ea0149871e35c937f3e52dee1a615907971fa8ec3f322f4dfe6d40f3", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/modules/utils.ts": "b5866a52cd4e0e1c0dc8ccb56c7281aeff2e2bf5e16866b77eda36e0529e312a", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/utils/mod.ts": "d4d0c0198168f63bd084872bf7dfb40925301ecb65fd0501520db942f6c0c961", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", + "https://raw.githubusercontent.com/metatypedev/ghjk/0c5f78/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/host/mod.ts": "604e2729145c16226af91e6880e3eca30ea060688fb4941ab39d9489109dd62c", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/install/mod.ts": "f78083efd15e82c8cc302dd801565f39c947497cfaa039fde1023f7e0d5ab368", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/main.ts": "21ea4582db19e163f4dd68ccdb19578c3c48e48dd23c094d8f8f88ab785e34e5", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/envs/mod.ts": "b9483be6dbd4c282d1c5b134864b2ff0f53d8bfb25dba6c96e591c84ccf25e01", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/envs/reducer.ts": "853347377f4b265792da2ece78dfde7602c2555341bbd9f8dfd7ac5fd7d989ad", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/ghrel.ts": "a1bf0e244080b8b2a62093f536bb7eff0b5a9c596f7eef9f516c11a80aad0be1", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/mod.ts": "2b5d4773d64641cdc0aacf09ece6c40d094feb090280647c68f33bbfa8dceee7", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/reducers.ts": "eaabbc2cf5d16a55cff5b3f95180f3e1ddb09b1a755776da2931f8817f28a0df", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/tasks/deno.ts": "2b9f33253ac1257eb79a4981cd221509aa9ecf8a3c36d7bd8be1cd6c1150100b", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/tasks/exec.ts": "eaf6b2f9639185fa76f560276e0d28d262a6c78d2bdc0d579e7683e062d7b542", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/tasks/mod.ts": "438f1cbb5e96470f380b6954bb18ad7693ed33bb99314137ff7080d82d026615", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/5bb0d24/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", + "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" + } +} diff --git a/.ghjk/lock.json b/.ghjk/lock.json index 9ddf4547..844d0807 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -18,13 +18,13 @@ "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "1.35", + "version": "1.34", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.5.5,", + "version": "v1.4.8,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -42,13 +42,13 @@ "version": "3.12.3", "buildDepConfigs": { "tar_aa": { - "version": "1.35", + "version": "1.34", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.5.5,", + "version": "v1.4.8,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -58,16 +58,28 @@ "specifiedVersion": false }, "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { - "version": "1.35", + "version": "1.34", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": { - "version": "v1.5.5,", + "version": "v1.4.8,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false + }, + "bciqkhpxlimubssnvndchh3qoipg6faxas7443pbsguwmd5xcqnbjpyy": { + "version": "v1.44.2", + "buildDepConfigs": {}, + "portRef": "deno_ghrel@0.1.0", + "specifiedVersion": false + }, + "bciqfvlwwndlfuqibybkgee3fgt7cst5ltpztmm3by6hib5veial5spy": { + "version": "v1.44.2", + "buildDepConfigs": {}, + "portRef": "deno_ghrel@0.1.0", + "specifiedVersion": true } } }, @@ -88,9 +100,14 @@ "installs": [ "bciqe72molvtvcuj3tuh47ziue2oqd6t4qetxn3rsoa764ofup6uwjmi", "bciqe4zlekl4uqqbhxunac7br24mrf6cdpfrfblahqa4vrgaqjujcl4i", - "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q" + "bciqjyl5um6634zwpw6cewv22chzlrsvhedbjahyghhy2zraqqgyiv2q", + "bciqmgggy7hd5as3zz7pzbx54va7lq657bdxvthntxphhlbsl2434dgq" ], - "allowedDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" + "allowedBuildDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" + }, + "ghjkEnvProvInstSet___test": { + "installs": [], + "allowedBuildDeps": "bciqjx7llw7t6pfczypzmhbwv7sxaicruj5pdbuac47m4c5qyildiowi" } } } @@ -98,19 +115,31 @@ { "id": "tasks", "config": { - "envs": {}, - "tasks": {}, - "tasksNamed": [] + "tasks": { + "lock-sed": { + "ty": "denoFile@v1", + "key": "lock-sed", + "envKey": "bciqekhy7ndyc6hmkzspdsguxjgvyz5yedr5weigsqsa72kyloity4jy" + } + }, + "tasksNamed": [ + "lock-sed" + ] } }, { "id": "envs", "config": { "envs": { - "test": { - "provides": [] + "bciqekhy7ndyc6hmkzspdsguxjgvyz5yedr5weigsqsa72kyloity4jy": { + "provides": [ + { + "ty": "ghjk.ports.InstallSetRef", + "setId": "ghjkEnvProvInstSet___test" + } + ] }, - "main": { + "bciqfzekhtsrjd72noxifmici3ssck4jgvbjwhxwhhwtirzm7yomhxya": { "desc": "the default default environment.", "provides": [ { @@ -120,7 +149,10 @@ ] } }, - "defaultEnv": "main" + "defaultEnv": "main", + "envsNamed": { + "main": "bciqfzekhtsrjd72noxifmici3ssck4jgvbjwhxwhhwtirzm7yomhxya" + } } } ], @@ -199,6 +231,23 @@ "moduleSpecifier": "file:///ports/cpy_bs.ts" } }, + "bciqmgggy7hd5as3zz7pzbx54va7lq657bdxvthntxphhlbsl2434dgq": { + "version": "1.44.2", + "port": { + "ty": "denoWorker@v1", + "name": "deno_ghrel", + "platforms": [ + "aarch64-linux", + "x86_64-linux", + "aarch64-darwin", + "x86_64-darwin", + "aarch64-windows", + "x86_64-windows" + ], + "version": "0.1.0", + "moduleSpecifier": "file:///ports/deno_ghrel.ts" + } + }, "bciqb6ua63xodzwxngnbjq35hfikiwzb3dclbqkc7e6xgjdt5jin4pia": { "manifest": { "ty": "ambientAccess@v1", diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 02efe78d..87e0a325 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: env: - DENO_VERSION: "1.43.1" + DENO_VERSION: "1.44.2" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHJK_LOG_PANIC_LEVEL: error DENO_DIR: .deno-dir diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6cc2f307..a6d2a98d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: - ready_for_review env: - DENO_VERSION: "1.43.1" + DENO_VERSION: "1.44.2" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHJK_LOG: debug GHJK_LOG_PANIC_LEVEL: error @@ -44,7 +44,7 @@ jobs: - os: ubuntu-latest platform: linux/x86_64 e2eType: "docker" - - os: custom-macos + - os: custom-arm platform: linux/aarch64 e2eType: "docker" - os: macos-latest diff --git a/deno.lock b/deno.lock index dbb09652..5ab46e01 100644 --- a/deno.lock +++ b/deno.lock @@ -18,10 +18,15 @@ "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", "npm:@noble/hashes@1.4.0": "npm:@noble/hashes@1.4.0", "npm:@types/node": "npm:@types/node@18.16.19", + "npm:lodash": "npm:lodash@4.17.21", + "npm:mathjs@11.11.1": "npm:mathjs@11.11.1", "npm:multiformats@13.1.0": "npm:multiformats@13.1.0", + "npm:pg": "npm:pg@8.12.0", + "npm:validator": "npm:validator@13.12.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.3.0": "npm:zod-validation-error@3.3.0_zod@3.23.3" + "npm:zod-validation-error@3.3.0": "npm:zod-validation-error@3.3.0_zod@3.23.3", + "npm:zod@3.23.8": "npm:zod@3.23.8" }, "jsr": { "@david/dax@0.40.1": { @@ -100,6 +105,12 @@ } }, "npm": { + "@babel/runtime@7.24.7": { + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dependencies": { + "regenerator-runtime": "regenerator-runtime@0.14.1" + } + }, "@noble/hashes@1.4.0": { "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dependencies": {} @@ -108,10 +119,143 @@ "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": {} }, + "complex.js@2.1.1": { + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "dependencies": {} + }, + "decimal.js@10.4.3": { + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dependencies": {} + }, + "escape-latex@1.2.0": { + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==", + "dependencies": {} + }, + "fraction.js@4.3.4": { + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", + "dependencies": {} + }, + "javascript-natural-sort@0.7.1": { + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dependencies": {} + }, + "lodash@4.17.21": { + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dependencies": {} + }, + "mathjs@11.11.1": { + "integrity": "sha512-uWrwMrhU31TCqHKmm1yFz0C352njGUVr/I1UnpMOxI/VBTTbCktx/mREUXx5Vyg11xrFdg/F3wnMM7Ql/csVsQ==", + "dependencies": { + "@babel/runtime": "@babel/runtime@7.24.7", + "complex.js": "complex.js@2.1.1", + "decimal.js": "decimal.js@10.4.3", + "escape-latex": "escape-latex@1.2.0", + "fraction.js": "fraction.js@4.3.4", + "javascript-natural-sort": "javascript-natural-sort@0.7.1", + "seedrandom": "seedrandom@3.0.5", + "tiny-emitter": "tiny-emitter@2.1.0", + "typed-function": "typed-function@4.2.1" + } + }, "multiformats@13.1.0": { "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==", "dependencies": {} }, + "pg-cloudflare@1.1.1": { + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "dependencies": {} + }, + "pg-connection-string@2.6.4": { + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==", + "dependencies": {} + }, + "pg-int8@1.0.1": { + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dependencies": {} + }, + "pg-pool@3.6.2_pg@8.12.0": { + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "dependencies": { + "pg": "pg@8.12.0" + } + }, + "pg-protocol@1.6.1": { + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==", + "dependencies": {} + }, + "pg-types@2.2.0": { + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "pg-int8@1.0.1", + "postgres-array": "postgres-array@2.0.0", + "postgres-bytea": "postgres-bytea@1.0.0", + "postgres-date": "postgres-date@1.0.7", + "postgres-interval": "postgres-interval@1.2.0" + } + }, + "pg@8.12.0": { + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "dependencies": { + "pg-cloudflare": "pg-cloudflare@1.1.1", + "pg-connection-string": "pg-connection-string@2.6.4", + "pg-pool": "pg-pool@3.6.2_pg@8.12.0", + "pg-protocol": "pg-protocol@1.6.1", + "pg-types": "pg-types@2.2.0", + "pgpass": "pgpass@1.0.5" + } + }, + "pgpass@1.0.5": { + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "split2@4.2.0" + } + }, + "postgres-array@2.0.0": { + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dependencies": {} + }, + "postgres-bytea@1.0.0": { + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "dependencies": {} + }, + "postgres-date@1.0.7": { + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dependencies": {} + }, + "postgres-interval@1.2.0": { + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "xtend@4.0.2" + } + }, + "regenerator-runtime@0.14.1": { + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dependencies": {} + }, + "seedrandom@3.0.5": { + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "dependencies": {} + }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dependencies": {} + }, + "tiny-emitter@2.1.0": { + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "dependencies": {} + }, + "typed-function@4.2.1": { + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "dependencies": {} + }, + "validator@13.12.0": { + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "dependencies": {} + }, + "xtend@4.0.2": { + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dependencies": {} + }, "zod-validation-error@3.1.0_zod@3.23.3": { "integrity": "sha512-zujS6HqJjMZCsvjfbnRs7WI3PXN39ovTcY1n8a+KTm4kOH0ZXYsNiJkH1odZf4xZKMkBDL7M2rmQ913FCS1p9w==", "dependencies": { @@ -133,6 +277,10 @@ "zod@3.23.3": { "integrity": "sha512-tPvq1B/2Yu/dh2uAIH2/BhUlUeLIUvAjr6dpL/75I0pCYefHgjhXk1o1Kob3kTU8C7yU1j396jFHlsVWFi9ogg==", "dependencies": {} + }, + "zod@3.23.8": { + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dependencies": {} } } }, @@ -411,6 +559,12 @@ "https://deno.land/std@0.213.0/url/join.ts": "00c7e9088cafaa24963ce4081119e58b3afe2c58f033701383f359ea02620dd2", "https://deno.land/std@0.213.0/url/mod.ts": "e2621f6a0db6fdbe7fbbd240064095bb203014657e5e1ab81db1c44d80dce6c9", "https://deno.land/std@0.213.0/url/normalize.ts": "6328c75df0fab300f74bc4a1c255062a0db882240e15ab646606d0009e7e40d7", + "https://deno.land/std@0.219.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", + "https://deno.land/std@0.219.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", + "https://deno.land/std@0.219.0/cli/mod.ts": "58f75df8ce43fb8266bdd26ec4465f73176b910316d72eb8e090b6a0549391da", + "https://deno.land/std@0.219.0/cli/parse_args.ts": "475b3edc8105c9acea09b83b100afc383d7bddbba9828da3f0c4adced006607a", + "https://deno.land/std@0.219.0/cli/prompt_secret.ts": "831cfb4efa83bfaf9bfd320ddbfd619e03cd87e81260909f93ca199ebe214ec2", + "https://deno.land/std@0.219.0/cli/spinner.ts": "005395c4e00b1086bfa2ae44e8c9413c1231c4741a08a55aa0d3c9ea267cecb5", "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", "https://deno.land/std@0.221.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", diff --git a/deps/cli.ts b/deps/cli.ts index bd6a415f..9829c228 100644 --- a/deps/cli.ts +++ b/deps/cli.ts @@ -4,4 +4,3 @@ export * from "./common.ts"; export * as cliffy_cmd from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; export { Table } from "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts"; -export * as zod_val_err from "npm:zod-validation-error@3.3.0"; diff --git a/deps/common.ts b/deps/common.ts index e0711079..d81d085f 100644 --- a/deps/common.ts +++ b/deps/common.ts @@ -2,7 +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.8/mod.ts"; +export { z as zod } from "npm:zod@3.23.8"; 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"; @@ -10,6 +10,7 @@ export * as std_fmt_colors from "https://deno.land/std@0.213.0/fmt/colors.ts"; export * as std_url from "https://deno.land/std@0.213.0/url/mod.ts"; export * as std_path from "https://deno.land/std@0.213.0/path/mod.ts"; export * as std_fs from "https://deno.land/std@0.213.0/fs/mod.ts"; +export * as zod_val_err from "npm:zod-validation-error@3.3.0"; // avoid using the following directly and go through the // wrappers in ./utils/mod.ts diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index 2143a245..0562f377 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -17,7 +17,7 @@ const ghjk = file({ enableRuntimes: true, // tasks aren't attached to envs // but have their own env - tasks: [], + tasks: {}, }); // we need this export for this file to be a valid ghjkfile @@ -33,19 +33,18 @@ env("main") .var("RUST_LOG", "info,actix=warn") // provision programs to be avail in the env .install(ports.jq_ghrel()) - .allowedBuildDeps([ + .allowedBuildDeps( // ports can use the following installs at build time // very WIP mechanism but this is meant to prevent ports from // pulling whatever dependency they want at build time unless // explicityl allowed to do so - ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), ports.node({}), ports.rust({ version: "stable" }), // add the std deps including the runtime ports. // These includes node and python but still, precedence is given // to our configuration of those ports above ...stdDeps({ enableRuntimes: true }), - ]); + ); // these top level installs go to the main env as well install( @@ -97,9 +96,24 @@ task("build-app", { }, }); +env("python") + // all envs will inherit from `defaultBaseEnv` + // unles set to false which ensures true isolation + .inherit(false) + .install( + ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), + ) + .allowedBuildDeps( + ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), + ); + env("dev") - .inherit("main") + // we can inherit from many envs + // if conflict on variables or build deps, the one declared + // later overrides + .inherit(["main", "python"]) // we can set tasks to run on activation/decativation + // which are inheritable .onEnter(task(($) => $`echo enter`)) .onEnter(task({ workingDir: "..", diff --git a/files/deno/worker.ts b/files/deno/worker.ts index f6e641f0..5a9528f7 100644 --- a/files/deno/worker.ts +++ b/files/deno/worker.ts @@ -7,6 +7,7 @@ // modify the Deno namespace before anyone touches it // NOTE: only import types +import { shimDenoNamespace } from "../../utils/worker.ts"; import type { DriverRequests, DriverResponse } from "./mod.ts"; self.onmessage = onMsg; @@ -46,157 +47,3 @@ async function serializeConfig(uri: string, envVars: Record) { listedFiles: shimHandle.getListedFiles(), }; } - -function shimDenoNamespace(envVars: Record) { - const { envShim, getAccessedEnvKeys } = denoEnvShim(envVars); - Object.defineProperty(Deno, "env", { - value: envShim, - }); - const { fsShims, getReadFiles, getListedFiles } = denoFsReadShim(); - for (const [name, shim] of fsShims) { - Object.defineProperty(Deno, name, { - value: shim, - }); - } - return { getAccessedEnvKeys, getReadFiles, getListedFiles }; -} - -function denoFsReadShim() { - const readFiles = new Set(); - const listedFiles = new Set(); - - const fsShims = [ - ["watchFs", () => { - throw new Error("Deno.watchFs API is disabled"); - }] as const, - ...[ - // TODO: systemize a way to make sure this - // tracks deno APIs - Deno.readFile, - Deno.readTextFileSync, - Deno.readTextFile, - Deno.readTextFileSync, - Deno.stat, - Deno.statSync, - Deno.lstat, - Deno.lstatSync, - Deno.readLink, - Deno.readLinkSync, - Deno.open, - Deno.openSync, - Deno.readDir, - Deno.readDirSync, - ].map((old) => { - const replace = ( - path: string | URL, - opts: Deno.ReadFileOptions | Deno.OpenOptions | undefined, - ) => { - readFiles.add(typeof path == "string" ? path : path.pathname); - return (old as any)(path, opts); - }; - return [old.name, replace] as const; - }), - ]; - { - const old = Deno.readDir; - const replace: typeof old = ( - path: string | URL, - ) => { - let parent = typeof path === "string" ? path : path.pathname; - readFiles.add(parent); - if (!parent.endsWith("/")) { - parent = path + "/"; - } - const oldIteratorFn = old(path)[Symbol.asyncIterator]; - return { - [Symbol.asyncIterator]: () => { - const iter = oldIteratorFn(); - return { - throw: iter.throw, - return: iter.return, - async next() { - const val = await iter.next(); - if (val.done) return val; - listedFiles.add(parent + val.value.name); - return val; - }, - }; - }, - }; - }; - fsShims.push(["readDir", replace]); - } - { - const old = Deno.readDirSync; - const replace: typeof old = ( - path: string | URL, - ) => { - let parent = typeof path === "string" ? path : path.pathname; - readFiles.add(parent); - if (!parent.endsWith("/")) { - parent = path + "/"; - } - const oldIteratorFn = old(path)[Symbol.iterator]; - return { - [Symbol.iterator]: () => { - const iter = oldIteratorFn(); - return { - throw: iter.throw, - return: iter.return, - next() { - const val = iter.next(); - if (val.done) return val; - listedFiles.add(parent + val.value.name); - return val; - }, - }; - }, - }; - }; - fsShims.push(["readDirSync", replace]); - } - return { - fsShims, - getReadFiles() { - return [...readFiles.keys()]; - }, - getListedFiles() { - return [...listedFiles.keys()]; - }, - }; -} - -function denoEnvShim(vars: Record) { - const map = new Map([...Object.entries(vars)]); - const accessedEnvKeys = new Set(); - const envShim: Deno.Env = { - get(key: string) { - accessedEnvKeys.add(key); - return map.get(key); - }, - set(key: string, val: string) { - accessedEnvKeys.add(key); - map.set(key, val); - }, - has(key: string) { - accessedEnvKeys.add(key); - return map.has(key); - }, - delete(key: string) { - accessedEnvKeys.add(key); - map.delete(key); - }, - toObject() { - for (const key of map.keys()) { - accessedEnvKeys.add(key); - } - return Object.fromEntries([...map.entries()]); - }, - }; - return { - envShim, - getAccessedEnvKeys() { - return [...accessedEnvKeys.keys()]; - }, - }; -} diff --git a/files/mod.ts b/files/mod.ts index 44bfb4d7..9fd5acf9 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -2,27 +2,27 @@ // NOTE: avoid adding sources of randomness // here to make the resulting config reasonably stable -// across serializaiton. No random identifiers. +// across repeated serializaitons. No random identifiers. -import { multibase32, multibase64 } from "../deps/common.ts"; +import { deep_eql, multibase32, multibase64, zod } from "../deps/common.ts"; // ports specific imports import portsValidators from "../modules/ports/types.ts"; import type { AllowedPortDep, InstallConfigFat, - InstallSet, InstallSetRefProvision, PortsModuleConfigHashed, } from "../modules/ports/types.ts"; -import logger from "../utils/logger.ts"; +import getLogger from "../utils/logger.ts"; +const logger = getLogger(import.meta); import { $, defaultCommandBuilder, objectHash, Path, thinInstallConfig, - unwrapParseRes, + unwrapZodRes, } from "../utils/mod.ts"; import * as std_ports from "../modules/ports/std.ts"; import * as cpy from "../ports/cpy_bs.ts"; @@ -42,18 +42,32 @@ import type { Provision, WellKnownProvision, } from "../modules/envs/types.ts"; +import modulesValidators from "../modules/types.ts"; + +const validators = { + envVars: zod.record( + modulesValidators.envVarName, + zod.union([zod.string(), zod.number()]), + ), +}; + +export type EnvParent = string | string[] | boolean | undefined; export type EnvDefArgs = { name: string; - installs?: InstallConfigFat[]; + installs?: InstallConfigFat | InstallConfigFat[]; allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; /** * If true or not set, will base the task's env on top - * of the default env (usually `main`). If false, will build on - * top of a new env. If given a string, will use the identified env as a base + * of the default env (usually `main`). + * Will be a standalone env if false. + * If given a string, will use the identified env as a base * for the task env. + * If given a set of strings, will inherit from each. + * If conflict is detected during multiple inheritance, the + * item from the env specified at a higher index will override. */ - inherit?: string | boolean; + inherit?: EnvParent; desc?: string; vars?: Record; /** @@ -88,8 +102,8 @@ export type TaskDefArgs = { workingDir?: string | Path; vars?: Record; allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; - installs?: InstallConfigFat[]; - inherit?: string | boolean; + installs?: InstallConfigFat | InstallConfigFat[]; + inherit?: EnvParent; }; export type DenoTaskDefArgs = TaskDefArgs & { @@ -119,10 +133,24 @@ export type DenoTaskDefArgs = TaskDefArgs & { type TaskDefTyped = DenoTaskDefArgs & { ty: "denoFile@v1" }; export class Ghjkfile { - #installSets = new Map(); + #installSets = new Map< + string, + { installs: Set; allowedBuildDeps: Record } + >(); + #seenInstallConfs = new Map(); + #seenAllowedDepPorts = new Map(); #tasks = new Map(); #bb = new Map(); #seenEnvs: Record = {}; + #finalizedEnvs: Record< + string, + { + finalized: ReturnType; + installSetId?: string; + vars: Record; + envHash: string; + } + > = {}; /* dump() { return { @@ -147,7 +175,7 @@ export class Ghjkfile { } */ addInstall(setId: string, configUnclean: InstallConfigFat) { - const config = unwrapParseRes( + const config = unwrapZodRes( portsValidators.installConfigFat.safeParse(configUnclean), { config: configUnclean, @@ -155,9 +183,10 @@ export class Ghjkfile { `error parsing InstallConfig`, ); + const hash = objectHashSafe(config); + this.#seenInstallConfs.set(hash, config); const set = this.#getSet(setId); - set.installs.push(config); - logger(import.meta).debug("install added", config); + set.installs.add(hash); } setAllowedPortDeps( @@ -168,25 +197,30 @@ export class Ghjkfile { set.allowedBuildDeps = Object.fromEntries( reduceAllowedDeps(deps).map(( dep, - ) => [dep.manifest.name, dep]), + ) => { + const hash = objectHashSafe(dep); + this.#seenAllowedDepPorts.set(hash, dep); + return [dep.manifest.name, hash]; + }), ); } addTask(args: TaskDefTyped) { - // NOTE: we make sure the env base declared here exists - // this call is necessary to make sure that a `task` can - // be declared before the `env` but still depend on it. - // Order-indepency like this makes the `ghjk.ts` way less - // brittle. - if (typeof args.inherit == "string") { - this.addEnv({ name: args.inherit }); + // FIXME: combine the task env processing + // with normal env processing + // we currrently process task envs at once in the end + // to do env deduplication + if (args.vars) { + args.vars = unwrapZodRes(validators.envVars.safeParse(args.vars), { + vars: args.vars, + }); } let key = args.name; if (!key) { switch (args.ty) { case "denoFile@v1": { const { fn, workingDir, ...argsRest } = args; - key = objectHash(JSON.parse(JSON.stringify({ + key = objectHashSafe({ ...argsRest, workingDir: workingDir instanceof Path ? workingDir.toString() @@ -198,7 +232,7 @@ export class Ghjkfile { fn: fn.toString(), } : {}), - }))); + }); key = multibase64.base64urlpad.encode( multibase32.base32.decode(key), ); @@ -214,21 +248,30 @@ export class Ghjkfile { return key; } - addEnv(args: EnvDefArgs) { - let env = this.#seenEnvs[args.name]?.[0]; + addEnv(key: string, args: EnvDefArgsPartial) { + let env = this.#seenEnvs[key]?.[0]; if (!env) { let finalizer: EnvFinalizer; - env = new EnvBuilder(this, (fin) => finalizer = fin, args.name); - this.#seenEnvs[args.name] = [env, finalizer!]; + env = new EnvBuilder( + this, + (fin) => { + finalizer = fin; + }, + key, + args.name, + ); + this.#seenEnvs[key] = [env, finalizer!]; } - if (args.inherit !== undefined) { - env.inherit(args.inherit); + if ("inherit" in args) { + env.inherit(args.inherit!); } if (args.installs) { - env.install(...args.installs); + env.install( + ...(Array.isArray(args.installs) ? args.installs : [args.installs]), + ); } if (args.allowedBuildDeps) { - env.allowedBuildDeps(args.allowedBuildDeps); + env.allowedBuildDeps(...args.allowedBuildDeps); } if (args.desc) { env.desc(args.desc); @@ -256,8 +299,18 @@ export class Ghjkfile { throw new Error(`task under "${key}" has unexpected type ${task.ty}`); } if (task.fn) { - const custom$ = task$(argv, envVars, workingDir); - await task.fn(custom$, { argv, env: envVars, $: custom$, workingDir }); + const custom$ = task$( + argv, + envVars, + workingDir, + ``, + ); + await task.fn(custom$, { + argv, + env: Object.freeze(envVars), + $: custom$, + workingDir, + }); } } @@ -269,13 +322,34 @@ export class Ghjkfile { }, ) { // make sure referenced envs exist - this.addEnv({ name: defaultEnv }); - this.addEnv({ name: defaultBaseEnv }); + this.addEnv(defaultEnv, { name: defaultEnv }); + this.addEnv(defaultBaseEnv, { name: defaultBaseEnv }); + + // crearte the envs used by the tasks + const taskToEnvMap = {} as Record; + for ( + const [key, { inherit, vars, installs, allowedBuildDeps }] of this.#tasks + .entries() + ) { + const envKey = `____task_env_${key}`; + this.addEnv(envKey, { + inherit, + vars, + installs, + allowedBuildDeps, + }); + taskToEnvMap[key] = envKey; + } + try { - const envsConfig = this.#processEnvs(defaultEnv, defaultBaseEnv); + const envsConfig = this.#processEnvs( + defaultEnv, + defaultBaseEnv, + taskToEnvMap, + ); const tasksConfig = this.#processTasks( envsConfig, - defaultBaseEnv, + taskToEnvMap, ); const portsConfig = this.#processInstalls(); @@ -301,7 +375,7 @@ export class Ghjkfile { #getSet(setId: string) { let set = this.#installSets.get(setId); if (!set) { - set = { installs: [], allowedBuildDeps: {} }; + set = { installs: new Set(), allowedBuildDeps: {} }; this.#installSets.set(setId, set); } return set; @@ -309,7 +383,7 @@ export class Ghjkfile { #addToBlackboard(inp: unknown) { // jsonHash.digest is async - const hash = objectHash(JSON.parse(JSON.stringify(inp))); + const hash = objectHashSafe(inp); if (!this.#bb.has(hash)) { this.#bb.set(hash, inp); @@ -317,102 +391,227 @@ export class Ghjkfile { return hash; } - /** this processes the defined envs, normalizing dependency (i.e. "envBase") + #mergeEnvs(keys: string[], childName: string) { + const mergedVars = {} as Record; + let mergedInstalls = new Set(); + const mergedOnEnterHooks = []; + const mergedOnExitHooks = []; + const mergedAllowedBuildDeps = {} as Record< + string, + [string, string] | undefined + >; + for (const parentName of keys) { + const { vars, installSetId, finalized } = this.#finalizedEnvs[parentName]; + mergedOnEnterHooks.push(...finalized.onEnterHookTasks); + mergedOnExitHooks.push(...finalized.onExitHookTasks); + for (const [key, val] of Object.entries(vars)) { + const conflict = mergedVars[key]; + // if parents share a parent themselves, they will have + // the same item so it's not exactly a conflict + if (conflict && val !== conflict[0]) { + logger.warn( + "environment variable conflict on multiple env inheritance, parent2 was chosen", + { + child: childName, + parent1: conflict[1], + parent2: parentName, + variable: key, + }, + ); + } + mergedVars[key] = [val, parentName]; + } + if (!installSetId) { + continue; + } + const set = this.#installSets.get(installSetId)!; + mergedInstalls = mergedInstalls.union(set.installs); + for ( + const [key, val] of Object.entries(set.allowedBuildDeps) + ) { + const conflict = mergedAllowedBuildDeps[key]; + if (conflict && !deep_eql(val, conflict[0])) { + logger.warn( + "allowedBuildDeps conflict on multiple env inheritance, parent2 was chosen", + { + child: childName, + parent1: conflict[1], + parent2: parentName, + depPort: key, + }, + ); + } + mergedAllowedBuildDeps[key] = [val, parentName]; + } + } + const outInstallSet = { + installs: mergedInstalls, + allowedBuildDeps: Object.fromEntries( + Object.entries(mergedAllowedBuildDeps).map(( + [key, val], + ) => [key, val![0]]), + ), + }; + const outVars = Object.fromEntries( + Object.entries(mergedVars).map(([key, val]) => [key, val![0]]), + ); + return { + installSet: outInstallSet, + onEnterHookTasks: mergedOnEnterHooks, + onExitHookTasks: mergedOnExitHooks, + vars: outVars, + }; + } + + #resolveEnvBases( + parent: EnvParent, + taskToEnvMap: Record, + defaultBaseEnv: string, + childKey: string, + ) { + if (parent === false) { + return []; + } + if (parent === true || parent === undefined || parent === null) { + return childKey != defaultBaseEnv ? [defaultBaseEnv] : []; + } + const inheritSet = typeof parent == "string" + ? [parent] + : parent + ? [...new Set(parent)] // js sets preserve insert order + : []; + + const swapJobs = [] as [number, string][]; + for (let ii = 0; ii < inheritSet.length; ii++) { + const parentKey = inheritSet[ii]; + // parent env exists + // note: env inheritances take prioritiy over + // tasks of the same name + if (this.#seenEnvs[parentKey]) { + //noop + } else if (this.#tasks.has(parentKey)) { + // while the ghjkfile only refers to the task envs + // by the task name, we must use the private task + // env key for inheritance resolution + // the swap job take cares of that + swapJobs.push([ii, taskToEnvMap[parentKey]] as const); + } else { + throw new Error( + `env "${childKey}" inherits from "${parentKey} but no env or task found under key"`, + ); + } + } + for (const [idx, envKey] of swapJobs) { + inheritSet[idx] = envKey; + } + return inheritSet; + } + + /** this processes the defined envs, resolving inherit * relationships to produce the standard EnvsModuleConfig */ #processEnvs( defaultEnv: string, defaultBaseEnv: string, + taskToEnvMap: Record, ) { const all = {} as Record< string, - ReturnType & { envBaseResolved: null | string } + ReturnType & { envBaseResolved: null | string[] } >; const indie = [] as string[]; + const deps = new Map(); const revDeps = new Map(); for ( - const [_name, [_builder, finalizer]] of Object.entries(this.#seenEnvs) + const [_key, [_builder, finalizer]] of Object.entries(this.#seenEnvs) ) { const final = finalizer(); - const envBaseResolved = typeof final.inherit === "string" - ? final.inherit - : (final.inherit !== false) && defaultBaseEnv != final.name - ? defaultBaseEnv - : null; - all[final.name] = { ...final, envBaseResolved }; - if (envBaseResolved) { - const parentRevDeps = revDeps.get(envBaseResolved); - if (parentRevDeps) { - parentRevDeps.push(final.name); - } else { - revDeps.set(envBaseResolved, [final.name]); + + const envBaseResolved = this.#resolveEnvBases( + final.inherit, + taskToEnvMap, + defaultBaseEnv, + final.key, + ); + all[final.key] = { ...final, envBaseResolved }; + if (envBaseResolved.length > 0) { + deps.set(final.key, [...envBaseResolved]); + for (const base of envBaseResolved) { + const parentRevDeps = revDeps.get(base); + if (parentRevDeps) { + parentRevDeps.push(final.key); + } else { + revDeps.set(base, [final.key]); + } } } else { - indie.push(final.name); + indie.push(final.key); } } - const processed = {} as Record< - string, - { installSetId?: string; vars: Record } - >; - const moduleConfig: EnvsModuleConfig = { envs: {}, defaultEnv }; + const moduleConfig: EnvsModuleConfig = { + envs: {}, + defaultEnv, + envsNamed: {}, + }; const workingSet = indie; + // console.log({ + // indie, + // deps, + // }); while (workingSet.length > 0) { const item = workingSet.pop()!; const final = all[item]; - const base = final.envBaseResolved - ? processed[final.envBaseResolved] - : null; + const base = this.#mergeEnvs(final.envBaseResolved ?? [], final.key); + // console.log({ parents: final.envBaseResolved, child: final.key, base }); - const processedVars = { - ...(base?.vars ?? {}), + const finalVars = { + ...base.vars, ...final.vars, }; - let processedInstallSetId: string | undefined; + let finalInstallSetId: string | undefined; { const installSet = this.#installSets.get(final.installSetId); if (installSet) { - // if base also has an install set - if (base?.installSetId) { - // merge the parent's installs into this one - const baseSet = this.#installSets.get( - base.installSetId, - )!; - const mergedInstallsSet = new Set([ - ...installSet.installs, - ...baseSet.installs, - ]); - installSet.installs = [...mergedInstallsSet.values()]; - for ( - const [key, val] of Object.entries(baseSet.allowedBuildDeps) - ) { - // prefer the port dep config of the child over any - // similar deps in the parent - if (!installSet.allowedBuildDeps[key]) { - installSet.allowedBuildDeps[key] = val; - } + installSet.installs = installSet.installs + .union(base.installSet.installs); + for ( + const [key, val] of Object.entries(base.installSet.allowedBuildDeps) + ) { + // prefer the port dep config of the child over any + // similar deps in the base + if (!installSet.allowedBuildDeps[key]) { + installSet.allowedBuildDeps[key] = val; } } - processedInstallSetId = final.installSetId; + finalInstallSetId = final.installSetId; } // if there's no install set found under the id else { // implies that the env has not ports explicitly configured - if (base) { - processedInstallSetId = base.installSetId; + if (final.envBaseResolved) { + // has a singluar parent + if (final.envBaseResolved.length == 1) { + finalInstallSetId = + this.#finalizedEnvs[final.envBaseResolved[0]].installSetId; + } else { + this.#installSets.set(final.installSetId, base.installSet); + finalInstallSetId = final.installSetId; + } } } } - processed[final.name] = { - installSetId: processedInstallSetId, - vars: processedVars, - }; const hooks = [ + ...base.onEnterHookTasks.map( + (key) => [key, "hook.onEnter.ghjkTask"] as const, + ), ...final.onEnterHookTasks.map( (key) => [key, "hook.onEnter.ghjkTask"] as const, ), + ...base.onExitHookTasks.map( + (key) => [key, "hook.onExit.ghjkTask"] as const, + ), ...final.onExitHookTasks.map( (key) => [key, "hook.onExit.ghjkTask"] as const, ), @@ -443,10 +642,12 @@ export class Ghjkfile { }, ); }); - moduleConfig.envs[final.name] = { + + // the actual final final recipe + const recipe: EnvRecipe = { desc: final.desc, provides: [ - ...Object.entries(processedVars).map(( + ...Object.entries(finalVars).map(( [key, val], ) => { const prov: WellKnownProvision = { ty: "posix.envVar", key, val }; @@ -456,26 +657,52 @@ export class Ghjkfile { ...hooks, ], }; - if (processedInstallSetId) { + + if (finalInstallSetId) { const prov: InstallSetRefProvision = { ty: "ghjk.ports.InstallSetRef", - setId: processedInstallSetId, + setId: finalInstallSetId, }; - moduleConfig.envs[final.name].provides.push(prov); + recipe.provides.push(prov); } - const curRevDeps = revDeps.get(final.name); - if (curRevDeps) { - workingSet.push(...curRevDeps); - revDeps.delete(final.name); + // hashing takes care of deduplication + const envHash = objectHashSafe(recipe); + this.#finalizedEnvs[final.key] = { + installSetId: finalInstallSetId, + vars: finalVars, + finalized: final, + envHash, + }; + // hashing takes care of deduplication + moduleConfig.envs[envHash] = recipe; + + if (final.name) { + moduleConfig.envsNamed[final.name] = envHash; + } + + for (const revDepKey of revDeps.get(final.key) ?? []) { + const revDepDeps = deps.get(revDepKey)!; + // swap remove + const idx = revDepDeps.indexOf(final.key); + const last = revDepDeps.pop()!; + if (revDepDeps.length > idx) { + revDepDeps[idx] = last; + } + + if (revDepDeps.length == 0) { + deps.delete(revDepKey); + workingSet.push(revDepKey); + } } } // sanity checks - if (revDeps.size > 0) { + if (deps.size > 0) { throw new Error(`working set empty but pending items found`, { cause: { - revDeps, + deps, workingSet, + revDeps, }, }); } @@ -484,7 +711,7 @@ export class Ghjkfile { #processTasks( envsConfig: EnvsModuleConfig, - defaultBaseEnv: string, + taskToEnvMap: Record, ) { const indie = [] as string[]; const deps = new Map(); @@ -515,103 +742,16 @@ export class Ghjkfile { const workingSet = indie; const localToFinalKey = {} as Record; const moduleConfig: TasksModuleConfig = { - envs: {}, tasks: {}, tasksNamed: [], }; while (workingSet.length > 0) { const key = workingSet.pop()!; const args = this.#tasks.get(key)!; - const { workingDir, desc, dependsOn, inherit } = args; + const { workingDir, desc, dependsOn } = args; - const taskEnvRecipe: EnvRecipe = { - provides: [], - }; - const taskInstallSet: InstallSet = { - installs: args.installs ?? [], - allowedBuildDeps: Object.fromEntries( - reduceAllowedDeps(args.allowedBuildDeps ?? []).map(( - dep, - ) => [dep.manifest.name, dep]), - ), - }; - - const envBaseResolved = typeof inherit === "string" - ? inherit - : (inherit !== false) - ? defaultBaseEnv - : null; - - const envBaseRecipe = envBaseResolved - ? envsConfig.envs[envBaseResolved] - : null; - - const mergedEnvVars = args.vars ?? {}; - if (envBaseRecipe) { - for ( - const prov of envBaseRecipe - .provides as ( - | WellKnownProvision - | InstallSetRefProvision - | InlineTaskHookProvision - )[] - ) { - // task envs don't need hooks - if ( - prov.ty == "hook.onEnter.ghjkTask" || - prov.ty == "hook.onExit.ghjkTask" - ) { - continue; - } else if (prov.ty == "posix.envVar") { - if (!mergedEnvVars[prov.key]) { - mergedEnvVars[prov.key] = prov.val; - } - } else if (prov.ty == "ghjk.ports.InstallSetRef") { - const baseSet = this.#installSets.get(prov.setId)!; - const mergedInstallsSet = new Set([ - ...taskInstallSet.installs, - ...baseSet.installs, - ]); - taskInstallSet.installs = [...mergedInstallsSet.values()]; - for ( - const [key, val] of Object.entries(baseSet.allowedBuildDeps) - ) { - // prefer the port dep config of the child over any - // similar deps in the base - if (!taskInstallSet.allowedBuildDeps[key]) { - taskInstallSet.allowedBuildDeps[key] = val; - } - } - } else { - taskEnvRecipe.provides.push(prov); - } - } - } - if (taskInstallSet.installs.length > 0) { - const setId = `ghjkTaskInstSet___${key}`; - this.#installSets.set(setId, taskInstallSet); - const prov: InstallSetRefProvision = { - ty: "ghjk.ports.InstallSetRef", - setId, - }; - taskEnvRecipe.provides.push(prov); - } - - taskEnvRecipe.provides.push( - ...Object.entries(mergedEnvVars).map(( - [key, val], - ) => { - const prov: WellKnownProvision = { - ty: "posix.envVar", - key, - val: val.toString(), - }; - return prov; - }), - ); - - const envHash = objectHash(JSON.parse(JSON.stringify(taskEnvRecipe))); - moduleConfig.envs[envHash] = taskEnvRecipe; + const envKey = taskToEnvMap[key]; + const { envHash } = this.#finalizedEnvs[envKey]; const def: TaskDefHashed = { ty: args.ty, @@ -628,7 +768,7 @@ export class Ghjkfile { ), } : {}, - envHash, + envKey: envHash, }; const taskHash = objectHash(def); // we prefer the name as a key if present @@ -685,6 +825,7 @@ export class Ghjkfile { }); } + // reduce task based env hooks for (const [_name, env] of Object.entries(envsConfig.envs)) { env.provides = env.provides.map( (prov) => { @@ -719,10 +860,18 @@ export class Ghjkfile { const [setId, set] of this.#installSets.entries() ) { out.sets[setId] = { - installs: set.installs.map((inst) => this.#addToBlackboard(inst)), - allowedDeps: this.#addToBlackboard(Object.fromEntries( + installs: [...set.installs.values()] + .map((instHash) => + this.#addToBlackboard(this.#seenInstallConfs.get(instHash)) + ), + allowedBuildDeps: this.#addToBlackboard(Object.fromEntries( Object.entries(set.allowedBuildDeps).map( - ([key, dep]) => [key, this.#addToBlackboard(dep)], + ( + [key, depHash], + ) => [ + key, + this.#addToBlackboard(this.#seenAllowedDepPorts.get(depHash)), + ], ), )), }; @@ -732,22 +881,62 @@ export class Ghjkfile { } type EnvFinalizer = () => { - name: string; + key: string; + name?: string; installSetId: string; - inherit: string | boolean; + inherit: string | string[] | boolean; vars: Record; desc?: string; onEnterHookTasks: string[]; onExitHookTasks: string[]; }; -// this class will be exposed to users and thus features -// a contrived implementation of the `build`/`finalize` method -// all to avoid exposing the function in the public api +export type EnvDefArgsPartial = + & { name?: string } + & Omit; +// +// /** +// * A version of {@link EnvDefArgs} that has all container +// * fields guratneed initialized to non null but possible empty values. +// */ +// export type EnvDefArgsReqiured = +// & Required> +// & Partial>; +// +// export function envDef( +// args: EnvDefArgsPartial, +// ): EnvDefArgsReqiured; +// export function envDef( +// name: string, +// args?: Omit, +// ): EnvDefArgsReqiured; +// export function envDef( +// nameOrArgs: string | EnvDefArgsPartial, +// argsMaybe?: Omit, +// ): EnvDefArgsReqiured { +// const args = typeof nameOrArgs == "object" +// ? nameOrArgs +// : { ...argsMaybe, name: nameOrArgs }; +// return { +// ...args, +// installs: [], +// inherit: args.inherit ?? [], +// vars: args.vars ?? {}, +// onExit: args.onExit ?? [], +// onEnter: args.onEnter ?? [], +// allowedBuildDeps: args.allowedBuildDeps ?? [], +// }; +// } + +/** + this class will be exposed to users and thus features + a contrived implementation of the `build`/`finalize` method + all to avoid exposing the function in the public api + */ export class EnvBuilder { #installSetId: string; #file: Ghjkfile; - #inherit: string | boolean = true; + #inherit: string | string[] | boolean = true; #vars: Record = {}; #desc?: string; #onEnterHookTasks: string[] = []; @@ -756,11 +945,13 @@ export class EnvBuilder { constructor( file: Ghjkfile, setFinalizer: (fin: EnvFinalizer) => void, - public name: string, + public readonly key: string, + public name?: string, ) { this.#file = file; - this.#installSetId = `ghjkEnvProvInstSet___${name}`; + this.#installSetId = `ghjkEnvProvInstSet___${key}`; setFinalizer(() => ({ + key: this.key, name: this.name, installSetId: this.#installSetId, inherit: this.#inherit, @@ -773,7 +964,7 @@ export class EnvBuilder { })); } - inherit(inherit: string | boolean) { + inherit(inherit: string | string[] | boolean) { this.#inherit = inherit; return this; } @@ -792,7 +983,7 @@ export class EnvBuilder { * Configure the build time deps allowed to be used by ports. * This is treated as a single set and will replace previously any configured set. */ - allowedBuildDeps(deps: (AllowedPortDep | InstallConfigFat)[]) { + allowedBuildDeps(...deps: (AllowedPortDep | InstallConfigFat)[]) { this.#file.setAllowedPortDeps(this.#installSetId, deps); return this; } @@ -809,7 +1000,10 @@ export class EnvBuilder { * Add multiple environment variable. */ vars(envVars: Record) { - Object.assign(this.#vars, envVars); + Object.assign( + this.#vars, + unwrapZodRes(validators.envVars.safeParse(envVars), { envVars }), + ); return this; } @@ -857,6 +1051,7 @@ function task$( argv: string[], env: Record, workingDir: string, + loggerName: string, ) { const custom$ = Object.assign( // NOTE: order is important on who assigns to who @@ -866,8 +1061,9 @@ function task$( }), { argv, - env, - workingDir, + env: Object.freeze(env), + workingDir: $.path(workingDir), + logger: getLogger(loggerName), }, ); return custom$; @@ -883,13 +1079,24 @@ export function reduceAllowedDeps( ): AllowedPortDep[] { return deps.map( (dep: any) => { - const res = portsValidators.allowedPortDep.safeParse(dep); - if (res.success) return res.data; + { + const res = portsValidators.allowedPortDep.safeParse(dep); + if (res.success) return res.data; + } + const inst = unwrapZodRes( + portsValidators.installConfigFat.safeParse(dep), + dep, + "invalid allowed dep object, provide either InstallConfigFat or AllowedPortDep objects", + ); const out: AllowedPortDep = { - manifest: dep.port, - defaultInst: thinInstallConfig(dep), + manifest: inst.port, + defaultInst: thinInstallConfig(inst), }; return portsValidators.allowedPortDep.parse(out); }, ); } + +function objectHashSafe(obj: unknown) { + return objectHash(JSON.parse(JSON.stringify(obj))); +} diff --git a/ghjk.ts b/ghjk.ts index 8b41f5a4..09af3c11 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,6 +1,7 @@ export { sophon } from "./hack.ts"; -import { config, env, install, stdDeps } from "./hack.ts"; +import { config, install, task } from "./hack.ts"; import * as ports from "./ports/mod.ts"; +import { sedLock } from "./std.ts"; config({ defaultBaseEnv: "test", @@ -15,4 +16,46 @@ install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], ports.cpy_bs(), + ports.deno_ghrel({ version: "1.44.2" }), +); + +task( + "lock-sed", + async ($) => { + const GHJK_VERSION = "0.2.1"; + const DENO_VERSION = "1.44.2"; + await sedLock( + $.path(import.meta.dirname!), + { + lines: { + "./.github/workflows/*.yml": [ + [/(DENO_VERSION: ").*(")/, DENO_VERSION], + ], + "**": [ + [/(GHJK_VERSION = ").*(")/, GHJK_VERSION], + ], + "./install.sh": [ + [/(GHJK_VERSION="\$\{GHJK_VERSION:-v).*(\}")/, GHJK_VERSION], + [/(DENO_VERSION="\$\{DENO_VERSION:-v).*(\}")/, DENO_VERSION], + ], + }, + ignores: [ + // ignore this file to avoid hits on the regexps + `ghjk.ts`, + `.git`, + // TODO: std function for real ignore handling + ...(await $.path(".gitignore").readText()) + .split("\n") + .map((l) => l.trim()) + .filter((line) => line.length > 0) + .map((l) => `${l}${l.endsWith("*") ? "" : "*"}`), + ...(await $.path(".ghjk/.gitignore").readText()) + .split("\n") + .map((l) => l.trim()) + .filter((line) => line.length > 0) + .map((l) => `.ghjk/${l}${l.endsWith("*") ? "" : "*"}`), + ], + }, + ); + }, ); diff --git a/host/mod.ts b/host/mod.ts index 9caace9c..e09c505f 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -14,20 +14,29 @@ import * as denoFile from "../files/deno/mod.ts"; import type { ModuleBase } from "../modules/mod.ts"; import { GhjkCtx } from "../modules/types.ts"; import { serializePlatform } from "../modules/ports/types/platform.ts"; -import { DePromisify } from "../port.ts"; export interface CliArgs { ghjkShareDir: string; ghjkfilePath?: string; ghjkDirPath?: string; + reFlagSet: boolean; + lockedFlagSet: boolean; } type HostCtx = { fileHashMemoStore: Map>; curEnvVars: Record; + reFlagSet: boolean; + lockedFlagSet: boolean; }; +const GHJK_VERSION = "0.2.1"; + export async function cli(args: CliArgs) { + logger().debug(`ghjk CLI`, GHJK_VERSION); + if (args.reFlagSet && args.lockedFlagSet) { + throw new Error("GHJK_LOCKED && GHJK_RE both set"); + } // items to run at end of function const defer = [] as (() => Promise)[]; @@ -53,7 +62,16 @@ export async function cli(args: CliArgs) { : undefined, blackboard: new Map(), }; - logger().debug({ ghjkfilePath: gcx.ghjkfilePath, ghjkDir: gcx?.ghjkDir }); + const hcx: HostCtx = { + fileHashMemoStore: new Map(), + curEnvVars: Deno.env.toObject(), + reFlagSet: args.reFlagSet, + lockedFlagSet: args.lockedFlagSet, + }; + logger().debug("context established", { + ghjkDir: gcx?.ghjkDir.toString(), + ghjkfilePath: gcx.ghjkfilePath?.toString(), + }); if (!await gcx.ghjkDir.join(".gitignore").exists()) { gcx.ghjkDir.join(".gitignore").writeText($.dedent` @@ -63,11 +81,11 @@ export async function cli(args: CliArgs) { // this returns nothing if no valid lockifle or ghjkfile // is found - const commands = await commandsFromConfig(gcx); + const commands = await commandsFromConfig(hcx, gcx); if (commands) { serializedConfig = commands.config; - // lock entries are generated across program usage - // so we defer writing it out until the end + // lock entries are also generated across program usage + // so we defer another write out until the end defer.push(commands.writeLockFile); for ( @@ -86,7 +104,7 @@ export async function cli(args: CliArgs) { const root = new cliffy_cmd.Command() .name("ghjk") - .version("0.1.1") // FIXME: better way to resolve version + .version(GHJK_VERSION) .description("Programmable runtime manager.") .action(function () { this.showHelp(); @@ -179,41 +197,29 @@ export async function cli(args: CliArgs) { await Promise.all(defer.map((fn) => fn())); } -async function commandsFromConfig(gcx: GhjkCtx) { - const hcx: HostCtx = { - fileHashMemoStore: new Map(), - curEnvVars: Deno.env.toObject(), - }; - +async function commandsFromConfig(hcx: HostCtx, gcx: GhjkCtx) { const lockFilePath = gcx.ghjkDir.join("lock.json"); const hashFilePath = gcx.ghjkDir.join("hash.json"); const foundLockObj = await readLockFile(lockFilePath); const foundHashObj = await readHashFile(hashFilePath); + if (hcx.lockedFlagSet) { + if (!foundLockObj) { + throw new Error("GHJK_LOCKED set but no lockfile found"); + } + if (!foundHashObj) { + throw new Error("GHJK_LOCKED set but no hashfile found"); + } + } + const lockEntries = {} as Record; const ghjkfileHash = await gcx.ghjkfilePath?.exists() ? await fileDigestHex(hcx, gcx.ghjkfilePath!) : undefined; - let configExt: SerializedConfigExt | null = null; - // TODO: figure out cross platform lockfiles :O - if ( - foundLockObj && // lockfile found - foundHashObj && - foundLockObj.version == "0" && - // avoid reserializing the config if - // the ghjkfile and environment is _satisfcatorily_ - // similar. "cache validation" - await isHashFileValid(hcx, foundLockObj, foundHashObj, ghjkfileHash) - ) { - configExt = { - config: foundLockObj.config, - envVarHashes: foundHashObj.envVarHashes, - readFileHashes: foundHashObj.readFileHashes, - listedFiles: foundHashObj.listedFiles, - }; + if (!hcx.reFlagSet && foundLockObj) { logger().debug("loading lockfile", lockFilePath); for (const man of foundLockObj.config.modules) { const mod = std_modules.map[man.id]; @@ -234,13 +240,36 @@ async function commandsFromConfig(gcx: GhjkCtx) { entry as Json, ); } + } + + let configExt: SerializedConfigExt | null = null; + let wasReSerialized = false; + if ( + !hcx.reFlagSet && + foundLockObj && + foundHashObj && + (hcx.lockedFlagSet || + // avoid reserializing the config if + // the ghjkfile and environment is _satisfcatorily_ + // similar. "cache validation" + foundLockObj.version == "0" && + await isHashFileValid(hcx, foundLockObj, foundHashObj, ghjkfileHash)) + ) { + configExt = { + config: foundLockObj.config, + envVarHashes: foundHashObj.envVarHashes, + readFileHashes: foundHashObj.readFileHashes, + listedFiles: foundHashObj.listedFiles, + }; } else if (gcx.ghjkfilePath) { logger().info("serializing ghjkfile", gcx.ghjkfilePath); configExt = await readGhjkfile(hcx, gcx.ghjkfilePath); + wasReSerialized = true; } else { // nothing to get the commands from return; } + const newHashObj: zod.infer = { version: "0", ghjkfileHash, @@ -277,14 +306,27 @@ async function commandsFromConfig(gcx: GhjkCtx) { subCommands[cmdName] = [cmd, man.id]; } } - if (!foundHashObj || !deep_eql(newHashObj, foundHashObj)) { + + 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 + // TODO(#90): file system lock file while ghjk is running + // to avoid multiple instances from clobbering each other + let lastLockObj = { ...foundLockObj }; return { subCommands, config: configExt.config, async writeLockFile() { + if (hcx.lockedFlagSet) return; + const newLockObj: zod.infer = { version: "0", platform: serializePlatform(Deno.build), @@ -306,7 +348,8 @@ async function commandsFromConfig(gcx: GhjkCtx) { ), ); // avoid writing lockfile if nothing's changed - if (!foundLockObj || !deep_eql(newLockObj, foundLockObj)) { + if (!lastLockObj || !deep_eql(newLockObj, lastLockObj)) { + lastLockObj = { ...newLockObj }; await lockFilePath.writeJsonPretty(newLockObj); } }, @@ -319,6 +362,7 @@ async function isHashFileValid( foundHashFile: zod.infer, ghjkfileHash?: string, ) { + // TODO: figure out cross platform lockfiles :O const platformMatch = () => serializePlatform(Deno.build) == foundLockFile.platform; @@ -360,7 +404,7 @@ async function isHashFileValid( type DigestsMap = Record; -type SerializedConfigExt = DePromisify< +type SerializedConfigExt = Awaited< ReturnType >; diff --git a/host/types.ts b/host/types.ts index a4d64f80..68283c8d 100644 --- a/host/types.ts +++ b/host/types.ts @@ -1,6 +1,5 @@ import { zod } from "../deps/common.ts"; import moduleValidators from "../modules/types.ts"; -// import portsValidator from "../modules/ports/types.ts"; /* const blackboard = zod.object({ // installs: zod.record(zod.string(), portsValidator.installConfigFat), diff --git a/install.sh b/install.sh index 89de7e54..263c7a9b 100755 --- a/install.sh +++ b/install.sh @@ -2,10 +2,10 @@ set -e -u -GHJK_VERSION="${GHJK_VERSION:-v0.1.0-alpha}" +GHJK_VERSION="${GHJK_VERSION:-v0.2.1}" GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.ts}" GHJK_SHARE_DIR="${GHJK_SHARE_DIR:-$HOME/.local/share/ghjk}" -DENO_VERSION="${DENO_VERSION:-v1.43.1}" +DENO_VERSION="${DENO_VERSION:-v1.44.2}" # make sure the version is prepended with v if [ "${DENO_VERSION#"v"}" = "$DENO_VERSION" ]; then diff --git a/install/hook.fish b/install/hook.fish index c7757744..072c19fe 100644 --- a/install/hook.fish +++ b/install/hook.fish @@ -12,8 +12,10 @@ end 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" + # we ignore previously loaded GHJK_ENV when switching + # directories + test "$argv" = "VARIABLE SET PWD"; and set next_env "" test -z $next_env; and set next_env "default" if set --query GHJK_CLEANUP_FISH @@ -63,7 +65,7 @@ function ghjk_reload --on-variable PWD --on-event ghjk_env_dir_change # 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 $next_env_dir/activate.fish -ot $local_ghjk_dir/../ghjk.ts + if test (__ghjk_get_mtime_ts $next_env_dir/activate.fish) -lt (__ghjk_get_mtime_ts $local_ghjk_dir/../ghjk.ts) set_color FF4500 if test $next_env = "default" echo "[ghjk] Possible drift from default environment, please sync..." @@ -95,7 +97,7 @@ function __ghjk_preexec --on-event fish_preexec # exists if set --query GHJK_NEXTFILE; and test -f "$GHJK_NEXTFILE"; - ghjk_reload "$(cat $GHJK_NEXTFILE)" + ghjk_reload (cat $GHJK_NEXTFILE) rm "$GHJK_NEXTFILE" # activate script has reloaded diff --git a/install/hook.sh b/install/hook.sh index cd050562..181ccbc5 100644 --- a/install/hook.sh +++ b/install/hook.sh @@ -96,6 +96,9 @@ precmd() { # - the PWD changes if [ "$GHJK_LAST_PWD" != "$PWD" ]; then + # we ignore previously loaded GHJK_ENV when switching + # directories + unset GHJK_ENV ghjk_reload export GHJK_LAST_PWD="$PWD" diff --git a/main.ts b/main.ts index 42aed1fd..35a2f835 100755 --- a/main.ts +++ b/main.ts @@ -23,6 +23,12 @@ if (import.meta.main) { ghjkdir = std_path.resolve(std_path.dirname(ghjkfile), ".ghjk"); } await cli({ + // FIXME: better + reFlagSet: !!Deno.env.get("GHJK_RE") && + !(["false", "", ""].includes(Deno.env.get("GHJK_RE")!)), + lockedFlagSet: !!Deno.env.get("GHJK_LOCKED") && + !(["false", "", ""].includes(Deno.env.get("GHJK_LOCKED")!)), + ghjkShareDir: Deno.env.get("GHJK_SHARE_DIR") ?? dirs().shareDir.resolve("ghjk").toString(), ghjkfilePath: ghjkfile ? std_path.resolve(Deno.cwd(), ghjkfile) : undefined, diff --git a/mod.ts b/mod.ts index 275fe178..a19e33c1 100644 --- a/mod.ts +++ b/mod.ts @@ -64,7 +64,7 @@ export type FileArgs = { /** * Additional ports that can be used as build time dependencies. * - * This applies to the "main" env. + * This applies to the `defaultBaseEnv` env. */ allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[]; /** @@ -73,13 +73,13 @@ export type FileArgs = { * is still respected. * True by default. * - * This applies to the "main" env. + * This applies to the `defaultBaseEnv` env. */ stdDeps?: boolean; /** * (unstable) Allow runtimes from std deps to be used as build time dependencies. * - * This applies to the "main" env. + * This applies to the `defaultBaseEnv` env. */ enableRuntimes?: boolean; /** @@ -89,7 +89,7 @@ export type FileArgs = { /** * Tasks to expose to the CLI. */ - tasks?: DenoTaskDefArgs[]; + tasks?: Record; /** * Different envs availaible to the CLI. */ @@ -129,9 +129,11 @@ export const file = Object.freeze(function file( const DEFAULT_BASE_ENV_NAME = "main"; const builder = new Ghjkfile(); - const mainEnv = builder.addEnv({ + const mainEnv = builder.addEnv(DEFAULT_BASE_ENV_NAME, { name: DEFAULT_BASE_ENV_NAME, - inherit: false, + inherit: args.defaultBaseEnv && args.defaultBaseEnv != DEFAULT_BASE_ENV_NAME + ? args.defaultBaseEnv + : false, installs: args.installs, desc: "the default default environment.", }); @@ -151,7 +153,7 @@ export const file = Object.freeze(function file( // if the user explicitly passes a port config, we let // it override any ports of the same kind from the std library for ( - const dep of args.stdDeps !== false // note: this is true if it's undefined + const dep of args.stdDeps !== false // i.e.e true if undefined ? stdDeps({ enableRuntimes: args.enableRuntimes ?? false }) : [] ) { @@ -160,17 +162,25 @@ export const file = Object.freeze(function file( } defaultBuildDepsSet.push(dep); } - mainEnv.allowedBuildDeps(defaultBuildDepsSet); + // we override the allowedBuildDeps of the + // defaultEnvBase each time `file` or `env` are used + if (args.defaultBaseEnv) { + builder.addEnv(args.defaultBaseEnv, { + allowedBuildDeps: defaultBuildDepsSet, + }); + } else { + mainEnv.allowedBuildDeps(...defaultBuildDepsSet); + } }; // populate the bulid deps by the default args first replaceDefaultBuildDeps(args); for (const env of args.envs ?? []) { - builder.addEnv(env); + builder.addEnv(env.name, env); } - for (const task of args.tasks ?? []) { - builder.addTask({ ...task, ty: "denoFile@v1" }); + for (const [name, def] of Object.entries(args.tasks ?? {})) { + builder.addTask({ name, ...def, ty: "denoFile@v1" }); } // FIXME: ses.lockdown to freeze primoridials @@ -241,23 +251,29 @@ export const file = Object.freeze(function file( const args = typeof nameOrArgs == "object" ? nameOrArgs : { ...argsMaybe, name: nameOrArgs }; - return builder.addEnv(args); + return builder.addEnv(args.name, args); }, config( - { defaultBaseEnv, defaultEnv, ...rest }: SecureConfigArgs, + newArgs: SecureConfigArgs, ) { if ( - rest.enableRuntimes !== undefined || - rest.allowedBuildDeps !== undefined || - rest.stdDeps !== undefined + newArgs.defaultBaseEnv !== undefined || + newArgs.enableRuntimes !== undefined || + newArgs.allowedBuildDeps !== undefined || + newArgs.stdDeps !== undefined + ) { + replaceDefaultBuildDeps(newArgs); + } + if ( + newArgs.defaultBaseEnv && + newArgs.defaultBaseEnv != DEFAULT_BASE_ENV_NAME ) { - replaceDefaultBuildDeps(rest); + mainEnv.inherit(newArgs.defaultBaseEnv); } // NOTE:we're deep mutating the first args from above args = { - ...rest, - ...{ defaultEnv, defaultBaseEnv }, + ...newArgs, }; }, }; diff --git a/modules/envs/inter.ts b/modules/envs/inter.ts new file mode 100644 index 00000000..90f2597f --- /dev/null +++ b/modules/envs/inter.ts @@ -0,0 +1,26 @@ +import type { GhjkCtx } from "../types.ts"; +import type { EnvsCtx } from "./mod.ts"; + +export function getEnvsCtx( + gcx: GhjkCtx, +): EnvsCtx { + const key = "ctx.envs"; + let ctx = gcx.blackboard.get(key) as + | EnvsCtx + | undefined; + + if (!ctx) { + ctx = { + activeEnv: "", + keyToName: {}, + config: { + defaultEnv: "", + envs: {}, + envsNamed: {}, + }, + }; + gcx.blackboard.set(key, ctx); + } + + return ctx; +} diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 8a8fddd8..31021de1 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -1,7 +1,7 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { $, detectShellPath, Json, unwrapParseRes } from "../../utils/mod.ts"; +import { $, detectShellPath, Json, unwrapZodRes } from "../../utils/mod.ts"; import validators from "./types.ts"; import type { EnvRecipeX, @@ -12,16 +12,18 @@ import { type GhjkCtx, type ModuleManifest } from "../types.ts"; import { ModuleBase } from "../mod.ts"; import type { Blackboard } from "../../host/types.ts"; import { cookPosixEnv } from "./posix.ts"; -import { getInstallSetStore, installGraphToSetMeta } from "../ports/inter.ts"; +import { getPortsCtx, installGraphToSetMeta } from "../ports/inter.ts"; import type { InstallSetProvision, InstallSetRefProvision, } from "../ports/types.ts"; import { buildInstallGraph, syncCtxFromGhjk } from "../ports/sync.ts"; -import { getEnvsCtx } from "../utils.ts"; +import { getEnvsCtx } from "./inter.ts"; +import { getTasksCtx } from "../tasks/inter.ts"; export type EnvsCtx = { activeEnv: string; + keyToName: Record; config: EnvsModuleConfigX; }; @@ -33,13 +35,13 @@ type EnvsLockEnt = zod.infer; export class EnvsModule extends ModuleBase { processManifest( - _ctx: GhjkCtx, + gcx: GhjkCtx, manifest: ModuleManifest, bb: Blackboard, _lockEnt: EnvsLockEnt | undefined, ) { function unwrapParseCurry(res: zod.SafeParseReturnType) { - return unwrapParseRes(res, { + return unwrapZodRes(res, { id: manifest.id, config: manifest.config, bb, @@ -51,9 +53,12 @@ export class EnvsModule extends ModuleBase { const setEnv = Deno.env.get("GHJK_ENV"); const activeEnv = setEnv && setEnv != "" ? setEnv : config.defaultEnv; - const envsCtx = getEnvsCtx(_ctx); + const envsCtx = getEnvsCtx(gcx); envsCtx.activeEnv = activeEnv; envsCtx.config = config; + for (const [name, key] of Object.entries(config.envsNamed)) { + envsCtx.keyToName[key] = [name, ...(envsCtx.keyToName[key] ?? [])]; + } return Promise.resolve(envsCtx); } @@ -62,6 +67,32 @@ export class EnvsModule extends ModuleBase { gcx: GhjkCtx, ecx: EnvsCtx, ) { + function envKeyArgs( + args: { + taskKeyMaybe?: string; + envKeyMaybe?: string; + }, + ) { + const { envKeyMaybe, taskKeyMaybe } = args; + if (taskKeyMaybe && envKeyMaybe) { + throw new Error( + "--task-env option can not be combined with [envName] argument", + ); + } + if (taskKeyMaybe) { + const tasksCx = getTasksCtx(gcx); + const taskDef = tasksCx.config.tasks[taskKeyMaybe]; + if (!taskDef) { + throw new Error(`no task found under key "${taskKeyMaybe}"`); + } + return { envKey: taskDef.envKey }; + } + const actualKey = ecx.config.envsNamed[envKeyMaybe ?? ecx.activeEnv]; + return actualKey + ? { envKey: actualKey, envName: envKeyMaybe ?? ecx.activeEnv } + : { envKey: envKeyMaybe ?? ecx.activeEnv }; + } + return { envs: new cliffy_cmd .Command() @@ -78,10 +109,14 @@ export class EnvsModule extends ModuleBase { .action(() => { // deno-lint-ignore no-console console.log( - Object.entries(ecx.config.envs) - .map(([name, { desc }]) => - `${name}${desc ? ": " + desc : ""}` - ) + Object.entries(ecx.config.envsNamed) + // envs that have names which start with underscors + // don't show up in the cli list + .filter(([key]) => !key.startsWith("_")) + .map(([name, hash]) => { + const { desc } = ecx.config.envs[hash]; + return `${name}${desc ? ": " + desc : ""}`; + }) .join("\n"), ); }), @@ -93,9 +128,17 @@ 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]") - .action(async function (_, envNameMaybe) { - const envName = envNameMaybe ?? ecx.config.defaultEnv; - await activateEnv(gcx, envName); + .option( + "-t, --task-env ", + "Synchronize to the environment used by the named task", + { standalone: true }, + ) + .action(async function ({ taskEnv }, envKeyMaybe) { + const { envKey } = envKeyArgs({ + taskKeyMaybe: taskEnv, + envKeyMaybe, + }); + await activateEnv(envKey); }), ) .command( @@ -105,9 +148,17 @@ export class EnvsModule extends ModuleBase { - If no [envName] is specified, this will cook the active env [${ecx.activeEnv}]`) .arguments("[envName:string]") - .action(async function (_void, envNameMaybe) { - const envName = envNameMaybe ?? ecx.activeEnv; - await reduceAndCookEnv(gcx, ecx, envName); + .option( + "-t, --task-env ", + "Synchronize to the environment used by the named task", + { standalone: true }, + ) + .action(async function ({ taskEnv }, envKeyMaybe) { + const { envKey, envName } = envKeyArgs({ + taskKeyMaybe: taskEnv, + envKeyMaybe, + }); + await reduceAndCookEnv(gcx, ecx, envKey, envName ?? envKey); }), ) .command( @@ -119,27 +170,48 @@ export class EnvsModule extends ModuleBase { - If no [envName] is specified and no env is active, this shows details of the default env [${ecx.config.defaultEnv}]. `) .arguments("[envName:string]") - .action(async function (_void, envNameMaybe) { - const envName = envNameMaybe ?? ecx.activeEnv; - const env = ecx.config.envs[envName]; + .option( + "-t, --task-env ", + "Synchronize to the environment used by the named task", + { standalone: true }, + ) + .action(async function ({ taskEnv }, envKeyMaybe) { + const { envKey } = envKeyArgs({ + taskKeyMaybe: taskEnv, + envKeyMaybe, + }); + const env = ecx.config.envs[envKey]; if (!env) { - throw new Error(`No env found under given name "${envName}"`); + throw new Error(`no env found under "${envKeyMaybe}"`); } // deno-lint-ignore no-console - console.log($.inspect(await showableEnv(gcx, env, envName))); + console.log($.inspect(await showableEnv(gcx, env, envKey))); }), ), sync: new cliffy_cmd.Command() .description(`Synchronize your shell to what's in your config. -Just simply cooks and activates an environment. +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]") - .action(async function (_, envNameMaybe) { - const envName = envNameMaybe ?? ecx.activeEnv; - await reduceAndCookEnv(gcx, ecx, envName); - await activateEnv(gcx, envName); + .option( + "-t, --task-env ", + "Synchronize to the environment used by the named task", + { standalone: true }, + ) + .action(async function ({ taskEnv }, envKeyMaybe) { + const { envKey, envName } = envKeyArgs({ + taskKeyMaybe: taskEnv, + envKeyMaybe, + }); + await reduceAndCookEnv( + gcx, + ecx, + envKey, + envName ?? envKey, + ); + await activateEnv(envKey); }), }; } @@ -169,15 +241,16 @@ Just simply cooks and activates an environment. async function reduceAndCookEnv( gcx: GhjkCtx, ecx: EnvsCtx, + envKey: string, envName: string, ) { - const recipe = ecx.config.envs[envName]; + const recipe = ecx.config.envs[envKey]; if (!recipe) { - throw new Error(`No env found under given name "${envName}"`); + throw new Error(`No env found under given name "${envKey}"`); } // TODO: diff env and ask confirmation from user - const envDir = $.path(gcx.ghjkDir).join("envs", envName); + const envDir = $.path(gcx.ghjkDir).join("envs", envKey); /* const recipeShowable = await showableEnv(gcx, recipe, envName); const oldRecipeShowable = {}; @@ -212,15 +285,31 @@ async function reduceAndCookEnv( await cookPosixEnv({ gcx, recipe, - envName, + envKey: envName, envDir: envDir.toString(), createShellLoaders: true, }); - if (envName == ecx.config.defaultEnv) { + if (envKey == ecx.config.defaultEnv) { const defaultEnvDir = $.path(gcx.ghjkDir).join("envs", "default"); await $.removeIfExists(defaultEnvDir); await defaultEnvDir.symlinkTo(envDir, { kind: "relative" }); } + await $.co( + Object + .entries(ecx.config.envsNamed) + .map(async ([name, key]) => { + if (key == envKey) { + const namedDir = $.path(gcx.ghjkDir).join("envs", name); + await $.removeIfExists(namedDir); + await namedDir.symlinkTo(envDir, { kind: "relative" }); + } + if (name == ecx.config.defaultEnv || key == ecx.config.defaultEnv) { + const defaultEnvDir = $.path(gcx.ghjkDir).join("envs", "default"); + await $.removeIfExists(defaultEnvDir); + await defaultEnvDir.symlinkTo(envDir, { kind: "relative" }); + } + }), + ); } async function showableEnv( @@ -273,8 +362,8 @@ async function showableEnv( break; } case "ghjk.ports.InstallSetRef": { - const setStore = getInstallSetStore(gcx); - const set = setStore.get(prov.setId); + const portsCx = getPortsCtx(gcx); + const set = portsCx.config.sets[prov.setId]; if (!set) { throw new Error( `unable to find install set ref provisioned under id ${prov.setId}`, @@ -298,10 +387,10 @@ async function showableEnv( }; } -async function activateEnv(gcx: GhjkCtx, envName: string) { +async function activateEnv(envKey: string) { const nextfile = Deno.env.get("GHJK_NEXTFILE"); if (nextfile) { - await $.path(gcx.ghjkDir).join("envs", "next").writeText(envName); + await $.path(nextfile).writeText(envKey); } else { const shell = await detectShellPath(); if (!shell) { @@ -312,6 +401,6 @@ async function activateEnv(gcx: GhjkCtx, envName: string) { // 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 $`${shell}`.env({ GHJK_ENV: envKey }); } } diff --git a/modules/envs/posix.ts b/modules/envs/posix.ts index 6be27c6f..848cdad3 100644 --- a/modules/envs/posix.ts +++ b/modules/envs/posix.ts @@ -13,15 +13,15 @@ import getLogger from "../../utils/logger.ts"; const logger = getLogger(import.meta); export async function cookPosixEnv( - { gcx, recipe, envName, envDir, createShellLoaders = false }: { + { gcx, recipe, envKey, envDir, createShellLoaders = false }: { gcx: GhjkCtx; recipe: EnvRecipeX; - envName: string; + envKey: string; envDir: string; createShellLoaders?: boolean; }, ) { - logger.debug("cooking env", envName, { envDir }); + logger.debug("cooking env", envKey, { envDir }); const reducedRecipe = await reduceStrangeProvisions(gcx, recipe); await $.removeIfExists(envDir); // create the shims for the user's environment @@ -39,7 +39,7 @@ export async function cookPosixEnv( const libPaths = [] as string[]; const includePaths = [] as string[]; const vars = { - GHJK_ENV: envName, + GHJK_ENV: envKey, } as Record; const onEnterHooks = [] as [string, string[]][]; const onExitHooks = [] as [string, string[]][]; @@ -195,7 +195,7 @@ async function shimLinkPaths( async function writeActivators( gcx: GhjkCtx, envDir: string, - env: Record, + envVars: Record, pathVars: Record, onEnterHooks: [string, string[]][], onExitHooks: [string, string[]][], @@ -218,7 +218,8 @@ async function writeActivators( const ghjkShimName = "__ghjk_shim"; const onEnterHooksEscaped = onEnterHooks.map(([cmd, args]) => [cmd == "ghjk" ? ghjkShimName : cmd, ...args] - .join(" ").replaceAll("'", "'\\''") + .join(" ") + .replaceAll("'", "'\\''") ); const onExitHooksEscaped = onExitHooks.map(([cmd, args]) => [cmd == "ghjk" ? ghjkShimName : cmd, ...args] @@ -232,6 +233,10 @@ async function writeActivators( // // posix shell version posix: [ + `# shellcheck shell=sh`, + `# 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`, @@ -242,31 +247,62 @@ async function writeActivators( `${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 - // 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}';`, - ``, - ]), + ...Object.entries(envVars).flatMap(([key, val]) => { + const safeVal = val.replaceAll("\\", "\\\\").replaceAll("'", "'\\''"); + // avoid triggering unbound variable if -e is set + // by defaulting to a value that's guranteed to + // be differeint than `key` + // TODO: avoid invalid key values elsewhere + const safeComparisionKey = `$\{${key}:-_${ + val.replace(/['"]/g, "").slice(0, 2) + }}`; + return [ + // we only restore the old $KEY value at cleanup if value of $KEY + // is the one set by the activate script + // we also single quote the supplied values to avoid + // any embedded expansion/execution + // we also single quote the entire test section to avoid + // expansion when creating the cleanup + // string (that's why we "escaped single quote" the value) + // NOTE: the addition sign at the end + `GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'[ \"${safeComparisionKey}\" = '\\''${safeVal}'\\'' ] && '` + + // we want to capture the old $key value here so we wrap those + // with double quotes but the rest is in single quotes + // within the value of $key + // 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};');`, + `export ${key}='${safeVal}';`, + ``, + ]; + }), ``, `# 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}";`, - ``, - ]), + ...Object.entries(pathVars).flatMap(([key, val]) => { + const safeVal = val.replaceAll("\\", "\\\\").replaceAll("'", "'\\''"); + return [ + // 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 '\\'"^${safeVal}"\\'' | tr "\\n" ":");${key}="\${${key}%:}";';`, + // FIXME: we're allowing expansion in the value to allow + // readable $ghjkDirVar usage + // (for now safe since all paths are created within ghjk) + `export ${key}="${safeVal}:$${key}";`, + ``, + ]; + }), ``, `# hooks that want to invoke ghjk are made to rely`, - `# on this shim instead improving latency`, + `# on this shim to improving latency`, + // the ghjk executable is itself a shell script + // which execs deno, we remove the middleman here + // also, the ghjk executable is optional ghjk_sh(gcx, denoDir, ghjkShimName), ``, + `# only run the hooks in interactive mode`, `case "$-" in`, - ` *i*)`, + ` *i*) # if the shell variables contain "i"`, ``, ` # on enter hooks`, ...onEnterHooksEscaped.map((line) => ` ${line}`), @@ -296,23 +332,35 @@ async function writeActivators( `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}';`, - ``, - ]), + ...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 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;);`, + `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};`, - ``, - ]), + ...Object.entries(pathVars).flatMap(([key, val]) => { + const safeVal = val.replaceAll("\\", "\\\\").replaceAll("'", "\\'"); + return [ + `set --global --append GHJK_CLEANUP_FISH 'set --global --export --path ${key} (string match --invert --regex \\''"^${safeVal}"'\\' $${key});';`, + `set --global --export --prepend ${key} "${safeVal}";`, + ``, + ]; + }), ``, `# hooks that want to invoke ghjk are made to rely`, - `# on this shim instead improving latency`, + `# on this shim to improving latency`, ghjk_fish(gcx, denoDir, ghjkShimName), ``, + `# only run the hooks in interactive mode`, `if status is-interactive;`, ` # on enter hooks`, ...onEnterHooksEscaped.map((line) => ` ${line}`), diff --git a/modules/envs/reducer.ts b/modules/envs/reducer.ts index 17db7793..ff402a3d 100644 --- a/modules/envs/reducer.ts +++ b/modules/envs/reducer.ts @@ -1,4 +1,4 @@ -import { unwrapParseRes } from "../../port.ts"; +import { unwrapZodRes } from "../../port.ts"; import type { GhjkCtx } from "../types.ts"; import type { EnvRecipeX, @@ -71,7 +71,7 @@ export async function reduceStrangeProvisions( const reduced = await reducer(items); reducedSet.push( ...reduced.map((prov) => - unwrapParseRes( + unwrapZodRes( validators.wellKnownProvision.safeParse(prov), { prov }, `error parsing reduced provision`, diff --git a/modules/envs/types.ts b/modules/envs/types.ts index a5589ee1..adc0cda7 100644 --- a/modules/envs/types.ts +++ b/modules/envs/types.ts @@ -1,5 +1,6 @@ import { std_path, zod } from "../../deps/common.ts"; import { installProvisionTy } from "../ports/types.ts"; +import moduleValidators from "../types.ts"; const absolutePath = zod.string().refine((path) => std_path.isAbsolute(path)); @@ -34,7 +35,7 @@ const wellKnownProvision = zod.discriminatedUnion( [ zod.object({ ty: zod.literal(wellKnownProvisionTypes[0]), - key: zod.string(), + key: moduleValidators.envVarName, val: zod.string(), }), ...hookProvisionTypes.map((ty) => @@ -71,7 +72,9 @@ const wellKnownEnvRecipe = envRecipe.merge(zod.object({ const envsModuleConfig = zod.object({ defaultEnv: zod.string(), envs: zod.record(zod.string(), envRecipe), -}).refine((conf) => conf.envs[conf.defaultEnv], { + // TODO: regex for env and task names + envsNamed: zod.record(zod.string(), zod.string()), +}).refine((conf) => conf.envsNamed[conf.defaultEnv], { message: `no env found under the provided "defaultEnv"`, }); diff --git a/modules/ports/inter.ts b/modules/ports/inter.ts index 7e884131..6f9c7efb 100644 --- a/modules/ports/inter.ts +++ b/modules/ports/inter.ts @@ -1,25 +1,19 @@ import type { GhjkCtx } from "../types.ts"; -import type { InstallSetX } from "./types.ts"; +import type { PortsCtx } from "./mod.ts"; import type { InstallGraph } from "./sync.ts"; // TODO: rename to install.ts -export type InstallSetStore = Map; - -/** - * {@link InstallSetStore} provides a way for other modules to get - * install sets from the {@link import("./types.ts").PortsModuleConfig} - */ -export function getInstallSetStore( +export function getPortsCtx( gcx: GhjkCtx, ) { - const id = "installSetStore"; - let memoStore = gcx.blackboard.get(id) as - | InstallSetStore + const id = "ctx.ports"; + let ctx = gcx.blackboard.get(id) as + | PortsCtx | undefined; - if (!memoStore) { - memoStore = new Map(); - gcx.blackboard.set(id, memoStore); + if (!ctx) { + ctx = { config: { sets: {} } }; + gcx.blackboard.set(id, ctx); } - return memoStore; + return ctx; } /** diff --git a/modules/ports/mod.ts b/modules/ports/mod.ts index fad8d558..e532caaf 100644 --- a/modules/ports/mod.ts +++ b/modules/ports/mod.ts @@ -1,7 +1,7 @@ export * from "./types.ts"; import { cliffy_cmd, Table, zod } from "../../deps/cli.ts"; -import { $, Json, unwrapParseRes } from "../../utils/mod.ts"; +import { $, Json, unwrapZodRes } from "../../utils/mod.ts"; import logger from "../../utils/logger.ts"; import validators, { installProvisionTy, @@ -13,6 +13,7 @@ import type { AllowedPortDep, InstallConfigResolved, InstallProvision, + InstallSetRefProvision, InstallSetX, PortsModuleConfigX, } from "./types.ts"; @@ -31,9 +32,9 @@ import type { Blackboard } from "../../host/types.ts"; import { getProvisionReducerStore } from "../envs/reducer.ts"; import { installSetReducer, installSetRefReducer } from "./reducers.ts"; import type { Provision, ProvisionReducer } from "../envs/types.ts"; -import { getInstallSetStore } from "./inter.ts"; -import { getActiveEnvInstallSetId, getEnvsCtx, getPortsCtx } from "../utils.ts"; +import { getPortsCtx } from "./inter.ts"; import { updateInstall } from "./utils.ts"; +import { getEnvsCtx } from "../envs/inter.ts"; export type PortsCtx = { config: PortsModuleConfigX; @@ -56,10 +57,9 @@ export class PortsModule extends ModuleBase { _lockEnt: PortsLockEnt | undefined, ) { function unwrapParseCurry(res: zod.SafeParseReturnType) { - return unwrapParseRes(res, { + return unwrapZodRes(res, { id: manifest.id, config: manifest.config, - bb, }, "error parsing module config"); } @@ -69,7 +69,6 @@ export class PortsModule extends ModuleBase { const pcx: PortsCtx = getPortsCtx(gcx); - const setStore = getInstallSetStore(gcx); // pre-process the install sets found in the config for (const [id, hashedSet] of Object.entries(hashedModConf.sets)) { // install sets in the config use hash references to dedupe InstallConfigs, @@ -80,7 +79,7 @@ export class PortsModule extends ModuleBase { ); const allowedDepSetHashed = unwrapParseCurry( validators.allowDepSetHashed.safeParse( - bb[hashedSet.allowedDeps], + bb[hashedSet.allowedBuildDeps], ), ); const allowedBuildDeps = Object.fromEntries( @@ -96,7 +95,6 @@ export class PortsModule extends ModuleBase { allowedBuildDeps, }; pcx.config.sets[id] = set; - setStore.set(id, set); } // register envrionment reducers for any @@ -150,13 +148,31 @@ export class PortsModule extends ModuleBase { "Update specific install", ) .option("-n, --update-all", "Update all installs") - .action(async (_opts) => { + .action(async (opts) => { const envsCtx = getEnvsCtx(gcx); const envName = envsCtx.activeEnv; const installSets = pcx.config.sets; - const currInstallSetId = getActiveEnvInstallSetId(envsCtx); + let currInstallSetId; + { + const activeEnvName = envsCtx.activeEnv; + const activeEnv = envsCtx.config + .envs[ + envsCtx.config.envsNamed[activeEnvName] ?? activeEnvName + ]; + if (!activeEnv) { + throw new Error( + `No env found under given name "${activeEnvName}"`, + ); + } + + const instSetRef = activeEnv.provides.filter((prov) => + prov.ty === installSetRefProvisionTy + )[0] as InstallSetRefProvision; + + currInstallSetId = instSetRef.setId; + } const currInstallSet = installSets[currInstallSetId]; const allowedDeps = currInstallSet.allowedBuildDeps; @@ -211,8 +227,8 @@ export class PortsModule extends ModuleBase { rows.push(row); } - if (_opts.updateInstall) { - const installName = _opts.updateInstall; + if (opts.updateInstall) { + const installName = opts.updateInstall; // TODO: convert from install name to install id, after port module refactor let installId!: string; const newVersion = latest.get(installId); @@ -226,7 +242,7 @@ export class PortsModule extends ModuleBase { return; } - if (_opts.updateAll) { + if (opts.updateAll) { for (const [installId, newVersion] of latest.entries()) { await updateInstall(gcx, installId, newVersion, allowedDeps); } @@ -264,7 +280,11 @@ export class PortsModule extends ModuleBase { } const memoStore = getResolutionMemo(gcx); for (const [hash, config] of Object.entries(entry.configResolutions)) { - logger().debug("restoring resolution from lockfile", config); + logger().debug( + "restoring resolution from lockfile", + config.portRef, + config.version, + ); memoStore.set(hash, Promise.resolve(config)); } @@ -300,7 +320,7 @@ async function getOldNewVersionComparison( // read from `recipe.json` and get installSetIds const recipeJson = JSON.parse(await Deno.readTextFile(recipePath)); - const reducedRecipe = unwrapParseRes( + const reducedRecipe = unwrapZodRes( envsValidators.envRecipe.safeParse(recipeJson), { envName, diff --git a/modules/ports/reducers.ts b/modules/ports/reducers.ts index 214d211b..ca34bdf4 100644 --- a/modules/ports/reducers.ts +++ b/modules/ports/reducers.ts @@ -1,6 +1,6 @@ //! Integration between Ports and Envs module -import { expandGlobsAndAbsolutize, unwrapParseRes } from "../../utils/mod.ts"; +import { expandGlobsAndAbsolutize, unwrapZodRes } from "../../utils/mod.ts"; import { Provision } from "../envs/types.ts"; import { GhjkCtx } from "../types.ts"; // NOTE: mod.ts must always be a type import @@ -25,7 +25,7 @@ export function installSetReducer(gcx: GhjkCtx) { 'only one "ghjkPorts" provision per environment is supported', ); } - const { set } = unwrapParseRes( + const { set } = unwrapZodRes( validators.installSetProvision.safeParse(provisions[0]), {}, "error parsing env provision", @@ -44,7 +44,7 @@ export function installSetRefReducer(gcx: GhjkCtx, pcx: PortsCtx) { return (provisions: InstallSetRefProvision[]) => directReducer(provisions.map( (prov) => { - const { setId } = unwrapParseRes( + const { setId } = unwrapZodRes( validators.installSetRefProvision.safeParse(prov), {}, "error parsing env provision", diff --git a/modules/ports/sync.ts b/modules/ports/sync.ts index f3cedaf9..c39e6593 100644 --- a/modules/ports/sync.ts +++ b/modules/ports/sync.ts @@ -20,7 +20,6 @@ import { AmbientAccessPort } from "./ambient.ts"; import { $, AVAIL_CONCURRENCY, - DePromisify, getInstallHash, getPortRef, objectHash, @@ -49,7 +48,7 @@ export function getResolutionMemo( return memoStore; } -export type SyncCtx = DePromisify>; +export type SyncCtx = Awaited>; export async function syncCtxFromGhjk( gcx: GhjkCtx, @@ -273,7 +272,7 @@ export async function installFromGraph( return installCtx.artifacts; } -export type InstallGraph = DePromisify>; +export type InstallGraph = Awaited>; // this returns a data structure containing all the info // required for installation including the dependency graph @@ -287,9 +286,6 @@ export async function buildInstallGraph( config: InstallConfigResolvedX; }; // this is all referring to port dependencies - // TODO: runtime dependencies - // NOTE: keep this easy to deserialize around as it's put directly - // into the lockfile const graph = { // maps from instHashId all: {} as Record, @@ -386,13 +382,13 @@ export async function buildInstallGraph( // this goes into graph.depEdges const deps: [string, string][] = []; for (const depId of manifest.buildDeps) { - const { manifest: depPort } = set.allowedBuildDeps[depId.name]; - if (!depPort) { + const dep = set.allowedBuildDeps[depId.name]; + if (!dep) { throw new Error( `unrecognized dependency "${depId.name}" specified by port "${manifest.name}@${manifest.version}"`, ); } - const portRef = addPort(depPort); + const portRef = addPort(dep.manifest); // get the install config of dependency // the conf is of the resolved kind which means @@ -412,7 +408,7 @@ export async function buildInstallGraph( }); } - deps.push([depInstallId, depPort.name]); + deps.push([depInstallId, dep.manifest.name]); // make sure the dependency knows this install depends on it const reverseDeps = graph.revDepEdges[depInstallId] ?? []; @@ -523,7 +519,7 @@ function resolveConfig( ); if (!match) { throw new Error(`error resolving verison: not found`, { - cause: { config, manifest }, + cause: { config, manifest, allVersions }, }); } version = match; diff --git a/modules/ports/types.ts b/modules/ports/types.ts index 1ca43480..249c127b 100644 --- a/modules/ports/types.ts +++ b/modules/ports/types.ts @@ -1,6 +1,7 @@ //! NOTE: type FooX is a version of Foo after zod processing/transformation import { semver, zod } from "../../deps/common.ts"; +import moduleValidators from "../types.ts"; import { relativeFileUrl } from "../../utils/url.ts"; import { ALL_ARCH, ALL_OS, archEnum, osEnum } from "./types/platform.ts"; @@ -151,7 +152,7 @@ const allowDepSetHashed = zod.record(zod.string(), zod.string()); const installSetHashed = zod.object({ installs: zod.array(zod.string()), - allowedDeps: zod.string(), + allowedBuildDeps: zod.string(), }); const installSet = zod.object({ @@ -191,7 +192,7 @@ const downloadArtifacts = zod.object({ }); const installArtifacts = zod.object({ - env: zod.record(zod.string(), zod.string()), + env: zod.record(moduleValidators.envVarName, zod.string()), installVersion: zod.string(), binPaths: zod.string().array(), libPaths: zod.string().array(), diff --git a/modules/tasks/deno.ts b/modules/tasks/deno.ts index fe246012..22e78bdb 100644 --- a/modules/tasks/deno.ts +++ b/modules/tasks/deno.ts @@ -8,6 +8,7 @@ import { std_url } from "../../deps/common.ts"; import { inWorker } from "../../utils/mod.ts"; import logger, { setup as setupLogger } from "../../utils/logger.ts"; +import { shimDenoNamespace } from "../../utils/worker.ts"; if (inWorker()) { initWorker(); @@ -60,6 +61,7 @@ async function importAndExec( uri: string, args: ExecTaskArgs, ) { + const _shimHandle = shimDenoNamespace(args.envVars); const mod = await import(uri); await mod.sophon.execTask(args); return true; diff --git a/modules/tasks/exec.ts b/modules/tasks/exec.ts index 17374495..8d72f145 100644 --- a/modules/tasks/exec.ts +++ b/modules/tasks/exec.ts @@ -1,4 +1,4 @@ -import { $, DePromisify } from "../../utils/mod.ts"; +import { $ } from "../../utils/mod.ts"; import type { TaskDefHashedX, TasksModuleConfigX } from "./types.ts"; import type { GhjkCtx } from "../types.ts"; @@ -8,13 +8,13 @@ import { execTaskDeno } from "./deno.ts"; const logger = getLogger(import.meta); import { cookPosixEnv } from "../envs/posix.ts"; +import { getEnvsCtx } from "../envs/inter.ts"; -export type TaskGraph = DePromisify>; +export type TaskGraph = Awaited>; export function buildTaskGraph( _gcx: GhjkCtx, tasksConfig: TasksModuleConfigX, - // env: Blackboard, ) { const graph = { indie: [] as string[], @@ -24,11 +24,13 @@ export function buildTaskGraph( depEdges: {} as Record, }; for (const [hash, task] of Object.entries(tasksConfig.tasks)) { - if (!tasksConfig.envs[task.envHash]) { + /* + * FIXME: find a way to pre-check if task envs are availaible + if (task.envKey && !envsCx.has(task.envKey)) { throw new Error( - `unable to find env referenced by task "${hash}" under hash "${task.envHash}"`, + `unable to find env referenced by task "${hash}" under key "${task.envKey}"`, ); - } + } */ if (!task.dependsOn || task.dependsOn.length == 0) { graph.indie.push(hash); } else { @@ -114,11 +116,13 @@ export async function execTask( const taskEnvDir = await Deno.makeTempDir({ prefix: `ghjkTaskEnv_${taskKey}_`, }); + const envsCx = getEnvsCtx(gcx); + const recipe = envsCx.config.envs[taskDef.envKey]; const { env: installEnvs } = await cookPosixEnv( { gcx, - recipe: tasksConfig.envs[taskDef.envHash], - envName: `taskEnv_${taskKey}`, + recipe: recipe ?? { provides: [] }, + envKey: taskDef.envKey ?? `taskEnv_${taskKey}`, envDir: taskEnvDir, }, ); @@ -134,10 +138,17 @@ export async function execTask( Object.entries(installEnvs).map( ( [key, val], - ) => [ - key, - key.match(/PATH/i) ? `${val}:${Deno.env.get(key) ?? ""}` : val, - ], + ) => { + if (key.match(/PATH/) && Deno.env.get(key)) { + val = [...new Set([val, ...Deno.env.get(key)!.split(":")]).keys()] + .filter((str) => str.length > 0) + .join(":"); + } + return [ + key, + val, + ]; + }, ), ), }; @@ -147,13 +158,16 @@ export async function execTask( "denoFile task found but no ghjkfile. This occurs when ghjk is working just on a lockfile alone", ); } + const workingDir = gcx.ghjkfilePath.parentOrThrow(); await execTaskDeno( - $.path(gcx.ghjkfilePath).toFileUrl().toString(), + gcx.ghjkfilePath.toFileUrl().toString(), { key: taskDef.key, argv: args, envVars, - workingDir: gcx.ghjkfilePath.parentOrThrow().toString(), + workingDir: taskDef.workingDir + ? workingDir.resolve(taskDef.workingDir).toString() + : workingDir.toString(), }, ); } else { diff --git a/modules/tasks/inter.ts b/modules/tasks/inter.ts new file mode 100644 index 00000000..5c4e3db3 --- /dev/null +++ b/modules/tasks/inter.ts @@ -0,0 +1,25 @@ +import type { GhjkCtx } from "../types.ts"; +import type { TasksCtx } from "./mod.ts"; + +export function getTasksCtx( + gcx: GhjkCtx, +): TasksCtx { + const key = "ctx.tasks"; + let ctx = gcx.blackboard.get(key) as + | TasksCtx + | undefined; + + if (!ctx) { + ctx = { + config: { tasks: {}, tasksNamed: [] }, + taskGraph: { + indie: [], + depEdges: {}, + revDepEdges: {}, + }, + }; + gcx.blackboard.set(key, ctx); + } + + return ctx; +} diff --git a/modules/tasks/mod.ts b/modules/tasks/mod.ts index 3ca91a15..7c87f7ff 100644 --- a/modules/tasks/mod.ts +++ b/modules/tasks/mod.ts @@ -1,7 +1,7 @@ export * from "./types.ts"; import { cliffy_cmd, zod } from "../../deps/cli.ts"; -import { Json, unwrapParseRes } from "../../utils/mod.ts"; +import { Json, unwrapZodRes } from "../../utils/mod.ts"; import validators from "./types.ts"; import type { TasksModuleConfigX } from "./types.ts"; @@ -10,7 +10,7 @@ import { ModuleBase } from "../mod.ts"; import { buildTaskGraph, execTask, type TaskGraph } from "./exec.ts"; import { Blackboard } from "../../host/types.ts"; -import { getTasksCtx } from "../utils.ts"; +import { getTasksCtx } from "./inter.ts"; export type TasksCtx = { config: TasksModuleConfigX; @@ -29,7 +29,7 @@ export class TasksModule extends ModuleBase { _lockEnt: TasksLockEnt | undefined, ) { function unwrapParseCurry(res: zod.SafeParseReturnType) { - return unwrapParseRes(res, { + return unwrapZodRes(res, { id: manifest.id, config: manifest.config, bb, @@ -54,29 +54,32 @@ export class TasksModule extends ModuleBase { tcx: TasksCtx, ) { const namedSet = new Set(tcx.config.tasksNamed); - const commands = Object.entries(tcx.config.tasks).map( - ([key, def]) => { - const cmd = new cliffy_cmd.Command() - .name(key) - .arguments("[argv...]") - .action(async (_, ...args) => { - await execTask( - gcx, - tcx.config, - tcx.taskGraph, - key, - args, - ); - }); - if (def.desc) { - cmd.description(def.desc); - } - if (!namedSet.has(key)) { - cmd.hidden(); - } - return cmd; - }, - ); + const commands = Object.keys(tcx.config.tasks) + .sort() + .map( + (key) => { + const def = tcx.config.tasks[key]; + const cmd = new cliffy_cmd.Command() + .name(key) + .useRawArgs() + .action(async (_, ...args) => { + await execTask( + gcx, + tcx.config, + tcx.taskGraph, + key, + args, + ); + }); + if (def.desc) { + cmd.description(def.desc); + } + if (!namedSet.has(key)) { + cmd.hidden(); + } + return cmd; + }, + ); const root = new cliffy_cmd.Command() .alias("x") .action(function () { diff --git a/modules/tasks/types.ts b/modules/tasks/types.ts index eefd0519..4472aca1 100644 --- a/modules/tasks/types.ts +++ b/modules/tasks/types.ts @@ -13,11 +13,11 @@ const taskDefBase = zod.object({ }); const taskDefFullBase = taskDefBase.merge(zod.object({ - env: envsValidators.envRecipe, + env: envsValidators.envRecipe.optional(), })); const taskDefHashedBase = taskDefBase.merge(zod.object({ - envHash: zod.string(), + envKey: zod.string(), })); const denoWorkerTaskDefBase = zod.object({ @@ -50,7 +50,6 @@ const taskDefHashed = // ]); const tasksModuleConfig = zod.object({ - envs: zod.record(zod.string(), envsValidators.envRecipe), /** * Tasks can be keyed with any old string. The keys * that also appear in {@field tasksNamed} will shown diff --git a/modules/types.ts b/modules/types.ts index 8ed5f4cd..f1c23ced 100644 --- a/modules/types.ts +++ b/modules/types.ts @@ -4,9 +4,7 @@ import type { Path } from "../utils/mod.ts"; // TODO: better module ident/versioning const moduleId = zod.string().regex(/[^ @]*/); -export const envsCtxBlackboardKey = "ctx.envs"; -export const portsCtxBlackboardKey = "ctx.ports"; -export const tasksCtxBlackboardKey = "ctx.tasks"; +const envVarName = zod.string().regex(/[a-zA-Z-_]*/); const moduleManifest = zod.object({ id: moduleId, @@ -25,4 +23,5 @@ export type GhjkCtx = { export default { moduleManifest, moduleId, + envVarName, }; diff --git a/modules/utils.ts b/modules/utils.ts deleted file mode 100644 index 49b18f56..00000000 --- a/modules/utils.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { EnvsCtx } from "./envs/mod.ts"; -import { PortsCtx } from "./ports/mod.ts"; -import { - InstallSetRefProvision, - installSetRefProvisionTy, -} from "./ports/types.ts"; -import { TasksCtx } from "./tasks/mod.ts"; -import { - envsCtxBlackboardKey, - GhjkCtx, - portsCtxBlackboardKey, - tasksCtxBlackboardKey, -} from "./types.ts"; - -export function getEnvsCtx( - gcx: GhjkCtx, -): EnvsCtx { - let envsCtx = gcx.blackboard.get(envsCtxBlackboardKey) as - | EnvsCtx - | undefined; - - if (!envsCtx) { - envsCtx = { - activeEnv: "", - config: { - defaultEnv: "", - envs: {}, - }, - }; - gcx.blackboard.set(envsCtxBlackboardKey, envsCtx); - } - - return envsCtx; -} - -export function getPortsCtx( - gcx: GhjkCtx, -): PortsCtx { - let portsCtx = gcx.blackboard.get(portsCtxBlackboardKey) as - | PortsCtx - | undefined; - - if (!portsCtx) { - portsCtx = { - config: { - sets: {}, - }, - }; - gcx.blackboard.set(portsCtxBlackboardKey, portsCtx); - } - - return portsCtx; -} - -export function getTasksCtx( - gcx: GhjkCtx, -): TasksCtx { - let tasksCtx = gcx.blackboard.get(tasksCtxBlackboardKey) as - | TasksCtx - | undefined; - - if (!tasksCtx) { - tasksCtx = { - config: { - envs: {}, - tasks: {}, - tasksNamed: [], - }, - taskGraph: { - indie: [], - revDepEdges: {}, - depEdges: {}, - }, - }; - gcx.blackboard.set(tasksCtxBlackboardKey, tasksCtx); - } - - return tasksCtx; -} - -export function getActiveEnvInstallSetId(envsCtx: EnvsCtx): string { - const activeEnvName = envsCtx.activeEnv; - const activeEnv = envsCtx.config.envs[activeEnvName]; - if (!activeEnv) { - throw new Error(`No env found under given name "${activeEnvName}"`); - } - - const instSetRef = activeEnv.provides.filter((prov) => - prov.ty === installSetRefProvisionTy - )[0] as InstallSetRefProvision; - - return instSetRef.setId; -} diff --git a/ports/act.ts b/ports/act.ts index bb6658ac..019f2362 100644 --- a/ports/act.ts +++ b/ports/act.ts @@ -7,7 +7,6 @@ import { type InstallArgs, type InstallConfigSimple, osXarch, - std_fs, std_path, unarchive, } from "../port.ts"; @@ -80,13 +79,20 @@ export class Port extends GithubReleasePort { await unarchive(fileDwnPath, args.tmpDirPath); + const tmpDir = $.path(args.tmpDirPath); + const binDir = await tmpDir.join("bin").ensureDir(); + for ( + const fileName of ["act"] + ) { + await tmpDir.join( + args.platform.os == "windows" ? fileName + ".exe" : fileName, + ).renameToDir(binDir); + } + const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); } - await std_fs.copy( - args.tmpDirPath, - installPath.join("bin").toString(), - ); + await tmpDir.rename(installPath); } } diff --git a/ports/cargo-binstall.ts b/ports/cargo-binstall.ts index 3056abbe..9c594dca 100644 --- a/ports/cargo-binstall.ts +++ b/ports/cargo-binstall.ts @@ -75,11 +75,11 @@ export class Port extends GithubReleasePort { await unarchive(fileDwnPath, args.tmpDirPath); const tmpDir = $.path(args.tmpDirPath); - await tmpDir.join("bin").ensureDir(); + const binDir = await tmpDir.join("bin").ensureDir(); for ( const fileName of ["cargo-binstall", "detect-targets", "detect-wasi"] ) { - await tmpDir.join(fileName).renameToDir(tmpDir.join("bin")); + await tmpDir.join(fileName).renameToDir(binDir); } const installPath = $.path(args.installPath); diff --git a/ports/deno_ghrel.ts b/ports/deno_ghrel.ts new file mode 100644 index 00000000..9da0d050 --- /dev/null +++ b/ports/deno_ghrel.ts @@ -0,0 +1,76 @@ +import { + $, + DownloadArgs, + dwnUrlOut, + GithubReleasePort, + InstallArgs, + InstallConfigSimple, + osXarch, + std_path, + unarchive, +} from "../port.ts"; +import { GithubReleasesInstConf, readGhVars } from "../modules/ports/ghrel.ts"; + +const manifest = { + ty: "denoWorker@v1" as const, + name: "deno_ghrel", + version: "0.1.0", + moduleSpecifier: import.meta.url, + platforms: osXarch(["linux", "darwin", "windows"], ["aarch64", "x86_64"]), +}; + +export default function conf( + config: + & InstallConfigSimple + & GithubReleasesInstConf = {}, +) { + return { + ...readGhVars(), + ...config, + port: manifest, + }; +} + +export class Port extends GithubReleasePort { + repoOwner = "denoland"; + repoName = "deno"; + + downloadUrls(args: DownloadArgs) { + const { installVersion, platform } = args; + const arch = platform.arch; + let os; + switch (platform.os) { + case "linux": + os = "unknown-linux-gnu"; + break; + case "windows": + os = "windows-msvc"; + break; + case "darwin": + os = "apple-darwin"; + break; + default: + throw new Error(`unsupported: ${platform}`); + } + return [ + this.releaseArtifactUrl( + installVersion, + `deno-${arch}-${os}.zip`, + ), + ].map(dwnUrlOut); + } + + async install(args: InstallArgs) { + const [{ name: fileName }] = this.downloadUrls(args); + + const fileDwnPath = std_path.resolve(args.downloadPath, fileName); + await unarchive(fileDwnPath, args.tmpDirPath); + + const installPath = $.path(args.installPath); + if (await installPath.exists()) { + await installPath.remove({ recursive: true }); + } + await $.path(args.tmpDirPath) + .rename(await installPath.join("bin").ensureDir()); + } +} diff --git a/ports/mod.ts b/ports/mod.ts index 25be49d5..c9851c14 100644 --- a/ports/mod.ts +++ b/ports/mod.ts @@ -4,6 +4,7 @@ export { default as cargo_binstall } from "./cargo-binstall.ts"; export { default as cargobi } from "./cargobi.ts"; export { default as cpy_bs } from "./cpy_bs.ts"; export { default as curl } from "./curl.ts"; +export { default as deno_ghrel } from "./deno_ghrel.ts"; export { default as earthly } from "./earthly.ts"; export { default as git } from "./git.ts"; export { default as infisical } from "./infisical.ts"; diff --git a/ports/temporal_cli.ts b/ports/temporal_cli.ts index 4bc867a6..132f2d6a 100644 --- a/ports/temporal_cli.ts +++ b/ports/temporal_cli.ts @@ -6,7 +6,6 @@ import { InstallArgs, InstallConfigSimple, osXarch, - std_fs, std_path, unarchive, } from "../port.ts"; @@ -62,13 +61,20 @@ export class Port extends GithubReleasePort { const fileDwnPath = std_path.resolve(args.downloadPath, fileName); await unarchive(fileDwnPath, args.tmpDirPath); + const tmpDir = $.path(args.tmpDirPath); + const binDir = await tmpDir.join("bin").ensureDir(); + for ( + const fileName of ["temporal"] + ) { + await tmpDir.join( + args.platform.os == "windows" ? fileName + ".exe" : fileName, + ).renameToDir(binDir); + } + const installPath = $.path(args.installPath); if (await installPath.exists()) { await installPath.remove({ recursive: true }); } - await std_fs.copy( - args.tmpDirPath, - installPath.join("bin").toString(), - ); + await tmpDir.rename(installPath); } } diff --git a/std.ts b/std.ts new file mode 100644 index 00000000..ff0c8c64 --- /dev/null +++ b/std.ts @@ -0,0 +1,2 @@ +export * from "./std/copyLock.ts"; +export * from "./std/sedLock.ts"; diff --git a/std/copyLock.ts b/std/copyLock.ts new file mode 100644 index 00000000..ad3339f5 --- /dev/null +++ b/std/copyLock.ts @@ -0,0 +1,46 @@ +import { $, expandGlobsAndAbsolutize, Path } from "../utils/mod.ts"; +import { std_fs } from "../deps/common.ts"; + +/** + * Copies the files under the key to the locations in the values. + * + * Supports globs. + */ +export async function copyLock( + wd: Path, + map: Record, + opts?: Omit, +): Promise { + let dirty = false; + await $.co( + Object.entries(map) + .map(async ([file, copies]) => { + const url = wd.resolve(file); + const text = await url.readText(); + + await $.co( + copies.map(async (pathOrGlob) => { + const paths = await expandGlobsAndAbsolutize( + pathOrGlob, + wd.toString(), + opts, + ); + + await $.co(paths.map(async (copy) => { + const copyUrl = $.path(copy); + const copyText = await copyUrl.readText(); + + if (copyText != text) { + copyUrl.writeText(text); + $.logStep(`Updated ${wd.relative(copyUrl)}`); + dirty = true; + } else { + $.logLight(`No change ${wd.relative(copyUrl)}`); + } + })); + }), + ); + }), + ); + return dirty; +} diff --git a/std/sedLock.ts b/std/sedLock.ts new file mode 100644 index 00000000..9219b807 --- /dev/null +++ b/std/sedLock.ts @@ -0,0 +1,111 @@ +import { $, Path, unwrapZodRes } from "../utils/mod.ts"; +import { std_fs, zod } from "../deps/common.ts"; + +export const lockfileValidator = zod.object({ + /** + * A map of paths/globs => regexp strings => replacements. + * + * Rexeps are expected to have two match groups. + * Replacement will be placed between the two groups. + * TODO: use named match groups instead. + */ + lines: zod.record( + zod.string(), + zod.tuple([ + zod.union([zod.string(), zod.instanceof(RegExp)]), + zod.string(), + ]).array(), + ), + ignores: zod.string().array().nullish(), +}); + +export type GrepLockfile = zod.input; + +/** + * Find and replace a set of strings across a directory. + * Useful to keep certain strings consistent across changes. + * + * It will throw an error if not even one hit is found for each pattern. + * + * Avoid globstars over your entire working dir unless you're being careful + * with your ignores. + */ +export async function sedLock( + workingDir: Path, + lockfileIn: GrepLockfile, +): Promise { + const { lines, ignores } = unwrapZodRes( + lockfileValidator.safeParse(lockfileIn), + ); + + let dirty = false; + + await $.co( + Object + .entries(lines) + .map(async ([glob, lookups]) => { + const paths = await Array.fromAsync( + std_fs.expandGlob(glob, { + root: workingDir.toString(), + includeDirs: false, + globstar: true, + exclude: ignores ?? [], + }), + ); + + if (paths.length == 0) { + throw new Error( + `No files found for ${glob}, please check and retry.`, + ); + } + + const matches = Object.fromEntries( + lookups.map(([key]) => [key.toString(), 0]), + ); + + await $.co( + paths.map(async ({ path: pathStr }) => { + const path = $.path(pathStr); + const text = await path.readText(); + const rewrite = [...text.split("\n")]; + + for (const [pattern, replacement] of lookups) { + const regex = typeof pattern == "string" + ? new RegExp(pattern) + : pattern; + + for (let i = 0; i < rewrite.length; i += 1) { + if (regex.test(rewrite[i])) { + matches[pattern.toString()] += 1; + } + + rewrite[i] = rewrite[i].replace( + regex, + `$1${replacement}$2`, + ); + } + } + + const newText = rewrite.join("\n"); + if (text != newText) { + await path.writeText(newText); + $.logStep(`Updated ${workingDir.relative(path)}`); + dirty = true; + } else { + // $.logLight(`No change ${workingDir.relative(path)}`); + } + }), + ); + + for (const [pattern, count] of Object.entries(matches)) { + if (count == 0) { + throw new Error( + `No matches found for ${pattern} in ${glob}, please check and retry.`, + ); + } + } + }), + ); + + return dirty; +} diff --git a/tests/envHooks.ts b/tests/envHooks.ts index be0e1d60..963ea072 100644 --- a/tests/envHooks.ts +++ b/tests/envHooks.ts @@ -53,6 +53,7 @@ type CustomE2eTestCase = Omit & { ePoint: string; stdin: string; }; + const cases: CustomE2eTestCase[] = [ { name: "bash_interactive", diff --git a/tests/envs.ts b/tests/envs.ts index 343c1b7e..7318226a 100644 --- a/tests/envs.ts +++ b/tests/envs.ts @@ -192,6 +192,130 @@ test "$SONG" = "ditto"; and exit 107 test "$HUMM" = "Soul Lady"; or exit 108 `, }, + { + name: "env_inherit_from_envs", + ePoint: "fish", + envs: [], + secureConfig: { + defaultEnv: "e1", + envs: [ + { name: "e1", inherit: "e2" }, + { + name: "e2", + vars: { HEY: "hello" }, + }, + ], + }, + stdin: ` +set fish_trace 1 +test "$GHJK_ENV" = "e1"; or exit 101 +test "$HEY" = "hello"; or exit 102 +`, + }, + { + name: "task_inherit_from_envs", + ePoint: "fish", + envs: [], + secureConfig: { + envs: [{ name: "e1", vars: { HEY: "hello" } }], + tasks: { t1: { inherit: "e1", fn: ($) => $`echo $HEY` } }, + }, + stdin: ` +set fish_trace 1 +test (ghjk x t1) = "hello"; or exit 102 +`, + }, + { + name: "env_inherit_from_tasks", + ePoint: "fish", + envs: [], + secureConfig: { + defaultEnv: "e1", + envs: [{ name: "e1", inherit: "t1" }], + tasks: { t1: { vars: { HEY: "hello" } } }, + }, + stdin: ` +set fish_trace 1 +test "$GHJK_ENV" = "e1"; or exit 101 +test "$HEY" = "hello"; or exit 102 +`, + }, + { + name: "task_inherit_from_task", + ePoint: "fish", + envs: [], + secureConfig: { + tasks: { + t1: { vars: { HEY: "hello" }, fn: ($) => $`echo fake` }, + t2: { + inherit: "t1", + fn: ($) => $`echo $HEY`, + }, + }, + }, + stdin: ` +set fish_trace 1 +test (ghjk x t2) = "hello"; or exit 102 +`, + }, + { + name: "hereditary", + ePoint: "fish", + envs: [ + { name: "e1", vars: { E1: "1" }, installs: [dummy({ output: "e1" })] }, + { + name: "e2", + inherit: "e1", + vars: { E2: "2" }, + }, + { + name: "e3", + inherit: "e2", + vars: { E3: "3" }, + }, + ], + stdin: ` +set fish_trace 1 +ghjk envs cook e3 +. .ghjk/envs/e3/activate.fish +test "$E1" = "1"; or exit 101 +test "$E2" = "2"; or exit 102 +test "$E3" = "3"; or exit 103 +test (dummy) = "e1"; or exit 104 +`, // TODO: test inheritance of more props + }, + { + name: "inheritance_diamond", + ePoint: "fish", + envs: [ + { name: "e1", vars: { E1: "1" }, installs: [dummy({ output: "e1" })] }, + { + name: "e2", + inherit: "e1", + vars: { E2: "2" }, + }, + { + name: "e3", + inherit: "e1", + vars: { E3: "3" }, + }, + { + name: "e4", + inherit: ["e2", "e3"], + vars: { E4: "4" }, + }, + ], + stdin: ` +set fish_trace 1 +ghjk envs cook e4 +. .ghjk/envs/e4/activate.fish +test "$E1" = "1"; or exit 101 +test "$E2" = "2"; or exit 102 +test "$E3" = "3"; or exit 103 +test "$E4" = "4"; or exit 104 +test (dummy) = "e1"; or exit 105 +`, // TODO: test inheritance of more props + }, ]; harness(cases.map((testCase) => ({ @@ -200,7 +324,7 @@ harness(cases.map((testCase) => ({ { secureConf: { ...testCase.secureConfig, - envs: testCase.envs, + envs: [...testCase.envs, ...(testCase.secureConfig?.envs ?? [])], }, }, ), diff --git a/tests/ports.ts b/tests/ports.ts index 9e5f34a7..72842485 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -110,6 +110,11 @@ const cases: CustomE2eTestCase[] = [ enableRuntimes: true, }, }, + { + name: "deno", + installConf: ports.deno_ghrel(), + ePoint: `deno --version`, + }, // 42 megs { name: "earthly", @@ -225,4 +230,5 @@ harness(cases.map((testCase) => ({ }, */ ], name: `ports/${testCase.name}`, + testCase: 10 * 60 * 1000, }))); diff --git a/tests/portsOutdatedTest.ts b/tests/portsOutdated.ts similarity index 55% rename from tests/portsOutdatedTest.ts rename to tests/portsOutdated.ts index ef9eaadf..c7f053c3 100644 --- a/tests/portsOutdatedTest.ts +++ b/tests/portsOutdated.ts @@ -10,16 +10,12 @@ type CustomE2eTestCase = Omit & { secureConf?: FileArgs; }; +// FIXME: const cases: CustomE2eTestCase[] = [ { - name: "ports_outdated", + name: "command", installConf: [ - ports.jq_ghrel(), - ports.protoc(), - ports.ruff(), - ...ports.npmi({ packageName: "node-gyp" }), - ports.earthly(), - ...ports.pipi({ packageName: "poetry" }), + ports.jq_ghrel({ version: "jq-1.7" }), ], ePoint: `ghjk p outdated`, secureConf: { @@ -27,14 +23,10 @@ const cases: CustomE2eTestCase[] = [ }, }, { - name: "ports_outdated_update_all", + name: "update_all", installConf: [ - ports.jq_ghrel(), - ports.protoc(), - ports.ruff(), - ...ports.npmi({ packageName: "node-gyp" }), - ports.earthly(), - ...ports.pipi({ packageName: "poetry" }), + ports.jq_ghrel({ version: "jq-1.7" }), + ports.protoc({ version: "v24.0" }), ], ePoint: `ghjk p outdated --update-all`, secureConf: { @@ -59,19 +51,6 @@ harness(cases.map((testCase) => ({ ...["bash -c", "fish -c", "zsh -c"].map((sh) => ({ cmd: [...`env ${sh}`.split(" "), `"${testCase.ePoint}"`], })), - /* // FIXME: better tests for the `InstallDb` - // installs db means this shouldn't take too long - // as it's the second sync - { - cmd: [ - ..."env".split(" "), - "bash -c 'timeout 1 ghjk envs cook'", - ], - }, */ ], - // building the test docker image might taka a while - // but we don't want some bug spinlocking the ci for - // an hour - timeout_ms: 5 * 60 * 1000, name: `portsOutdated/${testCase.name}`, }))); diff --git a/tests/reloadHooks.ts b/tests/reloadHooks.ts index 71482f9e..8f03679a 100644 --- a/tests/reloadHooks.ts +++ b/tests/reloadHooks.ts @@ -7,19 +7,19 @@ import type { InstallConfigFat } from "../port.ts"; // TODO: test for hook reload when nextfile is touched const posixInteractiveScript = ` -set -eux -[ "$DUMMY_ENV" = "dummy" ] || exit 101 +set -ex +[ "\${DUMMY_ENV:-}" = "dummy" ] || exit 101 dummy # it should be avail in subshells -sh -c '[ "$DUMMY_ENV" = "dummy" ]' || exit 105 +sh -c '[ "\${DUMMY_ENV:-}" = "dummy" ]' || exit 105 sh -c "dummy" pushd ../ # it shouldn't be avail here set +ex [ $(dummy) ] && exit 102 -[ "$DUMMY_ENV" = "dummy" ] && exit 103 +[ "\${DUMMY_ENV:-}" = "dummy" ] && exit 103 set -ex # cd back in @@ -27,12 +27,12 @@ popd # now it should be avail dummy -[ "$DUMMY_ENV" = "dummy" ] || exit 106 +[ "\${DUMMY_ENV:-}" = "dummy" ] || exit 106 -[ "$GHJK_ENV" = "main" ] || exit 107 +[ "\${GHJK_ENV:-}" = "main" ] || exit 107 ghjk e cook test echo "test" > $GHJK_NEXTFILE -[ "$GHJK_ENV" = "test" ] || exit 108 +[ "\${GHJK_ENV:-}" = "test" ] || exit 108 `; const posixNonInteractiveScript = ` @@ -40,11 +40,11 @@ set -eux # test that ghjk_reload is avail because BASH_ENV exposed by the suite ghjk_reload -[ "$DUMMY_ENV" = "dummy" ] || exit 101 +[ "\${DUMMY_ENV:-}" = "dummy" ] || exit 101 dummy # it should be avail in subshells -sh -c '[ "$DUMMY_ENV" = "dummy" ]' || exit 105 +sh -c '[ "\${DUMMY_ENV:-}" = "dummy" ]' || exit 105 sh -c "dummy" pushd ../ @@ -54,35 +54,35 @@ ghjk_reload # it shouldn't be avail now [ $(set +e; dummy) ] && exit 102 -[ "$DUMMY_ENV" = "dummy" ] && exit 103 +[ "\${DUMMY_ENV:-}" = "dummy" ] && exit 103 # cd back in popd # not avail yet [ $(set +e; dummy) ] && exit 104 -[ "$DUMMY_ENV" = "dummy" ] && exit 105 +[ "\${DUMMY_ENV:-}" = "dummy" ] && exit 105 ghjk_reload # now it should be avail dummy -[ "$DUMMY_ENV" = "dummy" ] || exit 106 +[ "\${DUMMY_ENV:-}" = "dummy" ] || exit 106 -[ "$GHJK_ENV" = "main" ] || exit 107 +[ "\${GHJK_ENV}" = "main" ] || exit 107 ghjk e cook test ghjk_reload test -[ "$GHJK_ENV" = "test" ] || exit 110 +[ "\${GHJK_ENV:-}" = "test" ] || exit 110 ghjk_reload -[ "$GHJK_ENV" = "test" ] || exit 111 +[ "\${GHJK_ENV:-}" = "test" ] || exit 111 GHJK_ENV=test ghjk_reload -[ "$GHJK_ENV" = "test" ] || exit 112 +[ "\${GHJK_ENV:-}" = "test" ] || exit 112 `; const fishScript = ` set fish_trace 1 -dummy; or exit 101 +which dummy; or exit 101 test $DUMMY_ENV = "dummy"; or exit 102 # it should be avail in subshells diff --git a/tests/tasks.ts b/tests/tasks.ts index c85f8bf1..5e35475e 100644 --- a/tests/tasks.ts +++ b/tests/tasks.ts @@ -151,7 +151,9 @@ harness(cases.map((testCase) => ({ tsGhjkfileStr: "ghjkTs" in testCase ? testCase.ghjkTs : genTsGhjkFile( { secureConf: { - tasks: testCase.tasks, + tasks: Object.fromEntries( + testCase.tasks.map((def) => [def.name!, def]), + ), enableRuntimes: testCase.enableRuntimesOnMasterPDAL, }, }, diff --git a/tests/test.Dockerfile b/tests/test.Dockerfile index 83bef2fe..3e4e2bab 100644 --- a/tests/test.Dockerfile +++ b/tests/test.Dockerfile @@ -1,4 +1,4 @@ -ARG DENO_VERSION=1.43.1 +ARG DENO_VERSION=1.44.2 FROM denoland/deno:bin-$DENO_VERSION AS deno @@ -47,9 +47,6 @@ ENV GHJK_INSTALL_HOOK_SHELLS=fish,bash,zsh ENV GHJK_INSTALL_DENO_DIR=$DENO_DIR RUN deno run -A /ghjk/install.ts -ARG GITHUB_TOKEN -ENV GITHUB_TOKEN=$GITHUB_TOKEN - # avoid variable expansion in the contents of the # here-document by quoting the tag COPY <<"EOT" /app/ghjk.ts diff --git a/tests/utils.ts b/tests/utils.ts index dc67c40a..f4575424 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -48,6 +48,9 @@ export async function dockerE2eTest(testCase: E2eTestCase) { const env = { ...testEnvs, }; + if (Deno.env.get("GITHUB_TOKEN")) { + env.GITHUB_TOKEN = Deno.env.get("GITHUB_TOKEN")!; + } const devGhjkPath = import.meta.resolve("../"); const configFile = tsGhjkfileStr @@ -193,10 +196,10 @@ export function genTsGhjkFile( 2, ); - const tasks = (secureConf?.tasks ?? []).map( - (def) => { + const tasks = Object.entries(secureConf?.tasks ?? {}).map( + ([name, def]) => { const stringifiedSection = JSON.stringify( - def, + { ...def, name }, (_, val) => typeof val == "string" ? val.replaceAll(/\\/g, "\\\\") : val, 2, diff --git a/utils/mod.ts b/utils/mod.ts index f9659be4..c1482348 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -9,6 +9,7 @@ import { std_path, syncSha256, zod, + zod_val_err, } from "../deps/common.ts"; // class re-exports are tricky. We want al importers // of path to get it from here so we rename in common.ts @@ -24,7 +25,6 @@ import type { PortManifest, } from "../modules/ports/types.ts"; -export type DePromisify = T extends Promise ? Inner : T; export type DeArrayify = T extends Array ? Inner : T; const literalSchema = zod.union([ zod.string(), @@ -180,16 +180,20 @@ export function defaultCommandBuilder() { .printCommand(true); builder.setPrintCommandLogger((cmd) => { // clean up the already colorized print command logs - // TODO: remove when https://github.com/dsherret/dax/pull/203 - // is merged - return logger().debug( - "spawning", - cmd, - ); + return logger().debug("spawning", cmd); }); return builder; } +// type Last = T extends readonly [...any, infer R] ? R +// : DeArrayify; +// +// type Ser[]> = T extends +// readonly [...Promise[], infer R] ? { (...promises: T): R } +// : { +// (...promises: T): DeArrayify; +// }; + export const $ = dax.build$( { commandBuilder: defaultCommandBuilder(), @@ -230,6 +234,27 @@ export const $ = dax.build$( depth: 10, }); }, + co( + values: T, + ): Promise<{ -readonly [P in keyof T]: Awaited }> { + return Promise.all(values); + }, + // coIter( + // items: Iterable, + // fn: (item:T) => PromiseLike, + // opts: { + // limit: "cpu" | number; + // } = { + // limit: "cpu" + // }, + // ): Promise[]> { + // const limit = opts.limit == "cpu" ? AVAIL_CONCURRENCY : opts.limit; + // const promises = [] as PromiseLike[]; + // let freeSlots = limit; + // do { + // } while(true); + // return Promise.all(promises); + // } pathToString(path: Path) { return path.toString(); }, @@ -238,6 +263,7 @@ export const $ = dax.build$( if (await pathRef.exists()) { await pathRef.remove({ recursive: true }); } + return pathRef; }, }, }, @@ -526,12 +552,16 @@ switchMap( // () =>5 ); -export async function expandGlobsAndAbsolutize(path: string, wd: string) { +export async function expandGlobsAndAbsolutize( + path: string, + wd: string, + opts?: Omit, +) { if (std_path.isGlob(path)) { const glob = std_path.isAbsolute(path) ? path : std_path.joinGlobs([wd, path], { extended: true }); - return (await Array.fromAsync(std_fs.expandGlob(glob))) + return (await Array.fromAsync(std_fs.expandGlob(glob, opts))) .map((entry) => std_path.resolve(wd, entry.path)); } return [std_path.resolve(wd, path)]; @@ -541,15 +571,19 @@ export async function expandGlobsAndAbsolutize(path: string, wd: string) { * Unwrap the result object returned by the `safeParse` method * on zod schemas. */ -export function unwrapParseRes( +export function unwrapZodRes( res: zod.SafeParseReturnType, cause: object = {}, errMessage = "error parsing object", ) { if (!res.success) { - throw new Error(errMessage, { + const zodErr = zod_val_err.fromZodError(res.error, { + includePath: true, + maxIssuesInMessage: 3, + }); + throw new Error(`${errMessage}: ${zodErr}`, { cause: { - zodErr: res.error, + issues: res.error.issues, ...cause, }, }); diff --git a/utils/worker.ts b/utils/worker.ts new file mode 100644 index 00000000..d8c1dddf --- /dev/null +++ b/utils/worker.ts @@ -0,0 +1,153 @@ +export function shimDenoNamespace(envVars: Record) { + const { envShim, getAccessedEnvKeys } = denoEnvShim(envVars); + Object.defineProperty(Deno, "env", { + value: envShim, + }); + const { fsShims, getReadFiles, getListedFiles } = denoFsReadShim(); + for (const [name, shim] of fsShims) { + Object.defineProperty(Deno, name, { + value: shim, + }); + } + return { getAccessedEnvKeys, getReadFiles, getListedFiles }; +} + +function denoFsReadShim() { + const readFiles = new Set(); + const listedFiles = new Set(); + + const fsShims = [ + ["watchFs", () => { + throw new Error("Deno.watchFs API is disabled"); + }] as const, + ...[ + // TODO: systemize a way to make sure this + // tracks deno APIs + Deno.readFile, + Deno.readTextFileSync, + Deno.readTextFile, + Deno.readTextFileSync, + Deno.stat, + Deno.statSync, + Deno.lstat, + Deno.lstatSync, + Deno.readLink, + Deno.readLinkSync, + Deno.open, + Deno.openSync, + Deno.readDir, + Deno.readDirSync, + ].map((old) => { + const replace = ( + path: string | URL, + opts: Deno.ReadFileOptions | Deno.OpenOptions | undefined, + ) => { + readFiles.add(typeof path == "string" ? path : path.pathname); + return (old as any)(path, opts); + }; + return [old.name, replace] as const; + }), + ]; + { + const old = Deno.readDir; + const replace: typeof old = ( + path: string | URL, + ) => { + let parent = typeof path === "string" ? path : path.pathname; + readFiles.add(parent); + if (!parent.endsWith("/")) { + parent = path + "/"; + } + const oldIteratorFn = old(path)[Symbol.asyncIterator]; + return { + [Symbol.asyncIterator]: () => { + const iter = oldIteratorFn(); + return { + throw: iter.throw, + return: iter.return, + async next() { + const val = await iter.next(); + if (val.done) return val; + listedFiles.add(parent + val.value.name); + return val; + }, + }; + }, + }; + }; + fsShims.push(["readDir", replace]); + } + { + const old = Deno.readDirSync; + const replace: typeof old = ( + path: string | URL, + ) => { + let parent = typeof path === "string" ? path : path.pathname; + readFiles.add(parent); + if (!parent.endsWith("/")) { + parent = path + "/"; + } + const oldIteratorFn = old(path)[Symbol.iterator]; + return { + [Symbol.iterator]: () => { + const iter = oldIteratorFn(); + return { + throw: iter.throw, + return: iter.return, + next() { + const val = iter.next(); + if (val.done) return val; + listedFiles.add(parent + val.value.name); + return val; + }, + }; + }, + }; + }; + fsShims.push(["readDirSync", replace]); + } + return { + fsShims, + getReadFiles() { + return [...readFiles.keys()]; + }, + getListedFiles() { + return [...listedFiles.keys()]; + }, + }; +} + +function denoEnvShim(vars: Record) { + const map = new Map([...Object.entries(vars)]); + const accessedEnvKeys = new Set(); + const envShim: Deno.Env = { + get(key: string) { + accessedEnvKeys.add(key); + return map.get(key); + }, + set(key: string, val: string) { + accessedEnvKeys.add(key); + map.set(key, val); + }, + has(key: string) { + accessedEnvKeys.add(key); + return map.has(key); + }, + delete(key: string) { + accessedEnvKeys.add(key); + map.delete(key); + }, + toObject() { + for (const key of map.keys()) { + accessedEnvKeys.add(key); + } + return Object.fromEntries([...map.entries()]); + }, + }; + return { + envShim, + getAccessedEnvKeys() { + return [...accessedEnvKeys.keys()]; + }, + }; +} From 4dd9d2385a5aba1fa88c133cf7bb54508af7240b Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 02:27:57 +0200 Subject: [PATCH 17/21] chore: set version 0.2.0 --- .pre-commit-config.yaml | 5 ++++ README.md | 49 +++++++++++++++++++++------------------- examples/kitchen/ghjk.ts | 25 ++++++++++---------- ghjk.ts | 13 ++++++++--- host/mod.ts | 2 +- install.sh | 2 +- install.ts | 21 ++++++++--------- install/mod.ts | 9 ++++++-- mod.ts | 13 ++++++++++- modules/envs/mod.ts | 3 +-- 10 files changed, 86 insertions(+), 56 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 523a9176..13f702e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,11 @@ repos: - commit-msg - repo: local hooks: + - id: lock-sed + name: Sed lock + language: system + entry: bash -c 'deno run --unstable -A main.ts x lock-sed' + pass_filenames: false - id: ghjk-resolve name: Ghjk resolve language: system diff --git a/README.md b/README.md index 06c6a613..53f84081 100644 --- a/README.md +++ b/README.md @@ -20,31 +20,31 @@ ghjk /jk/ is a programmable runtime manager. ```bash # stable -curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install.sh | bash # latest (main) -curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/main/install.sh | GHJK_VERSION=main sh +curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install.sh | GHJK_VERSION=main bash/fish/zsh ``` -In your project, create a configuration file `ghjk.ts`: +In your project, create a configuration file called `ghjk.ts` that look something like: ```ts -// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the 'sophon' object -// exported here. +// 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/main/hack.ts"; +export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts"; import { install, task, -} from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; -import node from "https://raw.githubusercontent.com/metatypedev/ghjk/main/ports/node.ts"; +} 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 into your env +// install programs (ports) into your env install( node({ version: "14.17.0" }), ); -// write simple scripts and execute them through +// write simple scripts and execute them using // `$ ghjk x greet` -task("greet", async ({ $, argv: [name] }) => { +task("greet", async ($, { argv: [name] }) => { await $`echo Hello ${name}!`; }); ``` @@ -57,13 +57,13 @@ ghjk sync ### Environments -Ghjk is primarily configured through constructs called "environments" or "envs" -for short. They serve as recipes for making reproducable (mostly) posix shells. +Ghjk is primarily configured through constructs called "environments" or "envs" for short. +They serve as recipes for making (mostly) reproducable posix shells. ```ts -export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; -import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/main/hack.ts"; -import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/main/ports/mod.ts"; +export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts"; +import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts"; +import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/ports/mod.ts"; // top level `install`s go to the `main` env ghjk.install(ports.protoc()); @@ -79,9 +79,10 @@ ghjk.env("main", { ghjk.env("dev", { // by default, all envs are additively based on `main` - // pass false here to make env indiependent. + // pass false here to make env independent. + // or pass name(s) of another env to base on top of inherit: false, - // envs can specify standard env vars + // envs can specify posix env vars vars: { CARGO_TARGET_DIR: "my_target" }, installs: [ ports.cargobi({ crateName: "cargo-insta" }), @@ -131,16 +132,18 @@ Once you've configured your environments: ### Ports -TBD: this feature is in development. Look in the [kitchen sink](./examples/kitchen/ghjk.ts) for what's currently implemented. +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. +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. +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` @@ -190,5 +193,5 @@ If you intend on using un-trusted third-party scripts in your ghjk, it's recomme ## Development ```bash -$ cat install.sh | GHJK_INSTALLER_URL=$(pwd)/install.ts bash +$ cat install.sh | GHJK_INSTALLER_URL=$(pwd)/install.ts bash/fish/zsh ``` diff --git a/examples/kitchen/ghjk.ts b/examples/kitchen/ghjk.ts index 0562f377..a88897e4 100644 --- a/examples/kitchen/ghjk.ts +++ b/examples/kitchen/ghjk.ts @@ -16,7 +16,6 @@ const ghjk = file({ stdDeps: true, enableRuntimes: true, // tasks aren't attached to envs - // but have their own env tasks: {}, }); @@ -37,12 +36,12 @@ env("main") // ports can use the following installs at build time // very WIP mechanism but this is meant to prevent ports from // pulling whatever dependency they want at build time unless - // explicityl allowed to do so - ports.node({}), + // explicitly allowed to do so + ports.node({ version: "1.2.3" }), ports.rust({ version: "stable" }), // add the std deps including the runtime ports. - // These includes node and python but still, precedence is given - // to our configuration of those ports above + // These includes node and python and this will override + // the node from above since it comes after ordinally ...stdDeps({ enableRuntimes: true }), ); @@ -50,7 +49,7 @@ env("main") install( // ports can declare their own config params ports.rust({ - version: "stable", + version: "1.78.0", profile: "minimal", components: ["rustfmt"], }), @@ -60,12 +59,12 @@ install( ); const ci = env("ci", { - // this inherits from main so it gets protoc and curl + // this inherits from main so it gets jq inherit: "main", // extra installs - installs: [ports.jq_ghrel()], + installs: [ports.protoc(), ports.curl()], // it has extra allowed deps - allowedBuildDeps: [ports.node()], + allowedBuildDeps: [ports.pnpm()], // more env vars vars: { CI: 1, @@ -91,20 +90,22 @@ task("build-app", { desc: "build the app", fn: async ($) => { await $`cargo build -p app`; - // we can access tar here from the ci env - await $`tar xcv ./target/debug/app -o app.tar.gz`; + // we can access zstd here from the ci env + await $`zstd ./target/debug/app -o app.tar.gz`; }, }); env("python") // all envs will inherit from `defaultBaseEnv` - // unles set to false which ensures true isolation + // unless set to false which ensures true isolation .inherit(false) .install( ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), ) .allowedBuildDeps( ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }), + ports.tar(), + ports.zstd(), ); env("dev") diff --git a/ghjk.ts b/ghjk.ts index 09af3c11..29ce169e 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -11,19 +11,20 @@ config({ // these are just for quick testing install(); +const DENO_VERSION = "1.44.2"; + // these are used for developing ghjk install( ports.act(), ports.pipi({ packageName: "pre-commit" })[0], ports.cpy_bs(), - ports.deno_ghrel({ version: "1.44.2" }), + ports.deno_ghrel({ version: DENO_VERSION }), ); task( "lock-sed", async ($) => { - const GHJK_VERSION = "0.2.1"; - const DENO_VERSION = "1.44.2"; + const GHJK_VERSION = "0.2.0"; await sedLock( $.path(import.meta.dirname!), { @@ -38,6 +39,12 @@ task( [/(GHJK_VERSION="\$\{GHJK_VERSION:-v).*(\}")/, GHJK_VERSION], [/(DENO_VERSION="\$\{DENO_VERSION:-v).*(\}")/, DENO_VERSION], ], + "./README.md": [ + [ + /(.*\/metatypedev\/ghjk\/)[^/]*(\/.*)/, + GHJK_VERSION, + ], + ], }, ignores: [ // ignore this file to avoid hits on the regexps diff --git a/host/mod.ts b/host/mod.ts index e09c505f..39d4990e 100644 --- a/host/mod.ts +++ b/host/mod.ts @@ -30,7 +30,7 @@ type HostCtx = { lockedFlagSet: boolean; }; -const GHJK_VERSION = "0.2.1"; +const GHJK_VERSION = "0.2.0"; export async function cli(args: CliArgs) { logger().debug(`ghjk CLI`, GHJK_VERSION); diff --git a/install.sh b/install.sh index 263c7a9b..5869d4a7 100755 --- a/install.sh +++ b/install.sh @@ -2,7 +2,7 @@ set -e -u -GHJK_VERSION="${GHJK_VERSION:-v0.2.1}" +GHJK_VERSION="${GHJK_VERSION:-v0.2.0}" GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.ts}" GHJK_SHARE_DIR="${GHJK_SHARE_DIR:-$HOME/.local/share/ghjk}" DENO_VERSION="${DENO_VERSION:-v1.44.2}" diff --git a/install.ts b/install.ts index 0efef5b1..c986fd5c 100755 --- a/install.ts +++ b/install.ts @@ -4,25 +4,24 @@ import "./setup_logger.ts"; import { defaultInstallArgs, install } from "./install/mod.ts"; -import { detectShell } from "./utils/mod.ts"; if (import.meta.main) { const skipBinInstall = Deno.env.get("GHJK_INSTALL_SKIP_EXE"); const noLockfile = Deno.env.get("GHJK_INSTALL_NO_LOCKFILE"); - let shellsToHook = Deno.env.get("GHJK_INSTALL_HOOK_SHELLS") + const shellsToHook = Deno.env.get("GHJK_INSTALL_HOOK_SHELLS") ?.split(",") ?.map((str) => str.trim()) ?.filter((str) => str.length > 0); - if (!shellsToHook) { - const userShell = await detectShell(); - if (!userShell) { - throw new Error( - "Unable to detect user's shell. Set $GHJK_INSTALL_HOOK_SHELLS to an empty string if no shell hooks are desired.", - ); - } - shellsToHook = [userShell]; - } + // if (!shellsToHook) { + // const userShell = await detectShell(); + // if (!userShell) { + // throw new Error( + // "Unable to detect user's shell. Set $GHJK_INSTALL_HOOK_SHELLS to an empty string if no shell hooks are desired.", + // ); + // } + // shellsToHook = [userShell]; + // } await install({ ...defaultInstallArgs, ghjkShareDir: Deno.env.get("GHJK_SHARE_DIR") ?? diff --git a/install/mod.ts b/install/mod.ts index e0814348..d63eca51 100644 --- a/install/mod.ts +++ b/install/mod.ts @@ -105,7 +105,7 @@ async function filterAddContent( interface InstallArgs { homeDir: string; ghjkShareDir: string; - shellsToHook: string[]; + shellsToHook?: string[]; /** The mark used when adding the hook to the user's shell rcs. * Override to allow multiple hooks in your rc. */ @@ -174,7 +174,7 @@ export async function install( ghjkShareDir, [[/__GHJK_SHARE_DIR__/g, ghjkShareDir.toString()]], ); - for (const shell of args.shellsToHook) { + for (const shell of args.shellsToHook ?? Object.keys(shellConfig)) { const { homeDir } = args; if (!(shell in shellConfig)) { @@ -182,6 +182,11 @@ export async function install( } const rcPath = $.path(homeDir).join(shellConfig[shell]); + // if the shell rc file isn't detected and we're hooking + // the default shell set, just skip it + if (!await rcPath.exists() && !args.shellsToHook) { + continue; + } logger.info("installing hook", { ghjkShareDir, shell, diff --git a/mod.ts b/mod.ts index a19e33c1..891c777a 100644 --- a/mod.ts +++ b/mod.ts @@ -134,10 +134,21 @@ export const file = Object.freeze(function file( inherit: args.defaultBaseEnv && args.defaultBaseEnv != DEFAULT_BASE_ENV_NAME ? args.defaultBaseEnv : false, - installs: args.installs, desc: "the default default environment.", }); + if (args.defaultBaseEnv) { + builder.addEnv(args.defaultBaseEnv, { + name: args.defaultBaseEnv, + inherit: false, + installs: args.installs, + }); + } else { + if (args.installs) { + mainEnv.install(...args.installs); + } + } + // this replaces the allowedBuildDeps contents according to the // args. Written to be called multilple times to allow // replacement. diff --git a/modules/envs/mod.ts b/modules/envs/mod.ts index 31021de1..6258f72b 100644 --- a/modules/envs/mod.ts +++ b/modules/envs/mod.ts @@ -37,14 +37,13 @@ export class EnvsModule extends ModuleBase { processManifest( gcx: GhjkCtx, manifest: ModuleManifest, - bb: Blackboard, + _bb: Blackboard, _lockEnt: EnvsLockEnt | undefined, ) { function unwrapParseCurry(res: zod.SafeParseReturnType) { return unwrapZodRes(res, { id: manifest.id, config: manifest.config, - bb, }, "error parsing module config"); } const config = unwrapParseCurry( From b21f430c74b737c4ba632beb2e094e1bf1c711d3 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 02:43:57 +0200 Subject: [PATCH 18/21] fix: more target sed pattern --- ghjk.ts | 2 +- modules/ports/ghrel.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ghjk.ts b/ghjk.ts index 29ce169e..076a8be7 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -32,7 +32,7 @@ task( "./.github/workflows/*.yml": [ [/(DENO_VERSION: ").*(")/, DENO_VERSION], ], - "**": [ + "./host/mod.ts": [ [/(GHJK_VERSION = ").*(")/, GHJK_VERSION], ], "./install.sh": [ diff --git a/modules/ports/ghrel.ts b/modules/ports/ghrel.ts index 7345f766..a15a6e55 100644 --- a/modules/ports/ghrel.ts +++ b/modules/ports/ghrel.ts @@ -17,7 +17,7 @@ export function readGhVars() { const out: GithubReleasesInstConf = { ghToken, }; - return out; + return ghToken ? out : {}; } export function ghHeaders(conf: Record) { From 929423e46aa411950b8977bee75e3d3ec4d4fd53 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 03:06:14 +0200 Subject: [PATCH 19/21] fix(ci): pre-commit issue --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a6d2a98d..915be01f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,6 @@ on: env: DENO_VERSION: "1.44.2" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHJK_LOG: debug GHJK_LOG_PANIC_LEVEL: error DENO_DIR: .deno-dir @@ -32,6 +31,11 @@ jobs: - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} + # run ghjk once to avoid trigger file changes when + # pre commit runs ghjk. We'll always see changes + # to lock.json since GITHUB_TOKEN is different + # in the CI + - run: deno run --unstable -A main.ts print config - uses: pre-commit/action@v3.0.1 env: SKIP: ghjk-resolve From 685978e9cdcc63487ada3df42006c7eb7b00b38a Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:49:56 +0200 Subject: [PATCH 20/21] fix: 10m timeout --- tests/ports.ts | 2 +- tests/portsOutdated.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ports.ts b/tests/ports.ts index 72842485..0a1016f2 100644 --- a/tests/ports.ts +++ b/tests/ports.ts @@ -230,5 +230,5 @@ harness(cases.map((testCase) => ({ }, */ ], name: `ports/${testCase.name}`, - testCase: 10 * 60 * 1000, + timeout_ms: 10 * 60 * 1000, }))); diff --git a/tests/portsOutdated.ts b/tests/portsOutdated.ts index c7f053c3..e079279f 100644 --- a/tests/portsOutdated.ts +++ b/tests/portsOutdated.ts @@ -53,4 +53,5 @@ harness(cases.map((testCase) => ({ })), ], name: `portsOutdated/${testCase.name}`, + timeout_ms: 10 * 60 * 1000, }))); From b6c84d0aba28d3343afe14bf7cda66ee71e0c8d5 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:46:26 +0200 Subject: [PATCH 21/21] fix: explicit GITHUB_TOKEN --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 915be01f..8e18270f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,7 @@ env: GHJK_LOG: debug GHJK_LOG_PANIC_LEVEL: error DENO_DIR: .deno-dir + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # removing the images after every test is unncessary DOCKER_NO_RMI: 1