Skip to content

Commit

Permalink
feat(twilio-run): restructure configuration
Browse files Browse the repository at this point in the history
This PR aims to restructure the way configuration is handled by dropping .twilio-functions files in
favor of a dedicated config file and a deploy info cache file.

BREAKING CHANGE: Drops support for .twilio-functions files and internally restructures activate
files to promote

fix #166
  • Loading branch information
dkundel committed Nov 12, 2020
1 parent 0f3b317 commit 2298573
Show file tree
Hide file tree
Showing 38 changed files with 1,248 additions and 635 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ dist/

*.tsbuildinfo
.twilio-functions
*.twiliodeployinfo

packages/serverless-api/docs/
62 changes: 62 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Configuration

## Examples

### Functions with Preprocessor like TypeScript or Babel

By default the Serverless Toolkit will be looking for Functions inside a `functions/` or `src/` directory and for assets for an `assets/` or `static/` directory.

If you are using a pre-processor such as TypeScript or Babel, your structure might look different. For example maybe you have a structure where `src/` contains all TypeScript files and `dist/` contains the output.

In which case you'd want a config that looks similar to this:

```json
{
"functionsFolderName": "dist"
}
```

### Using different `.env` files for different environments

If you are deploying to different environments you might want different environment variables for your application.

You can specify environment specific configurations inside the config file by using the domain suffix of your environment.

If you are using the `--production` flag you'll need to use the environment: `*`.

For example:

```json
{
"environments": {
"dev": {
"env": ".env"
},
"stage": {
"env": ".env.stage"
},
"*": {
"env": ".env.prod"
}
}
}
```

### Deploy to specific services on different accounts

There might be a scenario where you want to deploy the same project to different Twilio accounts or projects with different services.

Inside the config you can define which service the project should be deployed to depending on the Twilio Account SID.

```json
{
"projects": {
"AC11111111111111111111111111111111": {
"serviceSid": "ZSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"AC22222222222222222222222222222222": {
"serviceSid": "ZSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
}
}
```
131 changes: 131 additions & 0 deletions packages/twilio-run/__tests__/config/global.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
jest.mock('../../src/config/utils/configLoader');

import { readSpecializedConfig } from '../../src/config/global';
import { ConfigurationFile } from '../../src/types/config';
const {
__setTestConfig,
}: {
__setTestConfig: (config: Partial<ConfigurationFile>) => void;
} = require('../../src/config/utils/configLoader');

