From dc15c1aef613d4c86472afec02b0cd896355dc73 Mon Sep 17 00:00:00 2001 From: Fabrizio Antonangeli Date: Sat, 13 Nov 2021 18:16:42 +0100 Subject: [PATCH] Run command arguments elaboration --- .gitignore | 1 + src/common.ts | 33 ++++++++++++++++++---- src/index.ts | 3 +- src/run.ts | 47 ++++++++++++++++++++++++++++++++ src/utils.ts | 11 ++++++++ test/{test.ts => common.test.ts} | 15 +++++++++- test/run.test.ts | 13 +++++++++ 7 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 src/run.ts create mode 100644 src/utils.ts rename test/{test.ts => common.test.ts} (58%) create mode 100644 test/run.test.ts diff --git a/.gitignore b/.gitignore index d31a17c..f8ba8f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .clasp.json +.multi-clasp.json .tags .DS_Store node_modules diff --git a/src/common.ts b/src/common.ts index 00c5279..cd0b62b 100644 --- a/src/common.ts +++ b/src/common.ts @@ -3,6 +3,7 @@ import * as util from 'util'; import * as child_process from 'child_process'; const exec = util.promisify(child_process.exec); import { Config } from './config'; +import {OptionValues} from 'commander'; /** @@ -43,23 +44,43 @@ export function readMultiClaspConfig(): SingleClasp[] { return JSON.parse(fs.readFileSync(Config.MULTICLASP_FILENAME, Config.UTF_8 as BufferEncoding).toString()); } +/** + * Get the Options for the clasp command. + * + * @param args array of arguments + * @returns the string with the options, "" otherwise + */ +export function getOptions (args:string[]=process.argv):string { + if (!args) { + return ""; + } + return args.slice(3).join(' '); +} + /** * commander action to run clasp. * * @returns */ -export async function genericAction(): Promise { - let retVal=true; +export async function foreachClasp(fn:(claspConfig:SingleClasp)=>Promise):Promise { const clasps = readMultiClaspConfig(); for (let i = 0, len = clasps.length; i < len; i++) { - retVal = await runClasp(clasps[i], process.argv[2], process.argv.slice(3).join(' ')); - if (!retVal) { - return; - } + await fn(clasps[i]); } fs.unlink(Config.CLASP_FILENAME, (err) => { if (err) throw err; }); } + +/** + * commander action to run clasp. + * + * @returns + */ +export async function genericAction(): Promise { + foreachClasp(async (claspConfig)=>{ + await runClasp(claspConfig, process.argv[2], getOptions()); + }); +} diff --git a/src/index.ts b/src/index.ts index a07ee55..03b091e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { Command } from 'commander'; import { Config } from './config'; import { genericAction, readMultiClaspConfig, runClasp } from './common'; import {version} from '../package.json'; +import run from './run'; const program = new Command(); @@ -50,7 +51,7 @@ program .description('Run a function in your Apps Scripts project') .option('--nondev', 'Run script function in non-devMode') .option('-p, --params [StringArray]', 'Add parameters required for the function as a JSON String Array') - .action(genericAction); + .action(run); program .command('version [description]') diff --git a/src/run.ts b/src/run.ts new file mode 100644 index 0000000..a99dbf6 --- /dev/null +++ b/src/run.ts @@ -0,0 +1,47 @@ +import {foreachClasp, runClasp} from "./common"; +import {parseJsonOrDie} from "./utils"; + +interface CommandOption { + readonly nondev: boolean; + readonly params: string; +} + +/** + * Get Clasp Arguments. + * + * @param functionName {string} The function name within the Apps Script project. + * @param options.nondev {boolean} If we want to run the last deployed version vs the latest code. + * @param options.params {string} JSON string of parameters to be input to function. + * @returns {string} the command arguments + */ +export function getRunClaspArgs(functionName="", options: CommandOption): string{ + let claspArgs = functionName || ""; + const {params: jsonString = '[]'} = options; + const parameters = parseJsonOrDie(jsonString); + + if (!options) { + return claspArgs; + } + + claspArgs += options.nondev?" --nondev":""; + + claspArgs += " --params '"+JSON.stringify(parameters)+"'"; + + return claspArgs; +} + +/** + * Executes an Apps Script function. Requires clasp login --creds. + * @param functionName {string} The function name within the Apps Script project. + * @param options.nondev {boolean} If we want to run the last deployed version vs the latest code. + * @param options.params {string} JSON string of parameters to be input to function. + * @see https://developers.google.com/apps-script/api/how-tos/execute + * @requires `clasp login --creds` to be run beforehand. + */ +export default async (functionName: string, options: CommandOption): Promise => { + const claspArgs = getRunClaspArgs(functionName, options); + + foreachClasp(async (claspConfig)=>{ + await runClasp(claspConfig, process.argv[2], claspArgs); + }); +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2cef108 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,11 @@ +/** + * Parses input string into a valid JSON object or throws a `ClaspError` error. + * @param value JSON string. + */ +export const parseJsonOrDie = (value: string): T => { + try { + return JSON.parse(value) as T; + } catch { + throw "Invalid json"; + } +}; diff --git a/test/test.ts b/test/common.test.ts similarity index 58% rename from test/test.ts rename to test/common.test.ts index 8bffd07..e562b9a 100644 --- a/test/test.ts +++ b/test/common.test.ts @@ -1,5 +1,5 @@ import * as fs from "fs"; -import { runClasp } from '../src/common'; +import { getOptions, runClasp } from '../src/common'; jest.mock('util', () => ({ promisify: jest.fn(() => { @@ -24,4 +24,17 @@ describe('common.js tests', () => { expect(fs.writeFile).toHaveBeenCalledTimes(1); }); }); + + describe('getOptions', () => { + test('wrong inputs', () => { + expect(getOptions([])).toBe(""); + expect(getOptions(undefined)).toBe(""); + }) + test('good inputs', () => { + expect(getOptions(["node", "multi-clasp", "push"])).toBe(""); + expect(getOptions(["node", "multi-clasp", "push", "-f"])).toBe("-f"); + expect(getOptions(["node", "multi-clasp", "push", "-f", "-g", "what"])).toBe("-f -g what"); + expect(getOptions(["node", "multi-clasp", "run", "-p", '\'["what"]\''])).toBe("-p '[\"what\"]'"); + }) + }); }); diff --git a/test/run.test.ts b/test/run.test.ts new file mode 100644 index 0000000..9ac58f8 --- /dev/null +++ b/test/run.test.ts @@ -0,0 +1,13 @@ +import {getRunClaspArgs} from "../src/run"; + + +describe('run.ts tests', ()=>{ + test('wrong inputs', () => { + expect(()=>(getRunClaspArgs("", {nondev:false, params:''}))).toThrow("Invalid json"); + }); + test('good inputs', () => { + expect(getRunClaspArgs("testFunc", {nondev:false, params:'[]'})).toBe("testFunc --params '[]'"); + expect(getRunClaspArgs("testFunc", {nondev:false, params:'["dino"]'})).toBe("testFunc --params '[\"dino\"]'"); + expect(getRunClaspArgs("testFunc", {nondev:true, params:'["dino", "pepe"]'})).toBe("testFunc --nondev --params '[\"dino\",\"pepe\"]'"); + }); +})