From a9b65e4ec2cafcfd66a54f158864c10cf8611cee Mon Sep 17 00:00:00 2001 From: Ryan Wattrus <09wattry@gmail.com> Date: Tue, 16 Jun 2020 13:57:58 -0600 Subject: [PATCH 1/2] Added support for inline scripting --- deps.ts | 1 + src/interfaces.ts | 15 +++++-- src/run.ts | 34 +++++++++++++++- src/utils/__tests__/replaceEnvVars.test.ts | 28 +++++++++++++ src/utils/replaceEnvVars.ts | 28 +++++++++++++ test/e2e/scripts.test.ts | 41 +++++++++++++++++++- test/fixture/multiple_scripts/deno-workspace | 6 +++ 7 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 src/utils/__tests__/replaceEnvVars.test.ts create mode 100644 src/utils/replaceEnvVars.ts diff --git a/deps.ts b/deps.ts index 22efb06..cd2db06 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.50.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..c079699 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,11 @@ 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 +24,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 +36,5 @@ export { WorkspaceOptions, DenoOptionsEntries, DenoOptionValue, + DotenvConfig }; diff --git a/src/run.ts b/src/run.ts index 6b75955..d575edf 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"; @@ -21,7 +22,7 @@ async function run(scriptName: string): Promise { } catch (e) { if (e instanceof Deno.errors.PermissionDenied) { consolex.error(` - Please reinstall denox with the correct pemissions + Please reinstall denox with the correct permissions deno install -Af -n denox https://denopkg.com/BentoumiTech/denox/denox.ts `); } else { @@ -43,7 +44,36 @@ 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 }> { + // Use or replacement function to get any env variables switched out. + const replaced = replaceEnvVars(workspaceScript?.cmd, Deno.env.toObject()); + // Split on all the spaces to create an array of arguments + const cmd: any[] = replaced.split(' ').concat(args) || []; + const [mainScript] = cmd; + + // If the first argument is deno then we want the deno options. + if (mainScript === 'deno') { + 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..858b2e1 --- /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); +}); \ No newline at end of file diff --git a/src/utils/replaceEnvVars.ts b/src/utils/replaceEnvVars.ts new file mode 100644 index 0000000..eeb883f --- /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; \ No newline at end of file diff --git a/test/e2e/scripts.test.ts b/test/e2e/scripts.test.ts index 767a702..df7ee67 100644 --- a/test/e2e/scripts.test.ts +++ b/test/e2e/scripts.test.ts @@ -1,5 +1,10 @@ import { assertEquals, assertStrContains } from "../../dev_deps.ts"; import { testDenoXRun } from "../utils/denox-run.ts"; +import replaceEnvVars from '../../src/utils/replaceEnvVars.ts' + +// Set the env vars we'll need for testing. +Deno.env.set('ENV_VAR_1', 'Deno'); +Deno.env.set('ENV_VAR_2', 'Script'); Deno.test("Return an error when script doesn't exist", async () => { await testDenoXRun( @@ -12,7 +17,7 @@ Deno.test("Return an error when script doesn't exist", async () => { ); }); -Deno.test("execute exising script", async () => { +Deno.test("execute existing script", async () => { await testDenoXRun( "start", "test/fixture/single_script", @@ -33,3 +38,37 @@ 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.test("execute existing inline deno script when multiple are specified", async () => { + 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}" From bf9b2ebfeaf36529c4d83474f1bc8aa9407f8441 Mon Sep 17 00:00:00 2001 From: Ryan Wattrus <09wattry@gmail.com> Date: Tue, 16 Jun 2020 18:33:29 -0600 Subject: [PATCH 2/2] Fixed linting issues. --- src/interfaces.ts | 14 ++++---- src/parser/deno_workspace.ts | 2 +- src/run.ts | 20 ++++------- src/utils/__tests__/file.test.ts | 41 ++++++++++++--------- src/utils/__tests__/replaceEnvVars.test.ts | 16 ++++----- src/utils/replaceEnvVars.ts | 16 ++++----- test/e2e/args.test.ts | 42 +++++++++++----------- test/e2e/scripts.test.ts | 9 +++-- test/fixture/args/main.ts | 2 +- test/utils/denox-run.ts | 10 ++++-- 10 files changed, 90 insertions(+), 82 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index c079699..a60335a 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -10,9 +10,11 @@ type WorkspaceOptions = { }; // There must be either a file or a command provided. -type WorkspaceScript = ( - { file: string; cmd?: undefined } - | { cmd: string; file?: undefined }) +type WorkspaceScript = + & ( + | { file: string; cmd?: undefined } + | { cmd: string; file?: undefined } + ) & WorkspaceOptions; type WorkspaceGlobal = WorkspaceOptions; @@ -26,8 +28,8 @@ type DenoWorkspace = { // This type is exported from dotenv, which I'm assuming you will be using for this port. type DotenvConfig = { - [key: string]: DotEnvConfigValue -} + [key: string]: DotEnvConfigValue; +}; export { DenoWorkspace, @@ -36,5 +38,5 @@ export { WorkspaceOptions, DenoOptionsEntries, DenoOptionValue, - DotenvConfig + DotenvConfig, }; diff --git a/src/parser/deno_workspace.ts b/src/parser/deno_workspace.ts index e7dbcfe..50abd9d 100644 --- a/src/parser/deno_workspace.ts +++ b/src/parser/deno_workspace.ts @@ -16,7 +16,7 @@ async function loadDenoWorkspace(): Promise { ); if (extname(denoWorkspaceFilePath) === ".ts") { - const appendFileScheme = Deno.build.os == "windows" ? 'file:///' : ''; + const appendFileScheme = Deno.build.os == "windows" ? "file:///" : ""; return await _loadTSWorkspace(appendFileScheme + denoWorkspaceFilePath); } diff --git a/src/run.ts b/src/run.ts index d575edf..d27b152 100644 --- a/src/run.ts +++ b/src/run.ts @@ -2,7 +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 replaceEnvVars from "./utils/replaceEnvVars.ts"; import { upgradeVersionMessage } from "./lib/upgrade_version.ts"; @@ -54,21 +54,15 @@ async function _runInlineScript( workspaceGlobal: WorkspaceOptions, args: string[], ): Promise<{ code: number }> { - // Use or replacement function to get any env variables switched out. - const replaced = replaceEnvVars(workspaceScript?.cmd, Deno.env.toObject()); - // Split on all the spaces to create an array of arguments - const cmd: any[] = replaced.split(' ').concat(args) || []; - const [mainScript] = cmd; - - // If the first argument is deno then we want the deno options. - if (mainScript === 'deno') { - const denoOptions = await _getDenoOptions(workspaceScript, workspaceGlobal); + const cmd: any[] = replaceEnvVars(workspaceScript?.cmd, Deno.env.toObject()) + .split(" ") + .concat(args) || []; + const denoOptions = await _getDenoOptions(workspaceScript, workspaceGlobal); - cmd.concat(denoOptions); - } + cmd.concat(denoOptions); const process = Deno.run({ - cmd + cmd, }); const { code } = await process.status(); diff --git a/src/utils/__tests__/file.test.ts b/src/utils/__tests__/file.test.ts index 2a41220..82bf917 100644 --- a/src/utils/__tests__/file.test.ts +++ b/src/utils/__tests__/file.test.ts @@ -1,9 +1,15 @@ -import { assertEquals, assertThrowsAsync, resolve, assertStrContains } from "../../../dev_deps.ts"; +import { + assertEquals, + assertThrowsAsync, + resolve, + assertStrContains, +} from "../../../dev_deps.ts"; import { getFileContent, getFirstExistingPath, exists } from "../file.ts"; - Deno.test("read file", async () => { - const fileContent = await getFileContent('./src/utils/__tests__/fixture/file.txt') + const fileContent = await getFileContent( + "./src/utils/__tests__/fixture/file.txt", + ); assertStrContains( fileContent, @@ -12,17 +18,17 @@ Deno.test("read file", async () => { }); Deno.test("throw error if file don't exisit", async () => { - await assertThrowsAsync(async ()=> { - await getFileContent('./src/utils/__tests__/fixture/no-found.txt') + await assertThrowsAsync(async () => { + await getFileContent("./src/utils/__tests__/fixture/no-found.txt"); }, Deno.errors.NotFound); }); Deno.test("return first exisiting path", async () => { const firstExistingPath = await getFirstExistingPath([ - './src/utils/__tests__/fixture/list-files/first.txt', - './src/utils/__tests__/fixture/list-files/second.txt', - './src/utils/__tests__/fixture/list-files/third.txt', - ]) + "./src/utils/__tests__/fixture/list-files/first.txt", + "./src/utils/__tests__/fixture/list-files/second.txt", + "./src/utils/__tests__/fixture/list-files/third.txt", + ]); assertEquals( firstExistingPath, @@ -31,18 +37,17 @@ Deno.test("return first exisiting path", async () => { }); Deno.test("throw error if no file in list exist", async () => { - await assertThrowsAsync(async ()=> { + await assertThrowsAsync(async () => { await getFirstExistingPath([ - './src/utils/__tests__/fixture/list-files/fourth.txt', - './src/utils/__tests__/fixture/list-files/fifth.txt', - './src/utils/__tests__/fixture/list-files/sixth.txt', - ]) + "./src/utils/__tests__/fixture/list-files/fourth.txt", + "./src/utils/__tests__/fixture/list-files/fifth.txt", + "./src/utils/__tests__/fixture/list-files/sixth.txt", + ]); }, Deno.errors.NotFound); }); - Deno.test("return true if file exist", async () => { - const fileExist = await exists('./src/utils/__tests__/fixture/file.txt') + const fileExist = await exists("./src/utils/__tests__/fixture/file.txt"); assertEquals( fileExist, @@ -51,7 +56,9 @@ Deno.test("return true if file exist", async () => { }); Deno.test("return false if file is not found", async () => { - const fileExist = await exists('./src/utils/__tests__/fixture/file-not-found.txt') + const fileExist = await exists( + "./src/utils/__tests__/fixture/file-not-found.txt", + ); assertEquals( fileExist, diff --git a/src/utils/__tests__/replaceEnvVars.test.ts b/src/utils/__tests__/replaceEnvVars.test.ts index 858b2e1..d4f20f8 100644 --- a/src/utils/__tests__/replaceEnvVars.test.ts +++ b/src/utils/__tests__/replaceEnvVars.test.ts @@ -1,17 +1,17 @@ import { assertEquals, assertThrows } from "../../../dev_deps.ts"; -import replaceEnvVars from '../replaceEnvVars.ts'; +import replaceEnvVars from "../replaceEnvVars.ts"; -const CMD = 'deno run ${SOME_ENV_VAR_1} ${SOME_ENV_VAR_2} mod.js'; +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', + SOME_ENV_VAR_1: "VALUE_1", + SOME_ENV_VAR_2: "VALUE_2", }; -const REPLACED_CMD = 'deno run VALUE_1 VALUE_2 mod.js' +const REPLACED_CMD = "deno run VALUE_1 VALUE_2 mod.js"; Deno.test("should throw no cmd error", async () => { - assertThrows((): void => { - replaceEnvVars('', EMPTY_ENV) + assertThrows((): void => { + replaceEnvVars("", EMPTY_ENV); }, ReferenceError); }); @@ -25,4 +25,4 @@ Deno.test("should return the replaced cmd", async () => { const cmd = replaceEnvVars(CMD, ENV); assertEquals(cmd, REPLACED_CMD); -}); \ No newline at end of file +}); diff --git a/src/utils/replaceEnvVars.ts b/src/utils/replaceEnvVars.ts index eeb883f..a20616b 100644 --- a/src/utils/replaceEnvVars.ts +++ b/src/utils/replaceEnvVars.ts @@ -1,5 +1,5 @@ // TODO - Update this with the dotenv interface. -import { DotenvConfig } from '../interfaces.ts'; +import { DotenvConfig } from "../interfaces.ts"; /** * This is added here in anticipation of https://github.com/BentoumiTech/denox/issues/12 being completed. @@ -9,20 +9,20 @@ import { DotenvConfig } from '../interfaces.ts'; 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.'); + 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. + // 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. + // 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); + // 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; \ No newline at end of file +export default replaceEnvVars; diff --git a/test/e2e/args.test.ts b/test/e2e/args.test.ts index 7ac3a05..0a84d93 100644 --- a/test/e2e/args.test.ts +++ b/test/e2e/args.test.ts @@ -1,27 +1,26 @@ -import { testDenoXRun } from "../utils/denox-run.ts" -import { assertEquals } from "../../dev_deps.ts" - +import { testDenoXRun } from "../utils/denox-run.ts"; +import { assertEquals } from "../../dev_deps.ts"; Deno.test("Args are passed to the denox script", async () => { const args = { parsed: { - _: [ "first", "second", "third" ], - s: "value", - full: "value", - '--': [ "--test=arg", "fourth", "fifth" ] + _: ["first", "second", "third"], + s: "value", + full: "value", + "--": ["--test=arg", "fourth", "fifth"], }, - 'raw': [ - "first", - "second", - "third", - "-s=value", - "--full=value", - "--", - "--test=arg", - "fourth", - "fifth" - ] - }; + "raw": [ + "first", + "second", + "third", + "-s=value", + "--full=value", + "--", + "--test=arg", + "fourth", + "fifth", + ], + }; await testDenoXRun( "start", @@ -29,6 +28,9 @@ Deno.test("Args are passed to the denox script", async () => { async ({ code, output }) => { assertEquals(code, 0); assertEquals(JSON.parse(output), args); - }, 'first second third -s=value --full=value -- --test=arg fourth fifth'.split(' ') + }, + "first second third -s=value --full=value -- --test=arg fourth fifth".split( + " ", + ), ); }); diff --git a/test/e2e/scripts.test.ts b/test/e2e/scripts.test.ts index df7ee67..cd7525d 100644 --- a/test/e2e/scripts.test.ts +++ b/test/e2e/scripts.test.ts @@ -1,10 +1,5 @@ import { assertEquals, assertStrContains } from "../../dev_deps.ts"; import { testDenoXRun } from "../utils/denox-run.ts"; -import replaceEnvVars from '../../src/utils/replaceEnvVars.ts' - -// Set the env vars we'll need for testing. -Deno.env.set('ENV_VAR_1', 'Deno'); -Deno.env.set('ENV_VAR_2', 'Script'); Deno.test("Return an error when script doesn't exist", async () => { await testDenoXRun( @@ -61,8 +56,12 @@ Deno.test("execute existing inline script when multiple are specified with multi ); }); +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", diff --git a/test/fixture/args/main.ts b/test/fixture/args/main.ts index 39a5d04..4a0a5bd 100644 --- a/test/fixture/args/main.ts +++ b/test/fixture/args/main.ts @@ -2,5 +2,5 @@ import { parse } from "https://deno.land/std/flags/mod.ts"; console.log(JSON.stringify({ parsed: parse(Deno.args, { "--": true }), - raw: Deno.args + raw: Deno.args, })); diff --git a/test/utils/denox-run.ts b/test/utils/denox-run.ts index 5cd2161..058168c 100644 --- a/test/utils/denox-run.ts +++ b/test/utils/denox-run.ts @@ -9,7 +9,7 @@ async function testDenoXRun( assertRunOutput: ( denoxOutput: { output: string; errOutput: string; code: number }, ) => Promise, - args: string[] = [] + args: string[] = [], ): Promise { await changeAndRestoreCWD( workspaceFolder, @@ -24,7 +24,11 @@ async function testDenoXRun( ); } -function _denoXRun(denoxPath: string, scriptName: string, args: string[]): Deno.Process { +function _denoXRun( + denoxPath: string, + scriptName: string, + args: string[], +): Deno.Process { return Deno.run({ cmd: [ "deno", @@ -33,7 +37,7 @@ function _denoXRun(denoxPath: string, scriptName: string, args: string[]): Deno. denoxPath, "run", scriptName, - ...args + ...args, ], stdout: "piped", stderr: "piped",