zapier-platform-core 16.5.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ // not intended to be loaded via require() or import() - copied during build step
2
+ import zapier from 'zapier-platform-core';
3
+
4
+ let _appRaw;
5
+ try {
6
+ _appRaw = await import('{REPLACE_ME_PACKAGE_NAME}');
7
+ } catch (err) {
8
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
9
+ err.message +=
10
+ '\nMake sure you specify a valid entry point using `exports` in package.json.';
11
+ }
12
+ throw err;
13
+ }
14
+
15
+ // Allows a developer to use named exports or default export in entry point
16
+ if (_appRaw && _appRaw.default) {
17
+ _appRaw = _appRaw.default;
18
+ }
19
+
20
+ export const appRaw = _appRaw;
21
+ export const handler = zapier.createAppHandler(_appRaw);
package/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const zapier = require('./src');
2
2
  zapier.version = require('./package.json').version;
3
- zapier.Promise = zapier.ZapierPromise = require('./src/tools/promise');
4
3
  zapier.tools = require('./src/tools/exported');
5
4
  module.exports = zapier;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-core",
3
- "version": "16.5.0",
3
+ "version": "17.0.0",
4
4
  "description": "The core SDK for CLI apps in the Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -19,7 +19,7 @@
19
19
  "version": "node bin/bump-dependencies.js && yarn && git add package.json yarn.lock",
20
20
  "postversion": "git push && git push --tags",
21
21
  "main-tests": "mocha -t 20s --recursive test --exit",
22
- "type-tests": "tsd",
22
+ "type-tests": "tsd --files types/**/*.test-d.ts",
23
23
  "solo-test": "test $(OPT_OUT_PATCH_TEST_ONLY=yes mocha --recursive test -g 'should be able to opt out of patch' -R json | jq '.stats.passes') -eq 1 && echo 'Ran 1 test and it passed!'",
24
24
  "test": "yarn main-tests && yarn solo-test && yarn type-tests",
25
25
  "test:debug": "mocha inspect -t 10s --recursive test",
@@ -43,25 +43,24 @@
43
43
  "engineStrict": true,
44
44
  "dependencies": {
45
45
  "@zapier/secret-scrubber": "^1.1.2",
46
- "bluebird": "3.7.2",
47
46
  "content-disposition": "0.5.4",
48
- "dotenv": "16.4.6",
47
+ "dotenv": "16.5.0",
49
48
  "form-data": "4.0.1",
50
49
  "lodash": "4.17.21",
51
50
  "mime-types": "2.1.35",
52
51
  "node-abort-controller": "3.1.1",
53
52
  "node-fetch": "2.7.0",
54
53
  "oauth-sign": "0.9.0",
55
- "semver": "7.6.3",
56
- "zapier-platform-schema": "16.5.0"
54
+ "semver": "7.7.1",
55
+ "zapier-platform-schema": "17.0.0"
57
56
  },
58
57
  "devDependencies": {
59
58
  "@types/node-fetch": "^2.6.11",
60
59
  "adm-zip": "0.5.16",
61
60
  "aws-sdk": "^2.1397.0",
62
61
  "dicer": "^0.3.1",
63
- "fs-extra": "^11.1.1",
64
- "mock-fs": "^5.3.0",
62
+ "fs-extra": "^11.3.0",
63
+ "mock-fs": "^5.5.0",
65
64
  "nock": "^13.5.4",
66
65
  "tsd": "^0.31.1"
67
66
  },
@@ -1,12 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const ZapierPromise = require('../../tools/promise');
4
-
5
3
  /*
6
4
  After app middlewares that waits for all pending promises to resolve.
7
5
  */
8
6
  const waitForPromises = (output) => {
9
- return ZapierPromise.all(output.input._zapier.promises || [])
7
+ return Promise.all(output.input._zapier.promises || [])
10
8
  .catch(() => {}) // drop any errors in waiting promises
11
9
  .then(() => output);
12
10
  };
