Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
camden11 committed Sep 18, 2024
0 parents commit 4fc1a4c
Show file tree
Hide file tree
Showing 17 changed files with 1,142 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
npm-debug.log
npm-debug.log.*
*.log
.DS_Store
.env
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [4.1.5](https://github.com/HubSpot/hubspot-cli/compare/v4.1.5-beta.4...v4.1.5) (2023-01-09)

**Note:** Version bump only for package @hubspot/serverless-dev-runtime





## [3.0.4](https://github.com/HubSpot/hubspot-cli/compare/v3.0.4-beta.1...v3.0.4) (2021-04-01)

**Note:** Version bump only for package @hubspot/serverless-dev-runtime
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @hubspot/serverless-dev-runtime

A serverless function development runtime that can be used to test CMS serverless functions. This is intended for use with the [CMS CLI](https://developers.hubspot.com/docs/cms/developer-reference/local-development-cms-cli).

⚠️ **This is a BETA release that uses some HubSpot features that are not available to all customer accounts. Please refer to the HubSpot [Developer Beta Terms](https://legal.hubspot.com/developerbetaterms)** ⚠️

## Getting started

For more information on using these tools, see [Local Development Tooling: Getting Started](https://designers.hubspot.com/tutorials/getting-started-with-local-development).

### Usage

#### CLI Command (recommended)
Using the CLI to run serverless functions locally, requires installing [@hubspot/cli](https://www.npmjs.com/package/@hubspot/cms-cli). Once installed, to test your functions run…

```bash
hs functions test <folder.functions>
```

#### Importing

It also is possible to use the runtime inside your own tooling. To start the server, the `start` method can be imported from the `@hubspot/serverless-dev-runtime` package and run with settings like so...

```bash
const { start } = require('@hubspot/serverless-dev-runtime');

start({
accountId: <portalId/accountId>, // default: 123456
contact: <booleanValueToSpecifyIfContactDataShouldBePassedToServerlessFunction>, // default: true
path: <pathToLocalDotFunctionsFolder>, // required
port: <customPortToRunServerOn> // default: 5432
});
```
### Mocked Data
Some of the data that is passed to the serverless function context is mocked. Specifically the `contact` and `limits` properties. It is possible
to modify the mocked data by setting values for specific variables within a `.env` file within the `.functions` folder.
The variables used to modify the data are:
```
HUBSPOT_LIMITS_TIME_REMAINING // default: 600000
HUBSPOT_LIMITS_EXECUTIONS_REMAINING // default: 60
HUBSPOT_CONTACT_VID // default: 123
HUBSPOT_CONTACT_IS_LOGGED_IN // default: false
HUBSPOT_CONTACT_LIST_MEMBERSHIPS // default: []
```
Usage example `.env`:
```
HUBSPOT_LIMITS_TIME_REMAINING=1000
HUBSPOT_LIMITS_EXECUTIONS_REMAINING=2
HUBSPOT_CONTACT_VID=456
HUBSPOT_CONTACT_IS_LOGGED_IN=true
HUBSPOT_CONTACT_LIST_MEMBERSHIPS="some, memberships"
```
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { start } = require('./lib/server');

module.exports = {
start,
};
47 changes: 47 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// AWS does not allow overriding these
// https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html#lambda-environment-variables
const AWS_RESERVED_VARS = [
'_HANDLER',
'LAMBDA_TASK_ROOT',
'LAMBDA_RUNTIME_DIR',
'AWS_EXECUTION_ENV',
'AWS_DEFAULT_REGION',
'AWS_REGION',
'AWS_LAMBDA_LOG_GROUP_NAME',
'AWS_LAMBDA_LOG_STREAM_NAME',
'AWS_LAMBDA_FUNCTION_NAME',
'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
'AWS_LAMBDA_FUNCTION_VERSION',
'AWS_ACCESS_KEY',
'AWS_ACCESS_KEY_ID',
'AWS_SECRET_KEY',
'AWS_SECRET_ACCESS_KEY',
'AWS_SESSION_TOKEN',
'TZ',
];
const AWS_RESERVED_VARS_INFO_URL =
'https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html#lambda-environment-variables';
const MOCK_DATA = {
HUBSPOT_ACCOUNT_ID: 123456,
HUBSPOT_CONTACT_IS_LOGGED_IN: true,
HUBSPOT_CONTACT_LIST_MEMBERSHIPS: '',
HUBSPOT_CONTACT_VID: 12345,
HUBSPOT_LIMITS_TIME_REMAINING: 600000,
HUBSPOT_LIMITS_EXECUTIONS_REMAINING: 60,
};
const MAX_SECRETS = 50;
const MAX_RUNTIME = 3000;
const MAX_REQ_BODY_SIZE = '50mb';
const ROUTE_PATH_PREFIX = '_hcms/api/';
const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];

module.exports = {
ALLOWED_METHODS,
AWS_RESERVED_VARS,
AWS_RESERVED_VARS_INFO_URL,
MAX_REQ_BODY_SIZE,
MAX_RUNTIME,
MAX_SECRETS,
MOCK_DATA,
ROUTE_PATH_PREFIX,
};
151 changes: 151 additions & 0 deletions lib/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const fs = require('fs-extra');
const path = require('path');
const { logger } = require('@hubspot/local-dev-lib/logger');
const { getCwd } = require('@hubspot/local-dev-lib/path');
const { getDotEnvData } = require('./secrets');
const { MOCK_DATA, MAX_SECRETS, ROUTE_PATH_PREFIX } = require('./constants');

const getValidatedFunctionData = functionPath => {
// Allow passing serverless folder path with and without .functions extension
const splitPath = functionPath.split('.');
const functionPathWithExtension =
splitPath[splitPath.length - 1] === 'functions'
? functionPath
: `${functionPath}.functions`;

const resolvedFunctionPath = path.resolve(
getCwd(),
functionPathWithExtension
);
if (!fs.existsSync(resolvedFunctionPath)) {
logger.error(`The path ${functionPath} does not exist.`);
return;
} else {
const stats = fs.lstatSync(resolvedFunctionPath);
if (!stats.isDirectory()) {
logger.error(`${functionPath} is not a valid functions directory.`);
return;
}
}

const { endpoints = [], environment = {}, secrets = [] } = JSON.parse(
fs.readFileSync(`${resolvedFunctionPath}/serverless.json`, {
encoding: 'utf-8',
})
);
const routes = Object.keys(endpoints);

if (!routes.length) {
logger.error(`No endpoints found in ${functionPath}/serverless.json.`);
return;
}

if (secrets.length > MAX_SECRETS) {
logger.warn(
`This function currently exceeds the limit of ${MAX_SECRETS} secrets. See https://developers.hubspot.com/docs/cms/features/serverless-functions#know-your-limits for more info.`
);
}

return {
srcPath: resolvedFunctionPath,
endpoints,
environment,
routes,
secrets,
};
};

const getHeaders = req => {
const reqHeaders = req.headers;

return {
Accept: reqHeaders.accept,
'Accept-Encoding': reqHeaders['accept-encoding'],
'Accept-Language': reqHeaders['accept-language'],
'Cache-Control': reqHeaders['cache-control'],
Connection: reqHeaders.connection,
Cookie: reqHeaders.cookie,
Host: reqHeaders.host,
'True-Client-IP': req.ip, // https://stackoverflow.com/a/14631683/3612910
'upgrade-insecure-requests': reqHeaders['upgrade-insecure-requests'],
'User-Agent': reqHeaders['user-agent'],
'X-Forwarded-For':
req.headers['x-forwarded-for'] || req.connection.remoteAddress, // https://stackoverflow.com/a/14631683/3612910
};
};

// This purposefully puts each param into an array to mimic the way production
// does it. This should be updated when production is fixed so params work as
// expected instead of always being an array.
// See https://git.hubteam.com/HubSpot/ContentServerlessFunctions/pull/228
const getRequestQueryParams = req => {
const paramsObj = {};

Object.keys(req.query).forEach(param => {
const currentValue = req.query[param];
const newValue = Array.isArray(currentValue)
? currentValue
: [currentValue];

paramsObj[param] = newValue;
});

return paramsObj;
};

const getFunctionDataContext = async (
req,
functionPath,
allowedSecrets,
accountId,
contact
) => {
const {
secrets,
mockData: {
HUBSPOT_ACCOUNT_ID,
HUBSPOT_CONTACT_IS_LOGGED_IN,
HUBSPOT_CONTACT_LIST_MEMBERSHIPS,
HUBSPOT_CONTACT_VID,
HUBSPOT_LIMITS_TIME_REMAINING,
HUBSPOT_LIMITS_EXECUTIONS_REMAINING,
},
} = getDotEnvData(functionPath, allowedSecrets);
const data = {
secrets,
params: getRequestQueryParams(req),
limits: {
timeRemaining:
HUBSPOT_LIMITS_TIME_REMAINING ||
MOCK_DATA.HUBSPOT_LIMITS_TIME_REMAINING,
executionsRemaining:
HUBSPOT_LIMITS_EXECUTIONS_REMAINING ||
MOCK_DATA.HUBSPOT_LIMITS_EXECUTIONS_REMAINING,
},
body: req.body,
headers: getHeaders(req),
method: req.method,
endpoint: req.url.replace(`/${ROUTE_PATH_PREFIX}`, ''),
accountId: accountId || HUBSPOT_ACCOUNT_ID || MOCK_DATA.HUBSPOT_ACCOUNT_ID,
contact:
contact === 'true' || contact === true
? {
vid: HUBSPOT_CONTACT_VID || MOCK_DATA.HUBSPOT_CONTACT_VID,
isLoggedIn:
HUBSPOT_CONTACT_IS_LOGGED_IN ||
MOCK_DATA.HUBSPOT_CONTACT_IS_LOGGED_IN,
listMemberships: (
HUBSPOT_CONTACT_LIST_MEMBERSHIPS ||
MOCK_DATA.HUBSPOT_CONTACT_LIST_MEMBERSHIPS
).split(','),
}
: null,
};

return data;
};

module.exports = {
getValidatedFunctionData,
getFunctionDataContext,
};
42 changes: 42 additions & 0 deletions lib/environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { logger } = require('@hubspot/local-dev-lib/logger');
const {
AWS_RESERVED_VARS,
AWS_RESERVED_VARS_INFO_URL,
} = require('./constants');

const loadEnvironmentVariables = (
globalEnvironment = {},
localEnvironment = {}
) => {
Object.keys(globalEnvironment).forEach(globalEnvironmentVariable => {
if (AWS_RESERVED_VARS.indexOf(globalEnvironmentVariable) !== -1) {
logger.warn(
`The variable ${globalEnvironmentVariable} is a reserved AWS variable and should not be used. See ${AWS_RESERVED_VARS_INFO_URL} for more info.`
);
}

logger.debug(
`Setting environment variable(global) ${globalEnvironmentVariable} to ${localEnvironment[globalEnvironmentVariable]}.`
);
process.env[globalEnvironmentVariable] =
globalEnvironment[globalEnvironmentVariable];
});

Object.keys(localEnvironment).forEach(localEnvironmentVariable => {
if (AWS_RESERVED_VARS.indexOf(localEnvironmentVariable) !== -1) {
logger.warn(
`The variable ${localEnvironmentVariable} is a reserved AWS variable and should not be used. See ${AWS_RESERVED_VARS_INFO_URL} for more info.`
);
}

logger.debug(
`Setting environment variable(local) ${localEnvironmentVariable} to ${localEnvironment[localEnvironmentVariable]}.`
);
process.env[localEnvironmentVariable] =
localEnvironment[localEnvironmentVariable];
});
};

module.exports = {
loadEnvironmentVariables,
};
Loading

0 comments on commit 4fc1a4c

Please sign in to comment.