diff --git a/__tests__/templating/hooks/run-hook.ts b/__tests__/templating/hooks/run-hook.ts new file mode 100644 index 00000000..1e9f0274 --- /dev/null +++ b/__tests__/templating/hooks/run-hook.ts @@ -0,0 +1,29 @@ +jest.unmock('twilio'); +import { stripIndent } from 'common-tags'; +import { runHook } from '../../../src/templating/hooks/run-hook'; + +const EXAMPLE_HOOK = stripIndent` + async function postinstall(client, env) { + const services = await client.serverless.services.list(); + return { + env: { + SERVICE_SID: services[0].sid + } + } + } + module.exports = { postinstall } +`; + +describe('runPostInstallHook', () => { + test('runs', async () => { + const output = await runHook( + 'postinstall', + EXAMPLE_HOOK, + { + DATABASE_URL: 'http://localhost:3000', + }, + console + ); + expect(true).toBeTruthy(); + }); +}); diff --git a/package.json b/package.json index 0a1f4908..bf18f86c 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "title": "^3.4.1", "twilio": "^3.33.0", "type-fest": "^0.6.0", + "vm2": "^3.8.4", "window-size": "^1.1.1", "wrap-ansi": "^5.1.0", "yargs": "^13.2.2" diff --git a/src/commands/new.ts b/src/commands/new.ts index 87c25e24..34448d64 100644 --- a/src/commands/new.ts +++ b/src/commands/new.ts @@ -6,12 +6,16 @@ import { Arguments, Argv } from 'yargs'; import checkProjectStructure from '../checks/project-structure'; import { downloadTemplate, fetchListOfTemplates } from '../templating/actions'; import { setLogLevelByName } from '../utils/logger'; -import { baseCliOptions, BaseFlags, ExternalCliOptions } from './shared'; +import { + baseCliOptions, + ExternalCliOptions, + SharedFlagsWithCredentials, +} from './shared'; import { CliInfo } from './types'; import { getFullCommand } from './utils'; export type NewCliFlags = Arguments< - BaseFlags & { + SharedFlagsWithCredentials & { namespace?: string; template?: string; } @@ -20,6 +24,8 @@ export type NewCliFlags = Arguments< export type NewConfig = Merge< NewCliFlags, { + cwd?: string; + env?: string; namespace?: string; template?: string; } diff --git a/src/commands/shared.ts b/src/commands/shared.ts index db141afb..db187428 100644 --- a/src/commands/shared.ts +++ b/src/commands/shared.ts @@ -10,7 +10,7 @@ export type SharedFlags = BaseFlags & { cwd?: string; }; -export type SharedFlagsWithCrdentials = SharedFlags & { +export type SharedFlagsWithCredentials = SharedFlags & { accountSid?: string; authToken?: string; env?: string; diff --git a/src/config/activate.ts b/src/config/activate.ts index 24fb1145..e8ad5fa5 100644 --- a/src/config/activate.ts +++ b/src/config/activate.ts @@ -5,7 +5,7 @@ import checkForValidServiceSid from '../checks/check-service-sid'; import { cliInfo } from '../commands/activate'; import { ExternalCliOptions, - SharedFlagsWithCrdentials, + SharedFlagsWithCredentials, } from '../commands/shared'; import { getFullCommand } from '../commands/utils'; import { readSpecializedConfig } from './global'; @@ -19,7 +19,7 @@ type ActivateConfig = ApiActivateConfig & { }; export type ActivateCliFlags = Arguments< - SharedFlagsWithCrdentials & { + SharedFlagsWithCredentials & { cwd?: string; serviceSid?: string; buildSid?: string; diff --git a/src/config/deploy.ts b/src/config/deploy.ts index f249e20d..cf897bc4 100644 --- a/src/config/deploy.ts +++ b/src/config/deploy.ts @@ -4,7 +4,7 @@ import { Arguments } from 'yargs'; import { cliInfo } from '../commands/deploy'; import { ExternalCliOptions, - SharedFlagsWithCrdentials, + SharedFlagsWithCredentials, } from '../commands/shared'; import { deprecateFunctionsEnv } from '../commands/utils'; import { getFunctionServiceSid } from '../serverless-api/utils'; @@ -18,7 +18,7 @@ import { import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig'; export type DeployCliFlags = Arguments< - SharedFlagsWithCrdentials & { + SharedFlagsWithCredentials & { serviceSid?: string; functionsEnv?: string; environment: string; diff --git a/src/config/list.ts b/src/config/list.ts index d93cc5d8..108c553b 100644 --- a/src/config/list.ts +++ b/src/config/list.ts @@ -7,7 +7,7 @@ import { Arguments } from 'yargs'; import { cliInfo } from '../commands/list'; import { ExternalCliOptions, - SharedFlagsWithCrdentials, + SharedFlagsWithCredentials, } from '../commands/shared'; import { getFunctionServiceSid } from '../serverless-api/utils'; import { readSpecializedConfig } from './global'; @@ -21,7 +21,7 @@ export type ListConfig = ApiListConfig & { }; export type ListCliFlags = Arguments< - SharedFlagsWithCrdentials & { + SharedFlagsWithCredentials & { types: string; projectName?: string; serviceName?: string; diff --git a/src/config/utils/credentials.ts b/src/config/utils/credentials.ts index dbb57552..e8603f5d 100644 --- a/src/config/utils/credentials.ts +++ b/src/config/utils/credentials.ts @@ -1,6 +1,6 @@ import { ExternalCliOptions, - SharedFlagsWithCrdentials, + SharedFlagsWithCredentials, } from '../../commands/shared'; import { getDebugFunction } from '../../utils/logger'; import { readLocalEnvFile } from './env'; @@ -24,7 +24,7 @@ export type Credentials = { * @param externalCliOptions Any external information for example passed by the Twilio CLI */ export async function getCredentialsFromFlags< - T extends SharedFlagsWithCrdentials + T extends SharedFlagsWithCredentials >(flags: T, externalCliOptions?: ExternalCliOptions): Promise { // default Twilio CLI credentials (4) or empty string (5) let accountSid = diff --git a/src/templating/hooks/HookError.ts b/src/templating/hooks/HookError.ts new file mode 100644 index 00000000..9bd1ce53 --- /dev/null +++ b/src/templating/hooks/HookError.ts @@ -0,0 +1,3 @@ +export class HookError extends Error { + name = 'HookError'; +} diff --git a/src/templating/hooks/postinstall.ts b/src/templating/hooks/postinstall.ts new file mode 100644 index 00000000..81d8137b --- /dev/null +++ b/src/templating/hooks/postinstall.ts @@ -0,0 +1,11 @@ +import { EnvironmentVariablesWithAuth } from '../../types/generic'; +import { ILogger } from '../../utils/logger'; +import { runHook } from './run-hook'; + +export async function executePostInstallHook( + rawScript: string, + env: EnvironmentVariablesWithAuth, + logger: Console | ILogger +) { + return runHook('postinstall', rawScript, env, logger); +} diff --git a/src/templating/hooks/run-hook.ts b/src/templating/hooks/run-hook.ts new file mode 100644 index 00000000..58624fad --- /dev/null +++ b/src/templating/hooks/run-hook.ts @@ -0,0 +1,46 @@ +import * as Twilio from 'twilio'; +import { NodeVM, VMScript } from 'vm2'; +import { EnvironmentVariablesWithAuth } from '../../types/generic'; +import { getDebugFunction, ILogger } from '../../utils/logger'; +import { HookError } from './HookError'; + +const debug = getDebugFunction('twilio-run:templating:hooks:run'); + +export async function runHook( + hookName: string, + rawScript: string, + env: EnvironmentVariablesWithAuth, + logger: Console | ILogger +) { + const vm = new NodeVM({ + console: 'off', + sandbox: { + Twilio: Twilio, + console: logger, + process: { + env: env, + }, + }, + require: { + external: false, + builtin: [], + root: process.cwd(), + }, + }); + const client = new Twilio.Twilio( + process.env.TWILIO_ACCOUNT_SID || '', + process.env.TWILIO_AUTH_TOKEN || '' + ); + const script = new VMScript(rawScript); + const { [hookName]: hookFunction } = vm.run(script); + + try { + const output = await hookFunction(client, env); + return output; + } catch (err) { + debug('%O', err); + throw new HookError( + 'Hook failed running. Please file an issue at github.com/twilio-labs/function-templates' + ); + } +}