describe('readSpecializedConfig', () => {
test('returns the right base config', () => {
__setTestConfig({
serviceSid: 'ZS11112222111122221111222211112222',
env: '.env.example',
});

expect(
readSpecializedConfig('/tmp', '.twilioserverlessrc', 'deploy')
).toEqual({
serviceSid: 'ZS11112222111122221111222211112222',
env: '.env.example',
});
});

test('merges command-specific config', () => {
__setTestConfig({
serviceSid: 'ZS11112222111122221111222211112222',
commands: {
deploy: {
functionsFolder: '/tmp/functions',
},
},
});

expect(
readSpecializedConfig('/tmp', '.twilioserverlessrc', 'deploy')
).toEqual({
serviceSid: 'ZS11112222111122221111222211112222',
functionsFolder: '/tmp/functions',
});
});

test('ignores other command-specific config', () => {
__setTestConfig({
serviceSid: 'ZS11112222111122221111222211112222',
commands: {
deploy: {
functionsFolder: '/tmp/functions',
},
start: {
functionsFolder: '/tmp/src',
},
},
});

expect(
readSpecializedConfig('/tmp', '.twilioserverlessrc', 'deploy')
).toEqual({
serviceSid: 'ZS11112222111122221111222211112222',
functionsFolder: '/tmp/functions',
});
});

test('environments override other config', () => {
__setTestConfig({
serviceSid: 'ZS11112222111122221111222211112222',
env: '.env.example',
commands: {
deploy: {
functionsFolder: '/tmp/functions',
},
},
environments: {
stage: {
env: '.env.stage',
},
'*': {
env: '.env.prod',
},
},
});

expect(
readSpecializedConfig('/tmp', '.twilioserverlessrc', 'deploy', {
environmentSuffix: 'stage',
})
).toEqual({
serviceSid: 'ZS11112222111122221111222211112222',
functionsFolder: '/tmp/functions',
env: '.env.stage',
});
});

test('account config overrides every other config', () => {
__setTestConfig({
serviceSid: 'ZS11112222111122221111222211112222',
env: '.env.example',
commands: {
deploy: {
functionsFolder: '/tmp/functions',
},
},
environments: {
stage: {
serviceSid: 'ZS11112222111122221111222211112223',
env: '.env.stage',
},
'*': {
env: '.env.prod',
},
},
projects: {
AC11112222111122221111222211114444: {
serviceSid: 'ZS11112222111122221111222211114444',
},
},
});

expect(
readSpecializedConfig('/tmp', '.twilioserverlessrc', 'deploy', {
environmentSuffix: 'stage',
accountSid: 'AC11112222111122221111222211114444',
})
).toEqual({
serviceSid: 'ZS11112222111122221111222211114444',
functionsFolder: '/tmp/functions',
env: '.env.stage',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import '../../../src/config/utils/mergeFlagsAndConfig';
import { mergeFlagsAndConfig } from '../../../src/config/utils/mergeFlagsAndConfig';

const baseFlags = {
config: '.twilio-functions',
config: '.twilioserverlessrc.js',
cwd: process.cwd(),
list: true,
};
Expand Down Expand Up @@ -58,7 +58,7 @@ describe('mergeFlagsAndConfig', () => {
expect(merged).toEqual({
template: 'bye',
list: false,
config: '.twilio-functions',
config: '.twilioserverlessrc.js',
cwd: process.cwd(),
});
});
Expand All @@ -75,7 +75,7 @@ describe('mergeFlagsAndConfig', () => {
expect(merged).toEqual({
template: 'hello',
list: false,
config: '.twilio-functions',
config: '.twilioserverlessrc.js',
cwd: process.cwd(),
});
});
Expand All @@ -93,7 +93,7 @@ describe('mergeFlagsAndConfig', () => {
expect(merged).toEqual({
template: 'hello',
list: false,
config: '.twilio-functions',
config: '.twilioserverlessrc.js',
cwd: '/some/path',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { readPackageJsonContent } from '../../../src/config/utils/package-json';
import { getServiceNameFromFlags } from '../../../src/config/utils/service-name';

const baseFlags = {
config: '.twilio-functions',
config: '.twilioserverlessrc',
logLevel: 'info' as 'info',
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`writeDefaultConfigFile default file should match snapshot 1`] = `
"{
\\"commands\\": {},
\\"environments\\": {},
\\"projects\\": {},
// \\"accountSid\\": null /* A specific account SID to be used for deployment. Uses fields in .env otherwise */,
// \\"assets\\": true /* Upload assets. Can be turned off with --no-assets */,
// \\"assetsFolder\\": null /* Specific folder name to be used for static assets */,
// \\"authToken\\": null /* Use a specific auth token for deployment. Uses fields from .env otherwise */,
// \\"buildSid\\": null /* An existing Build SID to deploy to the new environment */,
// \\"config\\": null /* Location of the config file. Absolute path or relative to current working directory (cwd) */,
// \\"createEnvironment\\": false /* Creates environment if it couldn't find it. */,
// \\"cwd\\": null /* Sets the directory of your existing Serverless project. Defaults to current directory */,
// \\"detailedLogs\\": false /* Toggles detailed request logging by showing request body and query params */,
// \\"edge\\": null /* Twilio API Region */,
// \\"env\\": null /* Path to .env file for environment variables that should be installed */,
// \\"environment\\": \\"dev\\" /* The environment name (domain suffix) you want to use for your deployment */,
// \\"experimentalForkProcess\\": false /* Enable forking function processes to emulate production environment */,
// \\"extendedOutput\\": false /* Show an extended set of properties on the output */,
// \\"force\\": false /* Will run deployment in force mode. Can be dangerous. */,
// \\"functionSid\\": null /* Specific Function SID to retrieve logs for */,
// \\"functions\\": true /* Upload functions. Can be turned off with --no-functions */,
// \\"functionsFolder\\": null /* Specific folder name to be used for static functions */,
// \\"inspect\\": null /* Enables Node.js debugging protocol */,
// \\"inspectBrk\\": null /* Enables Node.js debugging protocol, stops execution until debugger is attached */,
// \\"legacyMode\\": false /* Enables legacy mode, it will prefix your asset paths with /assets */,
// \\"live\\": true /* Always serve from the current functions (no caching) */,
// \\"loadLocalEnv\\": false /* Includes the local environment variables */,
// \\"loadSystemEnv\\": false /* Uses system environment variables as fallback for variables specified in your .env file. Needs to be used with --env explicitly specified. */,
// \\"logCacheSize\\": null /* Tailing the log endpoint will cache previously seen entries to avoid duplicates. The cache is topped at a maximum of 1000 by default. This option can change that. */,
// \\"logLevel\\": \\"info\\" /* Level of logging messages. */,
// \\"logs\\": true /* Toggles request logging */,
// \\"ngrok\\": null /* Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account). */,
// \\"outputFormat\\": \\"\\" /* Output the log in a different format */,
// \\"overrideExistingProject\\": false /* Deploys Serverless project to existing service if a naming conflict has been found. */,
// \\"port\\": \\"3000\\" /* Override default port of 3000 */,
// \\"production\\": false /* Promote build to the production environment (no domain suffix). Overrides environment flag */,
// \\"properties\\": null /* Specify the output properties you want to see. Works best on single types */,
// \\"region\\": null /* Twilio API Region */,
// \\"serviceName\\": null /* Overrides the name of the Serverless project. Default: the name field in your package.json */,
// \\"serviceSid\\": null /* SID of the Twilio Serverless Service to deploy to */,
// \\"sourceEnvironment\\": null /* SID or suffix of an existing environment you want to deploy from. */,
// \\"tail\\": false /* Continuously stream the logs */,
// \\"template\\": null /* undefined */,
}"
`;
47 changes: 47 additions & 0 deletions packages/twilio-run/__tests__/templating/defaultConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
let _shouldFileExist = false;
const writeFile = jest.fn().mockImplementation(async () => {});
const fileExists = jest.fn().mockImplementation(async () => _shouldFileExist);

jest.mock('../../src/utils/fs', () => {
return {
writeFile,
fileExists,
};
});

import { writeDefaultConfigFile } from '../../src/templating/defaultConfig';

describe('writeDefaultConfigFile', () => {
test('should write default file if none exists', async () => {
_shouldFileExist = false;
const wroteFile = await writeDefaultConfigFile('/tmp/');
expect(wroteFile).toEqual(true);
expect(writeFile).toHaveBeenCalled();
});

test('default file should match snapshot', async () => {
_shouldFileExist = false;
const wroteFile = await writeDefaultConfigFile('/tmp/');
expect(wroteFile).toEqual(true);
expect(
writeFile.mock.calls[writeFile.mock.calls.length - 1][1]
).toMatchSnapshot();
});

test('should not write false file if one exists', async () => {
_shouldFileExist = true;
const wroteFile = await writeDefaultConfigFile('/tmp/');
expect(wroteFile).toEqual(false);
expect(writeFile).not.toHaveBeenCalled();
});

test('should handle if the default file could not be written', async () => {
_shouldFileExist = false;
writeFile.mockImplementationOnce(() => {
throw new Error('Expected error');
});
const wroteFile = await writeDefaultConfigFile('/tmp/');
expect(wroteFile).toEqual(false);
expect(writeFile).toHaveBeenCalled();
});
});
5 changes: 5 additions & 0 deletions packages/twilio-run/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"columnify": "^1.5.4",
"common-tags": "^1.8.0",
"conf": "^5.0.0",
"cosmiconfig": "^7.0.0",
"debug": "^3.1.0",
"dotenv": "^6.2.0",
"express": "^4.16.3",
Expand All @@ -55,6 +56,7 @@
"got": "^9.6.0",
"inquirer": "^6.5.0",
"is-ci": "^2.0.0",
"json5": "^2.1.3",
"listr": "^0.14.3",
"lodash.camelcase": "^4.3.0",
"lodash.debounce": "^4.0.8",
Expand All @@ -66,6 +68,7 @@
"nocache": "^2.1.0",
"normalize.css": "^8.0.1",
"ora": "^3.3.1",
"ow": "^0.19.0",
"pkg-install": "^1.0.0",
"serialize-error": "^7.0.1",
"terminal-link": "^1.3.0",
Expand All @@ -84,7 +87,9 @@
"@types/express-useragent": "^0.2.21",
"@types/got": "^9.6.0",
"@types/jest": "^24.0.15",
"@types/json5": "0.0.30",
"@types/listr": "^0.14.0",
"@types/lodash.camelcase": "^4.3.6",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.flatten": "^4.4.6",
"@types/lodash.kebabcase": "^4.1.6",
Expand Down
Loading

0 comments on commit 2298573

Please sign in to comment.