zapier-platform-cli 14.1.2 → 15.0.1

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.
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const _ = require('lodash');
4
4
  const prettier = require('prettier');
5
5
  const semver = require('semver');
6
+ const traverse = require('traverse');
6
7
 
7
8
  const {
8
9
  PACKAGE_VERSION,
@@ -16,7 +17,8 @@ const { snakeCase } = require('./misc');
16
17
  const { getPackageLatestVersion } = require('./npm');
17
18
  let { startSpinner, endSpinner } = require('./display');
18
19
 
19
- const TEMPLATE_DIR = path.join(__dirname, '../../scaffold/convert');
20
+ const SCAFFOLD_TEMPLATE_DIR = path.join(__dirname, '../../scaffold');
21
+ const GENERATORS_TEMPLATE_DIR = path.join(__dirname, '../generators/templates');
20
22
 
21
23
  // A placeholder that can be used to identify this is something we need to replace
22
24
  // before generating the final code. See replacePlaceholders function. Make it really
@@ -29,10 +31,6 @@ const makePlaceholder = (replacement) => `${REPLACE_DIRECTIVE}${replacement}`;
29
31
  const replacePlaceholders = (str) =>
30
32
  str.replace(new RegExp(`"${REPLACE_DIRECTIVE}([^"]+)"`, 'g'), '$1');
31
33
 
32
- const quote = (s) => `'${s}'`;
33
-
34
- const escapeSpecialChars = (s) => s.replace(/\n/g, '\\n').replace(/'/g, "\\'");
35
-
36
34
  const createFile = async (content, filename, dir) => {
37
35
  const destFile = path.join(dir, filename);
38
36
  await ensureDir(path.dirname(destFile));
@@ -139,7 +137,7 @@ const renderPackageJson = async (appInfo, appDefinition) => {
139
137
  description,
140
138
  main: 'index.js',
141
139
  scripts: {
142
- test: 'mocha --recursive -t 10000',
140
+ test: 'jest --testTimeout 10000',
143
141
  },
144
142
  engines: {
145
143
  node: `>=${LAMBDA_VERSION}`,
@@ -147,8 +145,7 @@ const renderPackageJson = async (appInfo, appDefinition) => {
147
145
  },
148
146
  dependencies,
149
147
  devDependencies: {
150
- mocha: '^5.2.0',
151
- should: '^13.2.0',
148
+ jest: '^29.6.0',
152
149
  },
153
150
  private: true,
154
151
  zapier: zapierMeta,
@@ -157,171 +154,65 @@ const renderPackageJson = async (appInfo, appDefinition) => {
157
154
  return prettifyJSON(pkg);
158
155
  };
159
156
 
160
- const renderStep = (type, definition) => {
161
- let exportBlock = _.cloneDeep(definition);
162
- let functionBlock = [];
163
-
164
- ['perform', 'performList', 'performSubscribe', 'performUnsubscribe'].forEach(
165
- (funcName) => {
166
- const func = definition.operation[funcName];
167
- if (func && func.source) {
168
- const args = func.args || ['z', 'bundle'];
169
- functionBlock.push(
170
- `const ${funcName} = async (${args.join(', ')}) => {\n${
171
- func.source
172
- }\n};`
173
- );
174
-
175
- exportBlock.operation[funcName] = makePlaceholder(funcName);
157
+ const renderSource = (definition, functions = {}) => {
158
+ traverse(definition).forEach(function (source) {
159
+ if (this.key === 'source') {
160
+ const args = this.parent.node.args || ['z', 'bundle'];
161
+ // Find first parent that is not an array (applies to inputFields)
162
+ const funcNameBase = this.path
163
+ .slice(0, -1)
164
+ .reverse()
165
+ .find((key) => !/^\d+$/.test(key));
166
+ let funcName = funcNameBase;
167
+ let funcNum = 0;
168
+ while (functions[funcName]) {
169
+ funcNum++;
170
+ funcName = `${funcNameBase}${funcNum}`;
176
171
  }
177
- }
178
- );
172
+ functions[funcName] = `const ${funcName} = async (${args.join(
173
+ ', '
174
+ )}) => {\n${source}\n};`;
179
175
 
180
- ['inputFields', 'outputFields'].forEach((key) => {
181
- const fields = definition.operation[key];
182
- if (Array.isArray(fields) && fields.length > 0) {
183
- // Godzilla currently doesn't allow mutliple dynamic fields (see PDE-948) but when it does, this will account for it
184
- let funcNum = 0;
185
- fields.forEach((maybeFunc, index) => {
186
- if (maybeFunc.source) {
187
- const args = maybeFunc.args || ['z', 'bundle'];
188
- // always increment the number, but only return a value if it's > 0
189
- const funcName = `get${_.upperFirst(key)}${
190
- funcNum ? funcNum++ : ++funcNum && ''
191
- }`;
192
- functionBlock.push(
193
- `const ${funcName} = async (${args.join(', ')}) => {\n${
194
- maybeFunc.source
195
- }\n};`
196
- );
197
-
198
- exportBlock.operation[key][index] = makePlaceholder(funcName);
199
- }
200
- });
176
+ this.parent.update(makePlaceholder(funcName));
201
177
  }
202
178
  });
179
+ };
180
+
181
+ const renderDefinitionSlice = (definitionSlice) => {
182
+ let exportBlock = _.cloneDeep(definitionSlice);
183
+ let functionBlock = {};
184
+
185
+ renderSource(exportBlock, functionBlock);
203
186
 
204
187
  exportBlock = `module.exports = ${replacePlaceholders(
205
188
  JSON.stringify(exportBlock)
206
189
  )};\n`;
207
190
 
208
- functionBlock = functionBlock.join('\n\n');
191
+ functionBlock = Object.values(functionBlock).join('\n\n');
209
192
 
210
193
  return prettifyJs(functionBlock + '\n\n' + exportBlock);
211
194
  };
212
195
 
213
- // Render authData for test code
214
- const renderAuthData = (appDefinition) => {
215
- const fieldKeys = getAuthFieldKeys(appDefinition);
216
- const lines = _.map(fieldKeys, (key) => {
217
- const upperKey = _.snakeCase(key).toUpperCase();
218
- return `"${key}": process.env.${upperKey}`;
219
- });
220
- if (_.isEmpty(lines)) {
221
- return `{
222
- // TODO: Put your custom auth data here
223
- }`;
224
- }
225
- return '{' + lines.join(',\n') + '}';
226
- };
227
-
228
- const renderDefaultInputData = (definition) => {
229
- const lines = [];
230
-
231
- if (definition.inputFields) {
232
- definition.inputFields.forEach((field) => {
233
- if (field.default || field.required) {
234
- const defaultValue = field.default
235
- ? quote(escapeSpecialChars(field.default))
236
- : null;
237
- lines.push(`'${field.key}': ${defaultValue}`);
238
- }
239
- });
240
- }
241
-
242
- if (lines.length === 0) {
243
- return '{}';
244
- }
245
- return `{
246
- // TODO: Pulled from input fields' default values. Edit if necessary.
247
- ${lines.join(',\n')}
248
- }`;
249
- };
250
-
251
- const renderStepTest = async (stepType, definition, appDefinition) => {
252
- const templateName = {
253
- triggers: 'trigger-test.template.js',
254
- creates: 'create-test.template.js',
255
- searches: 'search-test.template.js',
256
- }[stepType];
257
-
196
+ const renderStepTest = async (stepType, definition) => {
258
197
  const templateContext = {
259
- key: definition.key,
260
- authData: renderAuthData(appDefinition),
261
- inputData: renderDefaultInputData(definition),
198
+ ACTION_PLURAL: stepType,
199
+ KEY: definition.key,
200
+ MAYBE_RESOURCE: '',
262
201
  };
263
202
 
264
- const templateFile = path.join(TEMPLATE_DIR, templateName);
203
+ const templateFile = path.join(SCAFFOLD_TEMPLATE_DIR, 'test.template.js');
265
204
  return renderTemplate(templateFile, templateContext);
266
205
  };
267
206
 
268
- const renderAuth = async (appDefinition) => {
269
- let exportBlock = _.cloneDeep(appDefinition.authentication);
270
- let functionBlock = [];
207
+ const renderAuth = async (appDefinition) =>
208
+ renderDefinitionSlice(appDefinition.authentication);
271
209
 
272
- _.each(
273
- {
274
- connectionLabel: 'getConnectionLabel',
275
- test: 'testAuth',
276
- },
277
- (funcName, key) => {
278
- const func = appDefinition.authentication[key];
279
- if (func && func.source) {
280
- const args = func.args || ['z', 'bundle'];
281
- functionBlock.push(
282
- `const ${funcName} = async (${args.join(', ')}) => {${func.source}};`
283
- );
284
-
285
- exportBlock[key] = makePlaceholder(funcName);
286
- }
287
- }
288
- );
289
-
290
- exportBlock = `module.exports = ${replacePlaceholders(
291
- JSON.stringify(exportBlock)
292
- )};\n`;
293
-
294
- functionBlock = functionBlock.join('\n\n');
295
-
296
- return prettifyJs(functionBlock + '\n\n' + exportBlock);
297
- };
298
-
299
- const renderHydrators = async (appDefinition) => {
300
- let exportBlock = _.cloneDeep(appDefinition.hydrators);
301
- let functionBlock = [];
302
-
303
- _.each(appDefinition.hydrators, (func, funcName) => {
304
- if (func && func.source) {
305
- const args = func.args || ['z', 'bundle'];
306
- functionBlock.push(
307
- `const ${funcName} = async (${args.join(', ')}) => {${func.source}};`
308
- );
309
- exportBlock[funcName] = makePlaceholder(funcName);
310
- }
311
- });
312
-
313
- exportBlock = `module.exports = ${replacePlaceholders(
314
- JSON.stringify(exportBlock)
315
- )};\n`;
316
-
317
- functionBlock = functionBlock.join('\n\n');
318
-
319
- return prettifyJs(functionBlock + '\n\n' + exportBlock);
320
- };
210
+ const renderHydrators = async (appDefinition) =>
211
+ renderDefinitionSlice(appDefinition.hydrators);
321
212
 
322
213
  const renderIndex = async (appDefinition) => {
323
214
  let exportBlock = _.cloneDeep(appDefinition);
324
- let functionBlock = [];
215
+ let functionBlock = {};
325
216
  let importBlock = [];
326
217
 
327
218
  // replace version and platformVersion with dynamic reference
@@ -360,22 +251,7 @@ const renderIndex = async (appDefinition) => {
360
251
  exportBlock.hydrators = makePlaceholder('hydrators');
361
252
  }
362
253
 
363
- ['beforeRequest', 'afterResponse'].forEach((middlewareType) => {
364
- const middlewares = appDefinition[middlewareType];
365
- if (middlewares && middlewares.length > 0) {
366
- // Backend converter always generates only one middleware
367
- const func = middlewares[0];
368
- if (func.source) {
369
- const args = func.args || ['z', 'bundle'];
370
- const funcName = middlewareType;
371
- functionBlock.push(
372
- `const ${funcName} = async (${args.join(', ')}) => {${func.source}};`
373
- );
374
-
375
- exportBlock[middlewareType][0] = makePlaceholder(funcName);
376
- }
377
- }
378
- });
254
+ renderSource(exportBlock, functionBlock);
379
255
 
380
256
  if (appDefinition.legacy && appDefinition.legacy.scriptingSource) {
381
257
  importBlock.push("\nconst fs = require('fs');");
@@ -390,7 +266,7 @@ const renderIndex = async (appDefinition) => {
390
266
  )};`;
391
267
 
392
268
  importBlock = importBlock.join('\n');
393
- functionBlock = functionBlock.join('\n\n');
269
+ functionBlock = Object.values(functionBlock).join('\n\n');
394
270
 
395
271
  return prettifyJs(
396
272
  importBlock + '\n\n' + functionBlock + '\n\n' + exportBlock
@@ -408,19 +284,13 @@ const renderEnvironment = (appDefinition) => {
408
284
 
409
285
  const writeStep = async (stepType, definition, key, newAppDir) => {
410
286
  const filename = `${stepType}/${snakeCase(key)}.js`;
411
- const content = await renderStep(stepType, definition);
287
+ const content = await renderDefinitionSlice(definition);
412
288
  await createFile(content, filename, newAppDir);
413
289
  };
414
290
 
415
- const writeStepTest = async (
416
- stepType,
417
- definition,
418
- key,
419
- appDefinition,
420
- newAppDir
421
- ) => {
422
- const filename = `test/${stepType}/${snakeCase(key)}.js`;
423
- const content = await renderStepTest(stepType, definition, appDefinition);
291
+ const writeStepTest = async (stepType, definition, key, newAppDir) => {
292
+ const filename = `test/${stepType}/${snakeCase(key)}.test.js`;
293
+ const content = await renderStepTest(stepType, definition);
424
294
  await createFile(content, filename, newAppDir);
425
295
  };
426
296
 
@@ -458,7 +328,7 @@ const writeEnvironment = async (appDefinition, newAppDir) => {
458
328
  };
459
329
 
460
330
  const writeGitIgnore = async (newAppDir) => {
461
- const srcPath = path.join(TEMPLATE_DIR, '/gitignore');
331
+ const srcPath = path.join(GENERATORS_TEMPLATE_DIR, '/gitignore');
462
332
  const destPath = path.join(newAppDir, '/.gitignore');
463
333
  await copyFile(srcPath, destPath);
464
334
  };
@@ -489,7 +359,7 @@ const convertApp = async (appInfo, appDefinition, newAppDir) => {
489
359
  _.each(appDefinition[stepType], (definition, key) => {
490
360
  promises.push(
491
361
  writeStep(stepType, definition, key, newAppDir),
492
- writeStepTest(stepType, definition, key, appDefinition, newAppDir)
362
+ writeStepTest(stepType, definition, key, newAppDir)
493
363
  );
494
364
  });
495
365
  });
@@ -0,0 +1,67 @@
1
+ const ACTION_TYPE_MAPPING = {
2
+ trigger: 'read',
3
+ create: 'write',
4
+ search: 'search',
5
+ };
6
+ const isValidActionType = (type) => type in ACTION_TYPE_MAPPING;
7
+
8
+ const bugfixMatches = new Set(['fix', 'fixed', 'fixes']);
9
+ const featureUpdateMatches = new Set([
10
+ 'add',
11
+ 'adds',
12
+ 'added',
13
+ 'improve',
14
+ 'improves',
15
+ 'improved',
16
+ 'improvement',
17
+ 'improvements',
18
+ 'update',
19
+ 'updates',
20
+ 'updated',
21
+ 'new',
22
+ ]);
23
+
24
+ const extractMetadata = (token, context) => {
25
+ if (!context) {
26
+ return;
27
+ }
28
+ const issueMetadata = token.match(/#(?<issueId>\d+)/);
29
+ if (Number.isInteger(Number(issueMetadata?.groups?.issueId))) {
30
+ return {
31
+ app_change_type: context,
32
+ issue_id: Number(issueMetadata?.groups?.issueId),
33
+ };
34
+ }
35
+ const appMetadata = token.match(
36
+ /(?<actionType>(trigger|create|search))\/(?<actionKey>\w+)/
37
+ );
38
+ if (
39
+ appMetadata?.groups?.actionKey &&
40
+ isValidActionType(appMetadata.groups.actionType)
41
+ ) {
42
+ return {
43
+ app_change_type: context,
44
+ action_key: appMetadata.groups.actionKey,
45
+ action_type: ACTION_TYPE_MAPPING[appMetadata.groups.actionType],
46
+ };
47
+ }
48
+ };
49
+
50
+ function* metadataWalker(line) {
51
+ let context;
52
+ for (const token of line.split(' ')) {
53
+ if (bugfixMatches.has(token.toLowerCase())) {
54
+ context = 'BUGFIX';
55
+ } else if (featureUpdateMatches.has(token.toLowerCase())) {
56
+ context = 'FEATURE_UPDATE';
57
+ }
58
+ const metadata = extractMetadata(token, context);
59
+ if (metadata) {
60
+ yield metadata;
61
+ }
62
+ }
63
+ }
64
+
65
+ const getMetadata = (content) => Array.from(metadataWalker(content));
66
+
67
+ module.exports = { getMetadata };
@@ -16,4 +16,5 @@ module.exports = [
16
16
  { nodeVersion: '14', npmVersion: '>=5.6.0' }, // 11.x
17
17
  { nodeVersion: '14', npmVersion: '>=5.6.0' }, // 12.x
18
18
  { nodeVersion: '16', npmVersion: '>=5.6.0' }, // 13.x
19
+ { nodeVersion: '18', npmVersion: '>=5.6.0' }, // 15.x
19
20
  ];
@@ -1,22 +0,0 @@
1
- require('should');
2
-
3
- const zapier = require('zapier-platform-core');
4
-
5
- const App = require('../../index');
6
- const appTester = zapier.createAppTester(App);
7
-
8
- describe('Create - <%= key %>', () => {
9
- zapier.tools.env.inject();
10
-
11
- it('should create an object', async () => {
12
- const bundle = {
13
- authData: <%= authData %>,
14
- <% if (inputData) { %>
15
- inputData: <%= inputData %>
16
- <% } %>
17
- };
18
-
19
- const result = await appTester(App.creates['<%= key %>'].operation.perform, bundle);
20
- result.should.not.be.an.Array();
21
- });
22
- });
@@ -1,62 +0,0 @@
1
- # Logs
2
- logs
3
- *.log
4
- npm-debug.log*
5
- yarn-debug.log*
6
- yarn-error.log*
7
-
8
- # Runtime data
9
- pids
10
- *.pid
11
- *.seed
12
- *.pid.lock
13
-
14
- # Directory for instrumented libs generated by jscoverage/JSCover
15
- lib-cov
16
-
17
- # Coverage directory used by tools like istanbul
18
- coverage
19
-
20
- # nyc test coverage
21
- .nyc_output
22
-
23
- # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24
- .grunt
25
-
26
- # Bower dependency directory (https://bower.io/)
27
- bower_components
28
-
29
- # node-waf configuration
30
- .lock-wscript
31
-
32
- # Compiled binary addons (https://nodejs.org/api/addons.html)
33
- build/
34
-
35
- # Dependency directories
36
- node_modules/
37
- jspm_packages/
38
-
39
- # Typescript v1 declaration files
40
- typings/
41
-
42
- # Optional npm cache directory
43
- .npm
44
-
45
- # Optional eslint cache
46
- .eslintcache
47
-
48
- # Optional REPL history
49
- .node_repl_history
50
-
51
- # Output of 'npm pack'
52
- *.tgz
53
-
54
- # Yarn Integrity file
55
- .yarn-integrity
56
-
57
- # environment variables file
58
- .env
59
- .environment
60
-
61
- # next.js build output
62
- .next
@@ -1,26 +0,0 @@
1
- {
2
- "name": "<%= name %>",
3
- "version": "1.0.0",
4
- "description": "<%= description %>",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "mocha --recursive -t 10000"
8
- },
9
- "engines": {
10
- "node": ">=10",
11
- "npm": ">=5.6.0"
12
- },
13
- "dependencies": {
14
- "zapier-platform-core": "<%= coreVersion %>",
15
- "zapier-platform-legacy-scripting-runner": "<%= runnerVersion %>"
16
- },
17
- "devDependencies": {
18
- "mocha": "^5.2.0",
19
- "should": "^13.2.0"
20
- },
21
- "private": true,
22
- "zapier": {
23
- "convertedFromAppID": <%= appId %>,
24
- "convertedByCLIVersion": "<%= cliVersion %>"
25
- }
26
- }
@@ -1,24 +0,0 @@
1
- require('should');
2
-
3
- const zapier = require('zapier-platform-core');
4
-
5
- const App = require('../../index');
6
- const appTester = zapier.createAppTester(App);
7
-
8
- describe('Search - <%= key %>', () => {
9
- zapier.tools.env.inject();
10
-
11
- it('should get an array', async () => {
12
- const bundle = {
13
- authData: <%= authData %>,
14
- <% if (inputData) { %>
15
- inputData: <%= inputData %>
16
- <% } %>
17
- };
18
-
19
- const results = await appTester(App.searches['<%= key %>'].operation.perform, bundle);
20
- results.should.be.an.Array();
21
- results.length.should.be.aboveOrEqual(1);
22
- results[0].should.have.property('id');
23
- });
24
- });
@@ -1,22 +0,0 @@
1
- require('should');
2
-
3
- const zapier = require('zapier-platform-core');
4
-
5
- const App = require('../../index');
6
- const appTester = zapier.createAppTester(App);
7
-
8
- describe('Trigger - <%= key %>', () => {
9
- zapier.tools.env.inject();
10
-
11
- it('should get an array', async () => {
12
- const bundle = {
13
- authData: <%= authData %>,
14
- <% if (inputData) { %>
15
- inputData: <%= inputData %>
16
- <% } %>
17
- };
18
-
19
- const results = await appTester(App.triggers['<%= key %>'].operation.perform, bundle);
20
- results.should.be.an.Array();
21
- });
22
- });