Skip to content

Commit

Permalink
refactor(hooks): explicit bash command
Browse files Browse the repository at this point in the history
  • Loading branch information
Yohe-Am committed Dec 4, 2023
1 parent 1c818e0 commit 9dc062c
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 137 deletions.
20 changes: 14 additions & 6 deletions host/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import { cliffy_cmd } from "../deps/cli.ts";
import logger from "../utils/logger.ts";
// import { $ } from "../utils/mod.ts";

import { isColorfulTty } from "../utils/mod.ts";
import { envDirFromConfig, findConfig, isColorfulTty } from "../utils/mod.ts";
import validators from "./types.ts";
import * as std_modules from "../modules/std.ts";
import * as deno from "./deno.ts";

export async function main() {
const configPath = Deno.args[0]; // FIXME: might be better to get this from env var
const configPath = Deno.env.get("GHJK_CONFIG") ??
await findConfig(Deno.cwd());
if (!configPath) {
logger().error("ghjk did not find any `ghjk.ts` config.");
Deno.exit(2);
}
const envDir = envDirFromConfig(configPath);

logger().debug("config", configPath);
logger().debug({ configPath });
logger().debug({ envDir });

let serializedJson;
switch (std_path.extname(configPath)) {
Expand All @@ -36,9 +43,10 @@ export async function main() {
}
const serializedConfig = validators.serializedConfig.parse(serializedJson);

const ctx = { configPath, envDir };
let cmd: cliffy_cmd.Command<any, any, any, any> = new cliffy_cmd.Command()
.name("ghjk")
.version("0.1.0") // FIXME: get better version
.version("0.1.0") // FIXME: better way to resolve version
.description("Programmable runtime manager.")
.action(function () {
this.showHelp();
Expand All @@ -59,12 +67,12 @@ export async function main() {
if (!mod) {
throw new Error(`unrecognized module specified by ghjk.ts: ${man.id}`);
}
const instance = mod.ctor(man);
const instance = mod.ctor(ctx, man);
cmd = cmd.command(man.id, instance.command());
}
cmd
.command("completions", new cliffy_cmd.CompletionsCommand())
.parse(Deno.args.slice(1));
.parse(Deno.args);
// const serializedConfig = validators.serializedConfig.parse(
// serializedJson,
// );
Expand Down
8 changes: 7 additions & 1 deletion install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@

import { install } from "./install/mod.ts";

await install();
if (import.meta.main) {
await install();
} else {
throw new Error(
"unexpected ctx: if you want to access the ghjk installer, import `install` from ./install/mod.ts",
);
}
4 changes: 1 addition & 3 deletions install/hooks/bash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,16 @@ init_ghjk() {
# FIXME: -ot not valid in POSIX
# shellcheck disable=SC3000-SC4000
if [ "$envDir/loader.sh" -ot "$cur_dir/ghjk.ts" ]; then
printf "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}"
printf "${ansi_yel}[ghjk] Detected changes, please sync...${ansi_nc}\n"
fi
else
printf "${ansi_red}[ghjk] Uninstalled runtime found, please sync...${ansi_nc}\n"
printf "$envDir\n"
fi
alias ghjk="deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts"
return
fi
cur_dir="$(dirname "$cur_dir")"
done
alias ghjk="printf '${ansi_red}No ghjk.ts config found.${ansi_nc}\n'"
}

# onlt load bash-prexec if we detect bash
Expand Down
2 changes: 0 additions & 2 deletions install/hooks/fish.fish
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ function init_ghjk
echo $envDir
set_color normal
end
alias ghjk "deno run --unstable-worker-options -A $HOME/.local/share/ghjk/hooks/entrypoint.ts $cur_dir/ghjk.ts"
return
end
set cur_dir (dirname $cur_dir)
end
alias ghjk "echo 'No ghjk.ts config found.'"
end

# try to detect ghjk.ts on each change of PWD
Expand Down
70 changes: 40 additions & 30 deletions install/mod.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,30 @@
import { std_path } from "../deps/cli.ts";
import { dirs, importRaw } from "../utils/cli.ts";
import { spawnOutput } from "../utils/mod.ts";

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}`);
}
//! this installs the different shell ghjk hooks in ~/.local/share/ghjk
//! and a `ghjk` bin at ~/.local/share/bin

// null means it should be removed (for cleaning up old versions)
const vfs = {
// the script executed when users use the ghjk command
"hooks/entrypoint.ts": `
import { main } from "${import.meta.resolve("../host/mod.ts")}";
// TODO: support for different environments to use different versions of ghjk

await main();
`,
import { std_fs, std_path } from "../deps/cli.ts";
import { dirs, importRaw } from "../utils/mod.ts";
import { spawnOutput } from "../utils/mod.ts";

// null means it should be removed (for cleaning up old versions)
const hookVfs = {
"hooks/bash-preexec.sh": await importRaw(
"https://raw.githubusercontent.com/rcaloras/bash-preexec/0.5.0/bash-preexec.sh",
),

"hooks/.zshenv": (
await importRaw(import.meta.resolve("./hooks/zsh.zsh"))
)
.replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV),
),

// the hook run before every prompt draw in bash
"hooks/hook.sh": (
await importRaw(import.meta.resolve("./hooks/bash.sh"))
)
.replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV),
),

"hooks/hook.fish": (
await importRaw(import.meta.resolve("./hooks/fish.fish"))
)
.replaceAll("__var_LD_LIBRARY_ENV__", LD_LIBRARY_ENV),
),
};

async function detectShell(): Promise<string> {
Expand All @@ -60,11 +44,10 @@ async function detectShell(): Promise<string> {
}
return std_path.basename(path, ".exe").toLowerCase().trim();
}

async function unpackVFS(baseDir: string): Promise<void> {
await Deno.mkdir(baseDir, { recursive: true });

for (const [subpath, content] of Object.entries(vfs)) {
for (const [subpath, content] of Object.entries(hookVfs)) {
const path = std_path.resolve(baseDir, subpath);
if (content === null) {
await Deno.remove(path);
Expand Down Expand Up @@ -106,6 +89,9 @@ async function filterAddFile(
}

export async function install() {
if (Deno.build.os == "windows") {
throw new Error("windows is not yet supported :/");
}
const { homeDir, shareDir } = dirs();
await unpackVFS(shareDir);
const shell = await detectShell();
Expand All @@ -130,4 +116,28 @@ export async function install() {
} else {
throw new Error(`unsupported shell: ${shell}`);
}
const skipBinInstall = Deno.env.get("GHJK_SKIP_BIN_INSTALL");
if (!skipBinInstall && skipBinInstall != "0" && skipBinInstall != "false") {
switch (Deno.build.os) {
case "linux":
case "freebsd":
case "solaris":
case "illumos":
case "darwin": {
// TODO: respect xdg dirs
const binDir = Deno.env.get("GHJK_BIN_INSTALL_PATH") ??
std_path.resolve(homeDir, ".local", "bin");
await std_fs.ensureDir(binDir);
await Deno.writeTextFile(
std_path.resolve(binDir, `ghjk`),
`#!/bin/sh
deno run --unstable-worker-options -A ${import.meta.resolve("../main.ts")} $*`,
{ mode: 0o700 },
);
break;
}
default:
throw new Error(`${Deno.build.os} is not yet supported`);
}
}
}
11 changes: 11 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /usr/bin/env -S deno run --unstable-worker-options -A

import { main } from "./host/mod.ts";

if (import.meta.main) {
await main();
} else {
throw new Error(
"unexpected ctx: if you want to run the ghjk cli, import `main` from ./host/mod.ts",
);
}
8 changes: 6 additions & 2 deletions modules/ports/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import validators, {
type PortsModuleConfig,
type PortsModuleConfigBase,
} from "./types.ts";
import { type GhjkCtx } from "../types.ts";
import logger from "../../utils/logger.ts";
import { ModuleBase } from "../mod.ts";
import { sync } from "./sync.ts";

export class PortsModule extends ModuleBase {
constructor(public config: PortsModuleConfig) {
constructor(
public ctx: GhjkCtx,
public config: PortsModuleConfig,
) {
super();
}
command() {
Expand All @@ -29,7 +33,7 @@ export class PortsModule extends ModuleBase {
.command(
"sync",
new cliffy_cmd.Command().description("Syncs the environment.")
.action(() => sync(this.config)),
.action(() => sync(this.ctx.envDir, this.config)),
)
.command(
"list",
Expand Down
36 changes: 2 additions & 34 deletions modules/ports/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,9 @@ import validators, {
type PortsModuleConfig,
} from "./types.ts";
import { DenoWorkerPort } from "./worker.ts";
import { AVAIL_CONCURRENCY, dirs } from "../../utils/cli.ts";
import { AmbientAccessPort } from "./ambient.ts";
import { AsdfPort } from "./asdf.ts";
import { getInstallId } from "../../utils/mod.ts";

async function findConfig(path: string): Promise<string | null> {
let current = path;
while (current !== "/") {
const location = `${path}/ghjk.ts`;
if (await std_fs.exists(location)) {
return location;
}
current = std_path.dirname(current);
}
return null;
}

function envDirFromConfig(config: string): string {
const { shareDir } = dirs();
return std_path.resolve(
shareDir,
"envs",
std_path.dirname(config).replaceAll("/", "."),
);
}
import { AVAIL_CONCURRENCY, getInstallId } from "../../utils/mod.ts";

async function writeLoader(
envDir: string,
Expand Down Expand Up @@ -82,17 +60,7 @@ set --global --prepend ${k} ${v};
);
}

export async function sync(cx: PortsModuleConfig) {
const config = await findConfig(Deno.cwd());
if (!config) {
logger().error("ghjk did not find any `ghjk.ts` config.");
return;
}
logger().debug("syncing");

const envDir = envDirFromConfig(config);
logger().debug({ envDir });

export async function sync(envDir: string, cx: PortsModuleConfig) {
const installs = buildInstallGraph(cx);
const artifacts = new Map<string, InstallArtifacts>();
const pendingInstalls = [...installs.indie];
Expand Down
8 changes: 5 additions & 3 deletions modules/std.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { PortsModule } from "./ports/mod.ts";
import portsValidators from "./ports/types.ts";
import { type ModuleManifest } from "./types.ts";
import { type GhjkCtx, type ModuleManifest } from "./types.ts";

export const ports = "ports";

export const tasks = "tasks";

export const map = {
[ports as string]: {
ctor: (manifest: ModuleManifest) =>
ctor: (ctx: GhjkCtx, manifest: ModuleManifest) =>
new PortsModule(
ctx,
portsValidators.portsModuleConfig.parse(manifest.config),
),
},
[tasks as string]: {
// TODO: impl tasks module
ctor: (manifest: ModuleManifest) =>
ctor: (ctx: GhjkCtx, manifest: ModuleManifest) =>
new PortsModule(
ctx,
portsValidators.portsModuleConfig.parse(manifest.config),
),
},
Expand Down
4 changes: 4 additions & 0 deletions modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const moduleManifest = zod.object({

export type ModuleId = zod.infer<typeof moduleId>;
export type ModuleManifest = zod.infer<typeof moduleManifest>;
export type GhjkCtx = {
configPath: string;
envDir: string;
};

export default {
moduleManifest,
Expand Down
19 changes: 11 additions & 8 deletions tests/test.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ ARG CURL_V=7.88.1-10+deb12u4
ARG XZ_V=5.4.1-0.2
ARG UNZIP_V=6.0-28

RUN echo $FISH_V $GIT_V
RUN set -eux; \
export DEBIAN_FRONTEND=noninteractive; \
apt update; \
Expand All @@ -32,27 +31,31 @@ COPY deno.lock ./
COPY deps/* ./deps/
RUN deno cache deps/*
COPY . ./
RUN ln -s ./main.ts /bin/ghjk

WORKDIR /app

ENV GHJK_BIN_INSTALL_PATH=/usr/bin
# explicitly set the shell var as detection fails otherwise
# because ps program is not present in this image
RUN SHELL=/bin/bash deno run -A /ghjk/install.ts
RUN SHELL=/bin/fish deno run -A /ghjk/install.ts
RUN SHELL=/bin/zsh deno run -A /ghjk/install.ts

# activate ghjk non-interactive shells execs
ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh
ENV ZDOTDIR=/root/.local/share/ghjk/hooks/

RUN cat > ghjk.ts <<EOT
#{{CMD_ADD_CONFIG}}
EOT

RUN <<EOT
CLICOLOR_FORCE=1 ghjk config
ghjk ports sync
EOT

# activate ghjk non-interactive shells execs
ENV BASH_ENV=/root/.local/share/ghjk/hooks/hook.sh
ENV ZDOTDIR=/root/.local/share/ghjk/hooks/

# BASH_ENV behavior is only avail in bash, not sh
SHELL [ "/bin/bash", "-c"]

RUN CLICOLOR_FORCE=1 ghjk config
RUN ghjk ports sync

CMD ['false']
Loading

0 comments on commit 9dc062c

Please sign in to comment.