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

WIP: Added support for inline scripting #15

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
1 change: 1 addition & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export { YAMLError } from "https://deno.land/std@0.42.0/encoding/yaml/error.ts";
export { red } from "https://deno.land/std@0.50.0/fmt/colors.ts";
export { extname, resolve } from "https://deno.land/std@0.61.0/path/mod.ts";

import "https://deno.land/x/dotenv/load.ts";
// @deno-types="https://unpkg.com/cac@6.5.9/mod.d.ts"
export { cac } from "https://unpkg.com/cac@6.5.9/mod.js";
17 changes: 14 additions & 3 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
type DenoOptionValue = unknown;
type DotEnvConfigValue = string;

type DenoOptionsEntries = {
[key: string]: DenoOptionValue;
Expand All @@ -8,9 +9,13 @@ type WorkspaceOptions = {
deno_options?: DenoOptionsEntries;
};

type WorkspaceScript = {
file: string;
} & WorkspaceOptions;
// There must be either a file or a command provided.
type WorkspaceScript =
& (
| { file: string; cmd?: undefined }
| { cmd: string; file?: undefined }
)
& WorkspaceOptions;

type WorkspaceGlobal = WorkspaceOptions;

Expand All @@ -21,11 +26,17 @@ type DenoWorkspace = {
globals?: WorkspaceGlobal;
};

// This type is exported from dotenv, which I'm assuming you will be using for this port.
type DotenvConfig = {
[key: string]: DotEnvConfigValue;
};

export {
DenoWorkspace,
WorkspaceGlobal,
WorkspaceScript,
WorkspaceOptions,
DenoOptionsEntries,
DenoOptionValue,
DotenvConfig,
};
26 changes: 25 additions & 1 deletion src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CURRENT_VERSION, GITHUB_REPO_NAME } from "./const.ts";

import * as consolex from "./utils/consolex.ts";
import { ScriptNotFoundError } from "./utils/DenoXErrors.ts";
import replaceEnvVars from "./utils/replaceEnvVars.ts";

import { upgradeVersionMessage } from "./lib/upgrade_version.ts";

Expand Down Expand Up @@ -43,7 +44,30 @@ async function _runScript(
throw new ScriptNotFoundError(scriptName);
}

return await _runDenoFile(workspaceScript, workspaceGlobal, args);
return workspaceScript.file
? await _runDenoFile(workspaceScript, workspaceGlobal, args)
: await _runInlineScript(workspaceScript, workspaceGlobal, args);
}

async function _runInlineScript(
workspaceScript: WorkspaceScript,
workspaceGlobal: WorkspaceOptions,
args: string[],
): Promise<{ code: number }> {
const cmd: any[] = replaceEnvVars(workspaceScript?.cmd, Deno.env.toObject())
.split(" ")
.concat(args) || [];
const denoOptions = await _getDenoOptions(workspaceScript, workspaceGlobal);

cmd.concat(denoOptions);

const process = Deno.run({
cmd,
});

const { code } = await process.status();

return { code };
}

async function _runDenoFile(
Expand Down
28 changes: 28 additions & 0 deletions src/utils/__tests__/replaceEnvVars.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { assertEquals, assertThrows } from "../../../dev_deps.ts";
import replaceEnvVars from "../replaceEnvVars.ts";

const CMD = "deno run ${SOME_ENV_VAR_1} ${SOME_ENV_VAR_2} mod.js";
const EMPTY_ENV = {};
const ENV = {
SOME_ENV_VAR_1: "VALUE_1",
SOME_ENV_VAR_2: "VALUE_2",
};
const REPLACED_CMD = "deno run VALUE_1 VALUE_2 mod.js";

Deno.test("should throw no cmd error", async () => {
assertThrows((): void => {
replaceEnvVars("", EMPTY_ENV);
}, ReferenceError);
});

Deno.test("should return the cmd that entered as is", async () => {
const cmd = replaceEnvVars(CMD, EMPTY_ENV);

assertEquals(cmd, CMD);
});

Deno.test("should return the replaced cmd", async () => {
const cmd = replaceEnvVars(CMD, ENV);

assertEquals(cmd, REPLACED_CMD);
});
28 changes: 28 additions & 0 deletions src/utils/replaceEnvVars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// TODO - Update this with the dotenv interface.
import { DotenvConfig } from "../interfaces.ts";

/**
* This is added here in anticipation of https://github.com/BentoumiTech/denox/issues/12 being completed.
* There are many time we need to inject env variables into a script.
* These comments are merely to give you an insight to my thinking and would be removed after a successful review.
*/

function replaceEnvVars(cmd: string | undefined, env: DotenvConfig): string {
const envKeys: string[] = Object.keys(env);

if (!cmd) {
throw new ReferenceError("A command is required by this function.");
}

// I'm not sure if you have a loop preference? This is the method I have become accustom to in terms of readability and performance.
envKeys.forEach((envKey: string) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The forEach looks good for me

// This is a little ugly but I like explicit variable declaration for readability.
const envValue: string = env[envKey];
// String are immutable so it doesn't matter if we directly work on the cmd string.
cmd = cmd?.replace(`\${${envKey}}`, envValue);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about changing the format from ${ENV_NAME} to $ENV_NAME?

I prefer the second as it's the way POSIX commands utilize environment variables, it's the same way you would write in a bash terminal for example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am completely fine with changing that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, in POSIX shells ${ENV_NAME} == $ENV_NAME.

While $ENV_NAME is commonly used, ${ENV_NAME} is less ambiguous when embedded within other text (eg, "${ENV_NAME}S" vs "$ENV_NAMES"). Given the possible ambiguities, you'll want to at least allow the ${ENV_NAME} format.

});

return cmd;
}

export default replaceEnvVars;
38 changes: 38 additions & 0 deletions test/e2e/scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,41 @@ Deno.test("execute existing script when multiple are specified", async () => {
},
);
});

Deno.test("execute existing inline script when multiple are specified with no environment variables", async () => {
await testDenoXRun(
"inlineNoEnv",
"test/fixture/multiple_scripts",
async ({ code, output }) => {
assertEquals(code, 0);
assertStrContains(output, "my name is: Deno");
},
);
});

Deno.test("execute existing inline script when multiple are specified with multiple environment variables", async () => {
await testDenoXRun(
"inlineEnv",
"test/fixture/multiple_scripts",
async ({ code, output }) => {
assertEquals(code, 0);
assertStrContains(output, "my name is: Deno and my last name is: Script");
},
);
});

Deno.env.set("ENV_VAR_1", "Deno");
Deno.env.set("ENV_VAR_2", "Script");

Deno.test("execute existing inline deno script when multiple are specified", async () => {
// Set the env vars we'll need for testing.

await testDenoXRun(
"inline",
"test/fixture/multiple_scripts",
async ({ code, output }) => {
assertEquals(code, 0);
assertStrContains(output, "Hello World!");
},
);
});
6 changes: 6 additions & 0 deletions test/fixture/multiple_scripts/deno-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ scripts:
file: main.ts
develop:
file: develop.ts
inline:
cmd: deno run main.ts
inlineNoEnv:
cmd: "echo my name is: Deno"
inlineEnv:
cmd: "echo my name is: ${ENV_VAR_1} and my last name is: ${ENV_VAR_2}"