Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tracking: clearEnv on CommandBuilder #2

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,37 @@ Deno.test("exporting env should modify real environment when something changed v
}
});

Deno.test("env should be clean slate when clearEnv is set", async () => {
{
const text = await $`printenv`.clearEnv().text();
assertEquals(text, "");
}
Deno.env.set("DAX_TVAR", "123");
try {
const text = await $`deno eval 'console.log("DAX_TVAR: " + Deno.env.get("DAX_TVAR"))'`.clearEnv().text();
assertEquals(text, "DAX_TVAR: undefined");
} finally {
Deno.env.delete("DAX_TVAR");
}
});

Deno.test("clearEnv + exportEnv should not clear out real environment", async () => {
Deno.env.set("DAX_TVAR", "123");
try {
const text =
await $`deno eval 'console.log("VAR: " + Deno.env.get("DAX_TVAR") + " VAR2: " + Deno.env.get("DAX_TVAR2"))'`
.env("DAX_TVAR2", "shake it shake")
.clearEnv()
.exportEnv()
.text();
assertEquals(text, "VAR: undefined VAR2: shake it shake");
assertEquals(Deno.env.get("DAX_TVAR2"), "shake it shake");
} finally {
Deno.env.delete("DAX_TVAR");
Deno.env.delete("DAX_TVAR2");
}
});

Deno.test("setting an empty env var", async () => {
const text = await $`VAR= deno eval 'console.log("VAR: " + Deno.env.get("VAR"))'`.text();
assertEquals(text, "VAR: ");
Expand Down
22 changes: 19 additions & 3 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ interface CommandBuilderState {
env: Record<string, string | undefined>;
commands: Record<string, CommandHandler>;
cwd: string | undefined;
clearEnv: boolean;
exportEnv: boolean;
printCommand: boolean;
printCommandLogger: LoggerTreeBox;
Expand Down Expand Up @@ -147,6 +148,7 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
env: {},
cwd: undefined,
commands: { ...builtInCommands },
clearEnv: false,
exportEnv: false,
printCommand: false,
printCommandLogger: new LoggerTreeBox(
Expand Down Expand Up @@ -176,6 +178,7 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
env: { ...state.env },
cwd: state.cwd,
commands: { ...state.commands },
clearEnv: state.clearEnv,
exportEnv: state.exportEnv,
printCommand: state.printCommand,
printCommandLogger: state.printCommandLogger.createChild(),
Expand Down Expand Up @@ -454,6 +457,18 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
});
}

/**
* Clear environmental variables from parent process.
*
* Doesn't guarantee that only `env` variables are present, as the OS may
* set environmental variables for processes.
*/
clearEnv(value = true): CommandBuilder {
return this.#newWithState((state) => {
state.clearEnv = value;
});
}

/**
* Prints the command text before executing the command.
*
Expand Down Expand Up @@ -742,10 +757,11 @@ export function parseAndSpawnCommand(state: CommandBuilderState) {
stdin: stdin instanceof ReadableStream ? readerFromStreamReader(stdin.getReader()) : stdin,
stdout,
stderr,
env: buildEnv(state.env),
env: buildEnv(state.env, state.clearEnv),
commands: state.commands,
cwd: state.cwd ?? Deno.cwd(),
exportEnv: state.exportEnv,
clearedEnv: state.clearEnv,
signal,
fds,
});
Expand Down Expand Up @@ -1083,8 +1099,8 @@ export class CommandResult {
}
}

function buildEnv(env: Record<string, string | undefined>) {
const result = Deno.env.toObject();
function buildEnv(env: Record<string, string | undefined>, clearEnv: boolean) {
const result = clearEnv ? {} : Deno.env.toObject();
for (const [key, value] of Object.entries(env)) {
if (value == null) {
delete result[key];
Expand Down
38 changes: 37 additions & 1 deletion src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,41 @@ class ShellEnv implements Env {
}
}

/**
* Like {@link RealEnv} but any read actions will access values that were only set through it
* or return undefined.
*/
class RealEnvWriteOnly implements Env {
real = new RealEnv();
shell = new ShellEnv();

setCwd(cwd: string) {
this.real.setCwd(cwd);
this.shell.setCwd(cwd);
}

getCwd() {
return this.shell.getCwd();
}

setEnvVar(key: string, value: string | undefined) {
this.real.setEnvVar(key, value);
this.shell.setEnvVar(key, value);
}

getEnvVar(key: string) {
return this.shell.getEnvVar(key);
}

getEnvVars() {
return this.shell.getEnvVars();
}

clone(): Env {
return cloneEnv(this);
}
}

function initializeEnv(env: Env, opts: ShellEnvOpts) {
env.setCwd(opts.cwd);
for (const [key, value] of Object.entries(opts.env)) {
Expand Down Expand Up @@ -481,12 +516,13 @@ export interface SpawnOpts {
commands: Record<string, CommandHandler>;
cwd: string;
exportEnv: boolean;
clearedEnv: boolean;
signal: KillSignal;
fds: StreamFds | undefined;
}

export async function spawn(list: SequentialList, opts: SpawnOpts) {
const env = opts.exportEnv ? new RealEnv() : new ShellEnv();
const env = opts.exportEnv ? opts.clearedEnv ? new RealEnvWriteOnly() : new RealEnv() : new ShellEnv();
initializeEnv(env, opts);
const context = new Context({
env,
Expand Down