@@ -12,12 +12,13 @@ const logSafeBundle = (bundle) => {
12
12
  */
13
13
  const addAppContext = (input) => {
14
14
  const methodName = _.get(input, '_zapier.event.method');
15
+ input._zapier.whatHappened.push(`Executing ${methodName} with bundle`);
16
+
15
17
  const bundle = _.get(input, '_zapier.event.bundle', {});
18
+ if (Object.keys(bundle).length > 0) {
19
+ input._zapier.whatHappened.push(JSON.stringify(logSafeBundle(bundle)));
20
+ }
16
21
 
17
- input._addContext(
18
- `Executing ${methodName} with bundle`,
19
- JSON.stringify(logSafeBundle(bundle)),
20
- );
21
22
  return input;
22
23
  };
23
24
 
package/src/execute.js CHANGED
@@ -6,7 +6,6 @@ const addQueryParams = require('./http-middlewares/before/add-query-params');
6
6
  const ensureArray = require('./tools/ensure-array');
7
7
  const injectInput = require('./http-middlewares/before/inject-input');
8
8
  const prepareRequest = require('./http-middlewares/before/prepare-request');
9
- const ZapierPromise = require('./tools/promise');
10
9
 
11
10
  const constants = require('./constants');
12
11
 
@@ -36,7 +35,7 @@ const executeHttpRequest = (input, options) => {
36
35
  const executeInputOutputFields = (inputOutputFields, input) => {
37
36
  inputOutputFields = ensureArray(inputOutputFields);
38
37
 
39
- return ZapierPromise.all(
38
+ return Promise.all(
40
39
  inputOutputFields.map((field) =>
41
40
  _.isFunction(field) ? field(input.z, input.bundle) : field,
42
41
  ),
@@ -44,7 +43,7 @@ const executeInputOutputFields = (inputOutputFields, input) => {
44
43
  };
45
44
 
46
45
  const executeCallbackMethod = (z, bundle, method) => {
47
- return new ZapierPromise((resolve, reject) => {
46
+ return new Promise((resolve, reject) => {
48
47
  const callback = (err, output) => {
49
48
  if (err) {
50
49
  reject(err);
@@ -65,8 +65,9 @@ const logResponse = (resp) => {
65
65
  infoMsg += ` after ${logs.data.request_duration_ms}ms`;
66
66
  }
67
67
 
68
- resp._addContext(infoMsg);
69
- resp._addContext(
68
+ const whatHappened = resp.request.input._zapier.whatHappened;
69
+ whatHappened.push(infoMsg);
70
+ whatHappened.push(
70
71
  `Received content "${String(logs.data.response_content).substr(0, 100)}"`,
71
72
  );
72
73
 
@@ -14,7 +14,9 @@ const addQueryParams = (req) => {
14
14
 
15
15
  normalizeEmptyParamFields(req);
16
16
 
17
- let stringifiedParams = querystring.stringify(req.params);
17
+ let stringifiedParams = querystring.stringify(req.params, '&', '=', {
18
+ encodeURIComponent: req.encodeURIComponent,
19
+ });
18
20
 
19
21
  // it goes against spec, but for compatibility, some APIs want certain
20
22
  // characters (mostly $) unencoded
@@ -102,6 +102,29 @@ const finalRequest = (req) => {
102
102
  }
103
103
  };
104
104
 
105
+ const throwForCurlies = (value, path) => {
106
+ path = path || [];
107
+ if (typeof value === 'string') {
108
+ if (/{{\s*(bundle|process)\.[^}]*}}/.test(value)) {
109
+ throw new Error(
110
+ 'z.request() no longer supports {{bundle.*}} or {{process.*}} as of v17 ' +
111
+ "unless it's used in a shorthand request. " +
112
+ 'Use JavaScript template literals instead. ' +
113
+ `Value in violation: "${value}" in attribute "${path.join('.')}".`,
114
+ );
115
+ }
116
+ } else if (Array.isArray(value)) {
117
+ for (let i = 0; i < value.length; i++) {
118
+ const item = value[i];
119
+ throwForCurlies(item, [...path, String(i)]);
120
+ }
121
+ } else if (_.isPlainObject(value)) {
122
+ for (const [k, v] of Object.entries(value)) {
123
+ throwForCurlies(v, [...path, k]);
124
+ }
125
+ }
126
+ };
127
+
105
128
  const prepareRequest = function (req) {
106
129
  const input = req.input || {};
107
130
 
@@ -112,39 +135,53 @@ const prepareRequest = function (req) {
112
135
  params: false,
113
136
  body: false,
114
137
  },
115
- replace: true, // always replace curlies
116
138
  // read default from app flags, but always defer to the request object if the value was set
117
139
  skipThrowForStatus: _.get(
118
140
  input,
119
141
  ['_zapier', 'app', 'flags', 'skipThrowForStatus'],
120
142
  false,
121
143
  ),
122
- _addContext: () => {},
123
144
  });
124
145
 
125
146
  req = sugarBody(req);
126
147
 
127
148
  // apply app requestTemplate to request
128
149
  if (req.merge) {
129
- const requestTemplate = (input._zapier.app || {}).requestTemplate;
150
+ const requestTemplate = (input._zapier?.app || {}).requestTemplate;
130
151
  req = requestMerge(requestTemplate, req);
131
152
  }
132
153
 
133
- // replace {{curlies}} in the request
154
+ const replaceable = {
155
+ url: req.url,
156
+ headers: req.headers,
157
+ params: req.params,
158
+ body: req.body,
159
+ };
160
+
134
161
  if (req.replace) {
162
+ // replace {{curlies}} in the request
135
163
  const bank = createBundleBank(
136
164
  input._zapier.app,
137
165
  input._zapier.event,
138
166
  req.serializeValueForCurlies,
139
167
  );
140
- req = recurseReplaceBank(req, bank);
168
+ req = {
169
+ ...req,
170
+ ...recurseReplaceBank(replaceable, bank),
171
+ };
172
+ } else {
173
+ // throw if there's {{curlies}} in the request
174
+ throwForCurlies(replaceable);
141
175
  }
142
176
 
143
177
  req = coerceBody(req);
144
178
 
145
179
  req._requestStart = new Date();
146
180
 
147
- req._addContext(`Starting ${req.method} request to ${req.url}`);
181
+ const whatHappened = req.input._zapier.whatHappened;
182
+ if (whatHappened) {
183
+ whatHappened.push(`Starting ${req.method} request to ${req.url}`);
184
+ }
148
185
 
149
186
  return finalRequest(req);
150
187
  };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ // Middleware to trim whitespace from header keys and values
4
+ const sanitizeHeaders = (req) => {
5
+ req.headers = Object.fromEntries(
6
+ Object.entries(req.headers || {}).map(([key, value]) => [
7
+ key.trim(),
8
+ typeof value === 'string' ? value.trim() : value,
9
+ ]),
10
+ );
11
+ return req;
12
+ };
13
+
14
+ module.exports = sanitizeHeaders;
package/src/index.js CHANGED
@@ -15,4 +15,5 @@ module.exports = {
15
15
  createAppHandler: createLambdaHandler,
16
16
  createAppTester,
17
17
  integrationTestHandler,
18
+ ...require('./typeHelpers'),
18
19
  };
package/src/middleware.js CHANGED
@@ -3,7 +3,6 @@
3
3
  const _ = require('lodash');
4
4
 
5
5
  const envelope = require('./tools/envelope');
6
- const ZapierPromise = require('./tools/promise');
7
6
 
8
7
  /**
9
8
  Applies before and after middleware functions, returning
@@ -40,6 +39,17 @@ const ZapierPromise = require('./tools/promise');
40
39
  output. The default is false.
41
40
  */
42
41
 
42
+ const enrichErrorMessages = (error, input) => {
43
+ if (error.doNotContextify) {
44
+ throw error;
45
+ }
46
+ if (input._zapier && input._zapier.whatHappened) {
47
+ const details = input._zapier.whatHappened.map((f) => ` ${f}`).join('\n');
48
+ error.message = `${error.message}\nWhat happened:\n${details}\n ${error.message}`;
49
+ }
50
+ throw error;
51
+ };
52
+
43
53
  const applyMiddleware = (befores, afters, app, options) => {
44
54
  options = _.defaults({}, options, {
45
55
  skipEnvelope: false,
@@ -55,47 +65,46 @@ const applyMiddleware = (befores, afters, app, options) => {
55
65
  };
56
66
 
57
67
  return (input) => {
58
- const context = ZapierPromise.makeContext();
59
- const resolve = (val) => ZapierPromise.resolve(val).bind(context);
60
-
61
- const beforeMiddleware = (beforeInput) => {
62
- return befores.reduce((collector, func) => {
63
- return collector.then((newInput) => {
64
- newInput._addContext = context.addContext;
65
- const args = [newInput].concat(options.extraArgs);
66
- const result = func.apply(undefined, args);
67
- if (typeof result !== 'object') {
68
- throw new Error('Middleware should return an object.');
69
- }
70
- return result;
71
- });
72
- }, resolve(beforeInput));
68
+ const beforeMiddleware = async (beforeInput) => {
69
+ let newInput = beforeInput;
70
+ for (const func of befores) {
71
+ const args = [newInput].concat(options.extraArgs);
72
+ const maybePromise = func.apply(undefined, args);
73
+ // legacy scripting runner returns a Promise for beforeRequest
74
+ if (typeof maybePromise !== 'object') {
75
+ throw new Error('Middleware should return an object.');
76
+ }
77
+ newInput = await Promise.resolve(maybePromise);
78
+ }
79
+ return newInput;
73
80
  };
74
81
 
75
- const afterMiddleware = (output) => {
76
- return afters.reduce((collector, func) => {
77
- return collector.then((newOutput) => {
78
- newOutput._addContext = context.addContext;
79
- const args = [newOutput].concat(options.extraArgs);
80
- const maybePromise = func.apply(undefined, args);
81
- if (typeof maybePromise !== 'object') {
82
- throw new Error('Middleware should return an object.');
83
- }
84
- return resolve(maybePromise).then(ensureEnvelope);
85
- });
86
- }, resolve(output));
82
+ const afterMiddleware = async (output) => {
83
+ for (const func of afters) {
84
+ const args = [output].concat(options.extraArgs);
85
+ const maybePromise = func.apply(undefined, args);
86
+ if (typeof maybePromise !== 'object') {
87
+ throw new Error('Middleware should return an object.');
88
+ }
89
+ output = await Promise.resolve(maybePromise);
90
+ output = ensureEnvelope(output);
91
+ }
92
+ return output;
87
93
  };
88
94
 
89
- const promise = beforeMiddleware(input).then((newInput) => {
90
- return resolve(app(newInput))
91
- .then(ensureEnvelope)
92
- .then((output) => {
93
- output.input = newInput;
94
- return afterMiddleware(output);
95
- });
96
- });
95
+ const promise = async (input) => {
96
+ const newInput = await beforeMiddleware(input);
97
+ try {
98
+ let output = await app(newInput);
99
+ output = await ensureEnvelope(output);
100
+ output.input = newInput;
101
+ return afterMiddleware(output);
102
+ } catch (error) {
103
+ return enrichErrorMessages(error, newInput);
104
+ }
105
+ };
97
106
 
98
- return promise;
107
+ return promise(input);
99
108
  };
100
109
  };
101
110
 
@@ -14,6 +14,7 @@ const createInjectInputMiddleware = require('../http-middlewares/before/inject-i
14
14
  const disableSSLCertCheck = require('../http-middlewares/before/disable-ssl-cert-check');
15
15
  const oauth1SignRequest = require('../http-middlewares/before/oauth1-sign-request');
16
16
  const prepareRequest = require('../http-middlewares/before/prepare-request');
17
+ const sanitizeHeaders = require('../http-middlewares/before/sanatize-headers');
17
18
 
18
19
  // after middles
19
20
  const { logResponse } = require('../http-middlewares/after/log-response');
@@ -45,7 +46,7 @@ const createAppRequestClient = (input, options) => {
45
46
  httpBefores.push(oauth1SignRequest);
46
47
  }
47
48
  }
48
-
49
+ httpBefores.push(sanitizeHeaders);
49
50
  httpBefores.push(addQueryParams);
50
51
 
51
52
  const verifySSL = _.get(input, '_zapier.event.verifySSL');
@@ -2,30 +2,13 @@
2
2
 
3
3
  const createLambdaHandler = require('./create-lambda-handler');
4
4
  const resolveMethodPath = require('./resolve-method-path');
5
- const ZapierPromise = require('./promise');
6
5
  const { isFunction } = require('lodash');
7
6
  const { genId } = require('./data');
8
7
  const { shouldPaginate } = require('./should-paginate');
9
8
 
10
- // Convert a app handler to promise for convenience.
11
- const promisifyHandler = (handler) => {
12
- return (event) => {
13
- return new ZapierPromise((resolve, reject) => {
14
- handler(event, {}, (err, resp) => {
15
- if (err) {
16
- reject(err);
17
- } else {
18
- resolve(resp);
19
- }
20
- });
21
- });
22
- };
23
- };
24
-
25
9
  // A shorthand compatible wrapper for testing.
26
10
  const createAppTester = (appRaw, { customStoreKey } = {}) => {
27
11
  const handler = createLambdaHandler(appRaw);
28
- const createHandlerPromise = promisifyHandler(handler);
29
12
 
30
13
  const randomSeed = genId();
31
14
 
@@ -73,7 +56,7 @@ const createAppTester = (appRaw, { customStoreKey } = {}) => {
73
56
  event.detailedLogToStdout = true;
74
57
  }
75
58
 
76
- return createHandlerPromise(event).then((resp) => {
59
+ return handler(event).then((resp) => {
77
60
  delete appRaw._testRequest; // clear adHocFunc so tests can't affect each other
78
61
  return resp.results;
79
62
  });
@@ -59,6 +59,9 @@ const createHttpPatch = (event) => {
59
59
 
60
60
  // Only include request or response data for specific content types
61
61
  // which we are able to read in logs and which are not typically too large
62
+ // Limitation: This doesn't capture the request content-type if it's set afterwards, like:
63
+ // const req = https.request(options, callback);
64
+ // req.setHeader('Content-Type', 'text/plain');
62
65
  const requestContentType = getContentType(options.headers || {});
63
66
  const responseContentType = getContentType(response.headers || {});
64
67
 
@@ -35,6 +35,8 @@ const createInput = (app, event, logger, logBuffer, rpc) => {
35
35
  logger,
36
36
 
37
37
  logBuffer,
38
+
39
+ whatHappened: [],
38
40
  },
39
41
  };
40
42
  };