diff --git a/deps.ts b/deps.ts index c601dec..488d6a5 100644 --- a/deps.ts +++ b/deps.ts @@ -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"; diff --git a/src/interfaces.ts b/src/interfaces.ts index a766f65..a60335a 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,5 @@ type DenoOptionValue = unknown; +type DotEnvConfigValue = string; type DenoOptionsEntries = { [key: string]: DenoOptionValue; @@ -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; @@ -21,6 +26,11 @@ 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, @@ -28,4 +38,5 @@ export { WorkspaceOptions, DenoOptionsEntries, DenoOptionValue, + DotenvConfig, }; diff --git a/src/run.ts b/src/run.ts index 13513fb..d2ff857 100644 --- a/src/run.ts +++ b/src/run.ts @@ -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"; @@ -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( diff --git a/src/utils/__tests__/replaceEnvVars.test.ts b/src/utils/__tests__/replaceEnvVars.test.ts new file mode 100644 index 0000000..d4f20f8 --- /dev/null +++ b/src/utils/__tests__/replaceEnvVars.test.ts @@ -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); +}); diff --git a/src/utils/replaceEnvVars.ts b/src/utils/replaceEnvVars.ts new file mode 100644 index 0000000..a20616b --- /dev/null +++ b/src/utils/replaceEnvVars.ts @@ -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) => { + // 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); + }); + + return cmd; +} + +export default replaceEnvVars; diff --git a/test/e2e/scripts.test.ts b/test/e2e/scripts.test.ts index 3c0756d..cd7525d 100644 --- a/test/e2e/scripts.test.ts +++ b/test/e2e/scripts.test.ts @@ -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!"); + }, + ); +}); diff --git a/test/fixture/multiple_scripts/deno-workspace b/test/fixture/multiple_scripts/deno-workspace index 4c29688..3e9d70a 100644 --- a/test/fixture/multiple_scripts/deno-workspace +++ b/test/fixture/multiple_scripts/deno-workspace @@ -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}"