Skip to content

Commit 1c05f97

Browse files
feat(envs): dynamic variables for envs (#95)
* feat(envs): dynamic env * feat: dyn env naive (wip) * feat(envs): dynamic env (wip) * fix: attempt taskless approach * refactor: reducer * fix: dyn env reducer * feat: dyn env * test: dyn env, move impl to reducers * fix: pre-commit, remove unused import * fix: use hack.ts instead * chore: bump to 0.2.1 --------- Co-authored-by: Yohe-Am <[email protected]>
1 parent 100eb76 commit 1c05f97

File tree

17 files changed

+230
-35
lines changed

17 files changed

+230
-35
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,13 @@
22
play.*
33
examples/**/.ghjk
44
.dev
5+
6+
deps/https
7+
node_analysis_*
8+
v8_code_cache_*
9+
dep_analysis_*
10+
gen
11+
npm
12+
deno.land
13+
jsr.io
14+
esm.sh

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ ghjk was designed to be an intermediate alternative between [Earthly](https://gi
2828

2929
```bash
3030
# stable
31-
curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install.sh | bash
31+
curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install.sh | bash
3232
# latest (main)
33-
curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install.sh | GHJK_VERSION=main bash/fish/zsh
33+
curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install.sh | GHJK_VERSION=main bash/fish/zsh
3434
```
3535

3636
In your project, create a configuration file called `ghjk.ts` that look something like:
@@ -39,11 +39,11 @@ In your project, create a configuration file called `ghjk.ts` that look somethin
3939
// NOTE: All the calls in your `ghjk.ts` file are ultimately modifying the 'sophon' proxy
4040
// object exported here.
4141
// WARN: always import `hack.ts` file first
42-
export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts";
42+
export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts";
4343
import {
4444
install, task,
45-
} from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts";
46-
import node from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/ports/node.ts";
45+
} from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts";
46+
import node from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/node.ts";
4747

4848
// install programs (ports) into your env
4949
install(
@@ -69,9 +69,9 @@ Ghjk is primarily configured through constructs called "environments" or "envs"
6969
They serve as recipes for making (mostly) reproducable posix shells.
7070

7171
```ts
72-
export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts";
73-
import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/hack.ts";
74-
import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/ports/mod.ts";
72+
export { sophon } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts";
73+
import * as ghjk from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/hack.ts";
74+
import * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mod.ts";
7575

7676
// top level `install`s go to the `main` env
7777
ghjk.install(ports.protoc());

deno.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"tasks": {
33
"test": "deno test --parallel --unstable-worker-options --unstable-kv -A tests/*",
4+
"self": "deno run -A --unstable-kv --unstable-worker-options main.ts ",
45
"cache": "deno cache deps/*",
56
"check": "deno run -A ./scripts/check.ts",
67
"dev": "deno run -A ./scripts/dev.ts"

deno.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/env_vars/ghjk.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { file } from "../../hack.ts";
2+
3+
const ghjk = file({
4+
defaultEnv: "empty",
5+
envs: [{ name: "empty", inherit: false }],
6+
defaultBaseEnv: "empty",
7+
allowedBuildDeps: [],
8+
installs: [],
9+
stdDeps: true,
10+
enableRuntimes: true,
11+
tasks: {},
12+
});
13+
14+
export const sophon = ghjk.sophon;
15+
const { env, task } = ghjk;
16+
17+
env("main")
18+
.var("A", "A#STATIC")
19+
.var("C", ($) => $`echo C [$A, $B]`.text())
20+
.var("B", () => "B#DYNAMIC")
21+
.onEnter(task(($) => $`echo enter $A, $B, $C`));

files/mod.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ import * as std_modules from "../modules/std.ts";
3636
import type { ExecTaskArgs } from "../modules/tasks/deno.ts";
3737
import { TaskDefHashed, TasksModuleConfig } from "../modules/tasks/types.ts";
3838
// envs
39-
import type {
40-
EnvRecipe,
41-
EnvsModuleConfig,
42-
Provision,
43-
WellKnownProvision,
39+
import {
40+
type EnvRecipe,
41+
type EnvsModuleConfig,
42+
type Provision,
43+
type WellKnownProvision,
4444
} from "../modules/envs/types.ts";
45+
import envsValidators from "../modules/envs/types.ts";
4546
import modulesValidators from "../modules/types.ts";
4647

4748
const validators = {
@@ -100,7 +101,7 @@ export type TaskDefArgs = {
100101
desc?: string;
101102
dependsOn?: string | string[];
102103
workingDir?: string | Path;
103-
vars?: Record<string, string | number>;
104+
vars?: Record<string, string | number>; // TODO: add DynEnvValue?
104105
allowedBuildDeps?: (InstallConfigFat | AllowedPortDep)[];
105106
installs?: InstallConfigFat | InstallConfigFat[];
106107
inherit?: EnvParent;
@@ -305,7 +306,7 @@ export class Ghjkfile {
305306
workingDir,
306307
`<task:${task.name ?? key}>`,
307308
);
308-
await task.fn(custom$, {
309+
return await task.fn(custom$, {
309310
argv,
310311
env: Object.freeze(envVars),
311312
$: custom$,
@@ -653,6 +654,15 @@ export class Ghjkfile {
653654
const prov: WellKnownProvision = { ty: "posix.envVar", key, val };
654655
return prov;
655656
}),
657+
...Object.entries(final.dynVars).map((
658+
[key, val],
659+
) => {
660+
const prov = { ty: "posix.envVarDyn", key, taskKey: val };
661+
return unwrapZodRes(
662+
envsValidators.envVarDynProvision.safeParse(prov),
663+
prov,
664+
);
665+
}),
656666
// env hooks
657667
...hooks,
658668
],
@@ -886,6 +896,7 @@ type EnvFinalizer = () => {
886896
installSetId: string;
887897
inherit: string | string[] | boolean;
888898
vars: Record<string, string>;
899+
dynVars: Record<string, string>;
889900
desc?: string;
890901
onEnterHookTasks: string[];
891902
onExitHookTasks: string[];
@@ -894,6 +905,12 @@ type EnvFinalizer = () => {
894905
export type EnvDefArgsPartial =
895906
& { name?: string }
896907
& Omit<EnvDefArgs, "name">;
908+
909+
export type DynEnvValue =
910+
| (() => string | number)
911+
| (($_: typeof $) => string | number)
912+
| (($_: typeof $) => Promise<string | number>);
913+
897914
//
898915
// /**
899916
// * A version of {@link EnvDefArgs} that has all container
@@ -938,6 +955,7 @@ export class EnvBuilder {
938955
#file: Ghjkfile;
939956
#inherit: string | string[] | boolean = true;
940957
#vars: Record<string, string | number> = {};
958+
#dynVars: Record<string, string> = {};
941959
#desc?: string;
942960
#onEnterHookTasks: string[] = [];
943961
#onExitHookTasks: string[] = [];
@@ -958,6 +976,7 @@ export class EnvBuilder {
958976
vars: Object.fromEntries(
959977
Object.entries(this.#vars).map(([key, val]) => [key, val.toString()]),
960978
),
979+
dynVars: this.#dynVars,
961980
desc: this.#desc,
962981
onExitHookTasks: this.#onExitHookTasks,
963982
onEnterHookTasks: this.#onEnterHookTasks,
@@ -991,18 +1010,45 @@ export class EnvBuilder {
9911010
/**
9921011
* Add an environment variable.
9931012
*/
994-
var(key: string, val: string) {
1013+
var(key: string, val: string | DynEnvValue) {
9951014
this.vars({ [key]: val });
9961015
return this;
9971016
}
9981017

9991018
/**
10001019
* Add multiple environment variable.
10011020
*/
1002-
vars(envVars: Record<string, string | number>) {
1021+
vars(envVars: Record<string, string | number | DynEnvValue>) {
1022+
const vars = {}, dynVars = {};
1023+
for (const [k, v] of Object.entries(envVars)) {
1024+
switch (typeof v) {
1025+
case "string":
1026+
case "number":
1027+
Object.assign(vars, { [k]: v });
1028+
break;
1029+
case "function": {
1030+
const taskKey = this.#file.addTask({
1031+
ty: "denoFile@v1",
1032+
fn: v,
1033+
nonce: k,
1034+
});
1035+
Object.assign(dynVars, { [k]: taskKey });
1036+
break;
1037+
}
1038+
default:
1039+
throw new Error(
1040+
`environment value of type "${typeof v}" is not supported`,
1041+
);
1042+
}
1043+
}
1044+
10031045
Object.assign(
10041046
this.#vars,
1005-
unwrapZodRes(validators.envVars.safeParse(envVars), { envVars }),
1047+
unwrapZodRes(validators.envVars.safeParse(vars), { envVars: vars }),
1048+
);
1049+
Object.assign(
1050+
this.#dynVars,
1051+
dynVars,
10061052
);
10071053
return this;
10081054
}

ghjk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ install(
2424
task(
2525
"lock-sed",
2626
async ($) => {
27-
const GHJK_VERSION = "0.2.0";
27+
const GHJK_VERSION = "0.2.1";
2828
await sedLock(
2929
$.path(import.meta.dirname!),
3030
{
@@ -41,7 +41,7 @@ task(
4141
],
4242
"./README.md": [
4343
[
44-
/(.*\/metatypedev\/ghjk\/)[^/]*(\/.*)/,
44+
/(.*\/metatypedev\/ghjk\/v)[^/]*(\/.*)/,
4545
GHJK_VERSION,
4646
],
4747
],

host/mod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type HostCtx = {
3030
lockedFlagSet: boolean;
3131
};
3232

33-
const GHJK_VERSION = "0.2.0";
33+
const GHJK_VERSION = "0.2.1";
3434

3535
export async function cli(args: CliArgs) {
3636
logger().debug(`ghjk CLI`, GHJK_VERSION);

install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e -u
44

5-
GHJK_VERSION="${GHJK_VERSION:-v0.2.0}"
5+
GHJK_VERSION="${GHJK_VERSION:-v0.2.1}"
66
GHJK_INSTALLER_URL="${GHJK_INSTALLER_URL:-https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.ts}"
77
GHJK_SHARE_DIR="${GHJK_SHARE_DIR:-$HOME/.local/share/ghjk}"
88
DENO_VERSION="${DENO_VERSION:-v1.44.2}"

modules/envs/posix.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export async function cookPosixEnv(
6262
case "posix.headerFile":
6363
includePaths.push(wellKnownProv.absolutePath);
6464
break;
65+
// case "posix.envVarDyn":
6566
case "posix.envVar":
6667
if (vars[wellKnownProv.key]) {
6768
throw new Error(
@@ -84,7 +85,7 @@ export async function cookPosixEnv(
8485
break;
8586
default:
8687
throw Error(
87-
`unsupported provision type: ${(wellKnownProv as any).provision}`,
88+
`unsupported provision type: ${(wellKnownProv as any).ty}`,
8889
);
8990
}
9091
}));

modules/envs/reducer.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { unwrapZodRes } from "../../port.ts";
2+
import { execTask } from "../tasks/exec.ts";
3+
import { getTasksCtx } from "../tasks/inter.ts";
24
import type { GhjkCtx } from "../types.ts";
35
import type {
46
EnvRecipeX,
@@ -7,7 +9,7 @@ import type {
79
WellKnownEnvRecipeX,
810
WellKnownProvision,
911
} from "./types.ts";
10-
import { wellKnownProvisionTypes } from "./types.ts";
12+
import { envVarDynTy, wellKnownProvisionTypes } from "./types.ts";
1113
import validators from "./types.ts";
1214

1315
export type ProvisionReducerStore = Map<
@@ -31,6 +33,10 @@ export function getProvisionReducerStore(
3133
store = new Map();
3234
gcx.blackboard.set(id, store);
3335
}
36+
store?.set(
37+
envVarDynTy,
38+
installDynEnvReducer(gcx) as ProvisionReducer<Provision, Provision>,
39+
);
3440
return store;
3541
}
3642

@@ -85,3 +91,38 @@ export async function reduceStrangeProvisions(
8591
};
8692
return out;
8793
}
94+
95+
export function installDynEnvReducer(gcx: GhjkCtx) {
96+
return async (provisions: Provision[]) => {
97+
const output = [];
98+
const badProvisions = [];
99+
const taskCtx = getTasksCtx(gcx);
100+
101+
for (const provision of provisions) {
102+
const ty = "posix.envVar";
103+
const key = provision.taskKey as string;
104+
105+
const taskGraph = taskCtx.taskGraph;
106+
const taskConf = taskCtx.config;
107+
108+
const targetKey = Object.entries(taskConf.tasks)
109+
.filter(([_, task]) => task.key == key)
110+
.shift()?.[0];
111+
112+
if (targetKey) {
113+
// console.log("key", key, " maps to target ", targetKey);
114+
const results = await execTask(gcx, taskConf, taskGraph, targetKey, []);
115+
output.push({ ...provision, ty, val: results[key] as any ?? "" });
116+
} else {
117+
badProvisions.push(provision);
118+
}
119+
}
120+
121+
if (badProvisions.length >= 1) {
122+
throw new Error("cannot deduce task from keys", {
123+
cause: { badProvisions },
124+
});
125+
}
126+
return output;
127+
};
128+
}

modules/envs/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const installProvisionTypes = [
2121
installProvisionTy,
2222
] as const;
2323

24+
export const envVarDynTy = "posix.envVarDyn";
25+
2426
// we separate the posix file types in a separate
2527
// array in the interest of type inference
2628
export const wellKnownProvisionTypes = [
@@ -78,9 +80,16 @@ const envsModuleConfig = zod.object({
7880
message: `no env found under the provided "defaultEnv"`,
7981
});
8082

83+
const envVarDynProvision = zod.object({
84+
ty: zod.literal(envVarDynTy),
85+
key: moduleValidators.envVarName,
86+
taskKey: zod.string(),
87+
});
88+
8189
const validators = {
8290
provision,
8391
wellKnownProvision,
92+
envVarDynProvision,
8493
envRecipe,
8594
envsModuleConfig,
8695
wellKnownEnvRecipe,

modules/ports/mod.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ export class PortsModule extends ModuleBase<PortsCtx, PortsLockEnt> {
108108
installSetProvisionTy,
109109
installSetReducer(gcx) as ProvisionReducer<Provision, Provision>,
110110
);
111-
112111
return pcx;
113112
}
114113

0 commit comments

Comments
 (0)