zapier-platform-cli 15.18.1 → 16.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.
Files changed (73) hide show
  1. package/oclif.manifest.json +2316 -1
  2. package/package.json +43 -33
  3. package/scaffold/create.template.ts +64 -0
  4. package/scaffold/resource.template.ts +119 -0
  5. package/scaffold/search.template.ts +63 -0
  6. package/scaffold/test.template.ts +18 -0
  7. package/scaffold/trigger.template.ts +58 -0
  8. package/src/bin/run +4 -4
  9. package/src/bin/run.cmd +0 -3
  10. package/src/generators/index.js +11 -11
  11. package/src/index.js +1 -1
  12. package/src/oclif/ZapierBaseCommand.js +51 -44
  13. package/src/oclif/buildFlags.js +14 -16
  14. package/src/oclif/commands/analytics.js +6 -6
  15. package/src/oclif/commands/build.js +6 -6
  16. package/src/oclif/commands/cache/clear.js +13 -13
  17. package/src/oclif/commands/canary/create.js +27 -20
  18. package/src/oclif/commands/canary/delete.js +26 -16
  19. package/src/oclif/commands/canary/list.js +5 -7
  20. package/src/oclif/commands/convert.js +16 -16
  21. package/src/oclif/commands/delete/version.js +6 -6
  22. package/src/oclif/commands/deprecate.js +10 -11
  23. package/src/oclif/commands/describe.js +5 -5
  24. package/src/oclif/commands/env/get.js +5 -5
  25. package/src/oclif/commands/env/set.js +11 -12
  26. package/src/oclif/commands/env/unset.js +9 -10
  27. package/src/oclif/commands/init.js +12 -13
  28. package/src/oclif/commands/invoke.js +67 -69
  29. package/src/oclif/commands/jobs.js +1 -1
  30. package/src/oclif/commands/link.js +2 -2
  31. package/src/oclif/commands/login.js +15 -15
  32. package/src/oclif/commands/logout.js +1 -1
  33. package/src/oclif/commands/logs.js +9 -9
  34. package/src/oclif/commands/migrate.js +19 -22
  35. package/src/oclif/commands/promote.js +25 -27
  36. package/src/oclif/commands/push.js +2 -2
  37. package/src/oclif/commands/register.js +31 -32
  38. package/src/oclif/commands/scaffold.js +112 -106
  39. package/src/oclif/commands/team/add.js +12 -15
  40. package/src/oclif/commands/team/get.js +2 -2
  41. package/src/oclif/commands/team/remove.js +6 -6
  42. package/src/oclif/commands/test.js +8 -8
  43. package/src/oclif/commands/upload.js +1 -1
  44. package/src/oclif/commands/users/add.js +9 -11
  45. package/src/oclif/commands/users/get.js +7 -7
  46. package/src/oclif/commands/users/links.js +4 -4
  47. package/src/oclif/commands/users/remove.js +8 -9
  48. package/src/oclif/commands/validate.js +29 -21
  49. package/src/oclif/commands/versions.js +26 -1
  50. package/src/oclif/hooks/checkValidNodeVersion.js +1 -1
  51. package/src/oclif/hooks/deprecated.js +1 -1
  52. package/src/oclif/hooks/getAppRegistrationFieldChoices.js +4 -4
  53. package/src/oclif/hooks/renderMarkdownHelp.js +1 -2
  54. package/src/oclif/hooks/versionInfo.js +2 -2
  55. package/src/utils/analytics.js +4 -4
  56. package/src/utils/api.js +26 -29
  57. package/src/utils/ast.js +112 -11
  58. package/src/utils/auth-files-codegen.js +102 -99
  59. package/src/utils/build.js +27 -28
  60. package/src/utils/changelog.js +1 -1
  61. package/src/utils/check-missing-app-info.js +2 -2
  62. package/src/utils/convert.js +26 -20
  63. package/src/utils/credentials.js +1 -1
  64. package/src/utils/display.js +31 -8
  65. package/src/utils/files.js +3 -3
  66. package/src/utils/ignore.js +2 -2
  67. package/src/utils/local.js +1 -1
  68. package/src/utils/metadata.js +1 -1
  69. package/src/utils/misc.js +21 -22
  70. package/src/utils/promisify.js +1 -1
  71. package/src/utils/scaffold.js +293 -40
  72. package/src/utils/team.js +3 -3
  73. package/src/utils/xdg.js +3 -3
@@ -1,16 +1,33 @@
1
+ // @ts-check
2
+
1
3
  const path = require('path');
2
4
  const colors = require('colors/safe');
3
5
  const _ = require('lodash');
4
6
 
5
7
  const { ensureDir, fileExistsSync, readFile, writeFile } = require('./files');
6
8
  const { splitFileFromPath } = require('./string');
7
- const { createRootRequire, addKeyToPropertyOnApp } = require('./ast');
8
- const { snakeCase } = require('./misc');
9
+ const {
10
+ importActionInJsApp,
11
+ registerActionInJsApp,
12
+ importActionInTsApp,
13
+ registerActionInTsApp,
14
+ } = require('./ast');
9
15
 
10
16
  const plural = (type) => (type === 'search' ? `${type}es` : `${type}s`);
11
17
 
12
- const getTemplatePath = (actionType) =>
13
- path.join(__dirname, '..', '..', 'scaffold', `${actionType}.template.js`);
18
+ /**
19
+ * @param {TemplateType} templateType
20
+ * @param {'js' | 'ts'} language
21
+ * @returns {string}
22
+ */
23
+ const getTemplatePath = (templateType, language = 'js') =>
24
+ path.join(
25
+ __dirname,
26
+ '..',
27
+ '..',
28
+ 'scaffold',
29
+ `${templateType}.template.${language}`,
30
+ );
14
31
 
15
32
  // useful for making sure we don't conflict with other, similarly named things
16
33
  const variablePrefixes = {
@@ -22,34 +39,64 @@ const variablePrefixes = {
22
39
  const getVariableName = (action, noun) =>
23
40
  action === 'resource'
24
41
  ? [noun, 'resource'].join(' ')
25
- : [variablePrefixes[action], noun];
42
+ : [variablePrefixes[action], noun].join(' ');
43
+
44
+ /**
45
+ * Produce a valid snake_case key from one or more nouns, and fix the
46
+ * inconsistent version numbers that come from _.snakeCase.
47
+ *
48
+ * @example
49
+ * nounToKey('Cool Contact V10') // cool_contact_v10
50
+ */
51
+ const nounToKey = (noun) => _.snakeCase(noun).replace(/V_(\d+)$/gi, 'v$1');
26
52
 
27
- const createTemplateContext = (action, noun, includeComments) => {
53
+ /**
54
+ * Create a context object to pass to the template
55
+ * @param {Object} options
56
+ * @param {ActionType} options.actionType - the action type
57
+ * @param {string} options.noun - the noun for the action
58
+ * @param {boolean} [options.includeIntroComments] - whether to include comments in the template
59
+ * @returns {TemplateContext}
60
+ */
61
+ const createTemplateContext = ({
62
+ actionType,
63
+ noun,
64
+ includeIntroComments = false,
65
+ }) => {
28
66
  // if noun is "Cool Contact"
29
67
  return {
30
- ACTION: action, // trigger
31
- ACTION_PLURAL: plural(action), // triggers
68
+ ACTION: actionType, // trigger
69
+ ACTION_PLURAL: plural(actionType), // triggers
32
70
 
33
- VARIABLE: _.camelCase(getVariableName(action, noun)), // getContact, the variable that's imported
34
- KEY: snakeCase(noun), // "cool_contact", the action key
71
+ VARIABLE: _.camelCase(getVariableName(actionType, noun)), // getContact, the variable that's imported
72
+ KEY: nounToKey(noun), // "cool_contact", the action key
35
73
  NOUN: noun
36
74
  .split(' ')
37
75
  .map((s) => _.capitalize(s))
38
76
  .join(' '), // "Cool Contact", the noun
39
77
  LOWER_NOUN: noun.toLowerCase(), // "cool contact", for use in comments
40
78
  // resources need an extra line for tests to "just run"
41
- MAYBE_RESOURCE: action === 'resource' ? 'list.' : '',
42
- INCLUDE_INTRO_COMMENTS: includeComments,
79
+ MAYBE_RESOURCE: actionType === 'resource' ? 'list.' : '',
80
+ INCLUDE_INTRO_COMMENTS: includeIntroComments,
43
81
  };
44
82
  };
45
83
 
46
- const writeTemplateFile = async (
47
- actionType,
48
- templateContext,
84
+ /**
85
+ * @param {Object} options
86
+ * @param {TemplateType} options.templateType - the template to write
87
+ * @param {'js' | 'ts'} options.language - the language of the project
88
+ * @param {string} options.destinationPath - where to write the file
89
+ * @param {boolean} options.preventOverwrite - whether to prevent overwriting
90
+ * @param {TemplateContext} options.templateContext - the context for the template
91
+ */
92
+ const writeTemplateFile = async ({
93
+ templateType,
94
+ language,
49
95
  destinationPath,
50
- preventOverwrite
51
- ) => {
52
- const templatePath = getTemplatePath(actionType);
96
+ preventOverwrite,
97
+ templateContext,
98
+ }) => {
99
+ const templatePath = getTemplatePath(templateType, language);
53
100
 
54
101
  if (preventOverwrite && fileExistsSync(destinationPath)) {
55
102
  const [location, filename] = splitFileFromPath(destinationPath);
@@ -57,15 +104,15 @@ const writeTemplateFile = async (
57
104
  throw new Error(
58
105
  [
59
106
  `File ${colors.bold(filename)} already exists within ${colors.bold(
60
- location
107
+ location,
61
108
  )}.`,
62
109
  'You can either:',
63
110
  ' 1. Choose a different filename',
64
111
  ` 2. Delete ${filename} from ${location}`,
65
112
  ` 3. Run ${colors.italic('scaffold')} with ${colors.bold(
66
- '--force'
113
+ '--force',
67
114
  )} to overwrite the current ${filename}`,
68
- ].join('\n')
115
+ ].join('\n'),
69
116
  );
70
117
  }
71
118
 
@@ -79,42 +126,248 @@ const writeTemplateFile = async (
79
126
  const getRelativeRequirePath = (entryFilePath, newFilePath) =>
80
127
  path.relative(path.dirname(entryFilePath), newFilePath);
81
128
 
129
+ const isValidEntryFileUpdate = (
130
+ language,
131
+ indexFileResolved,
132
+ actionType,
133
+ newActionKey,
134
+ ) => {
135
+ if (language === 'js') {
136
+ // ensure a clean access
137
+ delete require.cache[require.resolve(indexFileResolved)];
138
+ // this line fails if `npm install` hasn't been run, since core isn't present yet.
139
+ const rewrittenIndex = require(indexFileResolved);
140
+ return Boolean(_.get(rewrittenIndex, [plural(actionType), newActionKey]));
141
+ }
142
+ return true;
143
+ };
144
+
82
145
  /**
83
- * performs a series of updates to a file at a path.
146
+ * Modify an index.js/index.ts file to import and reference the newly
147
+ * scaffolded action.
84
148
  *
85
- * returns the original file contents in case a revert is needed
149
+ * @param {Object} options
150
+ * @param {'ts'|'js'} options.language - the language of the project
151
+ * @param {string} options.indexFileResolved - the App's entry point (index.js/ts)
152
+ * @param {string} options.actionRelativeImportPath - The path to import the new action with
153
+ * @param {string} options.actionImportName - the name of the import, i.e the action key converted to camel_case
154
+ * @param {ActionType} options.actionType - The type of action, e.g. 'trigger'
86
155
  */
87
- const updateEntryFile = async (
88
- entryFilePath,
89
- varName,
90
- newFilePath,
156
+ const updateEntryFile = async ({
157
+ language,
158
+ indexFileResolved,
159
+ actionRelativeImportPath,
160
+ actionImportName,
91
161
  actionType,
92
- newActionKey
93
- ) => {
94
- let codeStr = (await readFile(entryFilePath)).toString();
162
+ }) => {
163
+ if (language === 'ts') {
164
+ return updateEntryFileTs({
165
+ indexFileResolved,
166
+ actionRelativeImportPath,
167
+ actionImportName,
168
+ actionType,
169
+ });
170
+ }
171
+ return updateEntryFileJs({
172
+ indexFileResolved,
173
+ actionRelativeImportPath,
174
+ actionImportName,
175
+ actionType,
176
+ });
177
+ };
178
+
179
+ /**
180
+ *
181
+ * @param {Object} options
182
+ * @param {string} options.indexFileResolved - the App's entry point (index.js/ts)
183
+ * @param {string} options.actionRelativeImportPath - The path to import the new action with
184
+ * @param {string} options.actionImportName - the name of the import, i.e the action key converted to camel_case
185
+ * @param {ActionType} options.actionType - The type of action, e.g. 'trigger'
186
+ */
187
+ const updateEntryFileJs = async ({
188
+ indexFileResolved,
189
+ actionRelativeImportPath,
190
+ actionImportName,
191
+ actionType,
192
+ }) => {
193
+ let codeStr = (await readFile(indexFileResolved)).toString();
194
+ const originalCodeStr = codeStr; // untouched copy in case we need to bail
195
+
196
+ codeStr = importActionInJsApp(
197
+ codeStr,
198
+ actionImportName,
199
+ actionRelativeImportPath,
200
+ );
201
+ codeStr = registerActionInJsApp(
202
+ codeStr,
203
+ plural(actionType),
204
+ actionImportName,
205
+ );
206
+ await writeFile(indexFileResolved, codeStr);
207
+ return originalCodeStr;
208
+ };
209
+
210
+ /**
211
+ *
212
+ * @param {Object} options
213
+ * @param {string} options.indexFileResolved - The App's entry point (index.js/ts)
214
+ * @param {string} options.actionRelativeImportPath - The path to import the new action with (relative to the index)
215
+ * @param {string} options.actionImportName - The name of the import, i.e the action key converted to camel_case
216
+ * @param {ActionType} options.actionType - The type of action, e.g. 'trigger'
217
+ */
218
+ const updateEntryFileTs = async ({
219
+ indexFileResolved,
220
+ actionRelativeImportPath,
221
+ actionImportName,
222
+ actionType,
223
+ }) => {
224
+ let codeStr = (await readFile(indexFileResolved)).toString();
95
225
  const originalCodeStr = codeStr; // untouched copy in case we need to bail
96
- const relativePath = getRelativeRequirePath(entryFilePath, newFilePath);
97
226
 
98
- codeStr = createRootRequire(codeStr, varName, `./${relativePath}`);
99
- codeStr = addKeyToPropertyOnApp(codeStr, plural(actionType), varName);
100
- await writeFile(entryFilePath, codeStr);
227
+ codeStr = importActionInTsApp(
228
+ codeStr,
229
+ actionImportName,
230
+ actionRelativeImportPath,
231
+ );
232
+ codeStr = registerActionInTsApp(
233
+ codeStr,
234
+ plural(actionType),
235
+ actionImportName,
236
+ );
237
+ await writeFile(indexFileResolved, codeStr);
101
238
  return originalCodeStr;
102
239
  };
103
240
 
104
- const isValidEntryFileUpdate = (entryFilePath, actionType, newActionKey) => {
105
- // ensure a clean access
106
- delete require.cache[require.resolve(entryFilePath)];
241
+ /**
242
+ *
243
+ * Prepare everything needed to define what's happening in a scaffolding
244
+ * operation.
245
+ *
246
+ * @param {Object} options
247
+ * @param {ActionType} options.actionType - the action type
248
+ * @param {string} options.noun - the noun for the action
249
+ * @param {'js' | 'ts'} options.language - the language of the project
250
+ * @param {string} options.indexFileLocal - the App's entry point (index.js/ts)
251
+ * @param {string} options.actionDirLocal - where to put the new action
252
+ * @param {string} options.testDirLocal - where to put the new action's test
253
+ * @param {boolean} options.includeIntroComments - whether to include comments in the template
254
+ * @param {boolean} options.preventOverwrite - whether to force overwrite
255
+ *
256
+ * @returns {ScaffoldContext}
257
+ */
258
+ const createScaffoldingContext = ({
259
+ actionType,
260
+ noun,
261
+ language,
262
+ indexFileLocal,
263
+ actionDirLocal,
264
+ testDirLocal,
265
+ includeIntroComments,
266
+ preventOverwrite,
267
+ }) => {
268
+ const key = nounToKey(noun);
269
+ const cwd = process.cwd();
270
+ const indexFileResolved = path.join(cwd, indexFileLocal);
271
+ const actionFileResolved = `${path.join(
272
+ cwd,
273
+ actionDirLocal,
274
+ key,
275
+ )}.${language}`;
276
+ const actionFileResolvedStem = path.join(cwd, actionDirLocal, key);
277
+ const actionFileLocal = `${path.join(actionDirLocal, key)}.${language}`;
278
+ const actionFileLocalStem = path.join(actionDirLocal, key);
279
+ const testFileResolved = `${path.join(
280
+ cwd,
281
+ testDirLocal,
282
+ key,
283
+ )}.test.${language}`;
284
+ const testFileLocal = `${path.join(testDirLocal, key)}.${language}`;
285
+ const testFileLocalStem = path.join(testDirLocal, key);
286
+ const actionRelativeImportPath = `./${getRelativeRequirePath(
287
+ indexFileResolved,
288
+ actionFileResolvedStem,
289
+ )}`;
290
+
291
+ return {
292
+ actionType,
293
+ actionTypePlural: plural(actionType),
294
+ noun,
295
+ preventOverwrite,
296
+ language,
297
+ templateContext: createTemplateContext({
298
+ actionType,
299
+ noun,
300
+ includeIntroComments,
301
+ }),
302
+
303
+ indexFileLocal,
304
+ indexFileResolved,
305
+ actionRelativeImportPath,
306
+
307
+ actionFileResolved,
308
+ actionFileResolvedStem,
309
+ actionFileLocal,
310
+ actionFileLocalStem,
107
311
 
108
- // this line fails if `npm install` hasn't been run, since core isn't present yet.
109
- const rewrittenIndex = require(entryFilePath);
110
- return Boolean(_.get(rewrittenIndex, [plural(actionType), newActionKey]));
312
+ testFileResolved,
313
+ testFileLocal,
314
+ testFileLocalStem,
315
+ };
111
316
  };
112
317
 
113
318
  module.exports = {
319
+ createScaffoldingContext,
114
320
  createTemplateContext,
115
321
  getRelativeRequirePath,
116
322
  plural,
323
+ nounToKey,
117
324
  updateEntryFile,
118
325
  isValidEntryFileUpdate,
119
326
  writeTemplateFile,
120
327
  };
328
+
329
+ /**
330
+ * The varieties of actions that can be generated.
331
+ * @typedef {'create' | 'resource' | 'search' | 'trigger'} ActionType
332
+ */
333
+ /**
334
+ * The types of templates that can be made, including "test" files.
335
+ * @typedef { ActionType | 'test' } TemplateType
336
+ */
337
+
338
+ /**
339
+ * @typedef {Object} TemplateContext
340
+ * @property {string} ACTION - the action type
341
+ * @property {string} ACTION_PLURAL - the plural of the action type
342
+ * @property {string} VARIABLE - the variable that's imported
343
+ * @property {string} KEY - the action key
344
+ * @property {string} NOUN - the noun
345
+ * @property {string} LOWER_NOUN - the noun in lowercase
346
+ * @property {string} MAYBE_RESOURCE - an extra line for resources
347
+ * @property {boolean} INCLUDE_INTRO_COMMENTS - whether to include comments
348
+ */
349
+
350
+ /**
351
+ * Everything needed to define a scaffolding operation.
352
+ *
353
+ * @typedef {Object} ScaffoldContext
354
+ * @property {ActionType} actionType - the type of action being created
355
+ * @property {string} actionTypePlural - plural of the template type, e.g. "triggers".
356
+ * @property {string} noun - the noun for the action
357
+ * @property {'js' | 'ts'} language - the language of the project
358
+ * @property {boolean} preventOverwrite - whether to prevent overwriting
359
+ * @property {TemplateContext} templateContext - the context for templates
360
+ *
361
+ * @property {string} indexFileLocal - e.g. `index.js` or `src/index.ts`
362
+ * @property {string} indexFileResolved - e.g. `/Users/sal/my-app/index.js`
363
+ *
364
+ * @property {string} actionFileResolved - e.g. `/Users/sal/my-app/triggers/foobar.js`
365
+ * @property {string} actionFileResolvedStem - e.g. `/Users/sal/my-app/triggers/foobar`
366
+ * @property {string} actionFileLocal - e.g. `triggers/foobar.js`
367
+ * @property {string} actionFileLocalStem - e.g. `triggers/foobar`
368
+ * @property {string} actionRelativeImportPath - e.g. `triggers/foobar`
369
+ *
370
+ * @property {string} testFileResolved - e.g. `/Users/sal/my-app/test/triggers/foobar.test.js`
371
+ * @property {string} testFileLocal - e.g. `test/triggers/foobar.js`
372
+ * @property {string} testFileLocalStem - e.g. `test/triggers/foobar`
373
+ */
package/src/utils/team.js CHANGED
@@ -10,8 +10,8 @@ const transformUserRole = (role) =>
10
10
  role === 'collaborator'
11
11
  ? 'admin'
12
12
  : role === 'subscriber'
13
- ? 'subscriber'
14
- : 'collaborator';
13
+ ? 'subscriber'
14
+ : 'collaborator';
15
15
 
16
16
  const listTeamMembers = async () => {
17
17
  return listEndpointMulti(
@@ -24,7 +24,7 @@ const listTeamMembers = async () => {
24
24
  endpoint: (app) =>
25
25
  `${constants.BASE_ENDPOINT}/api/platform/v3/integrations/${app.id}/subscribers`,
26
26
  keyOverride: 'subscribers',
27
- }
27
+ },
28
28
  );
29
29
  };
30
30
  module.exports = {
package/src/utils/xdg.js CHANGED
@@ -38,17 +38,17 @@ if (process.platform === 'win32') {
38
38
  ensureDataDir = ensureDir.bind(
39
39
  null,
40
40
  'XDG_DATA_HOME',
41
- path.join(HOME_DIR, '.local', 'share')
41
+ path.join(HOME_DIR, '.local', 'share'),
42
42
  );
43
43
  ensureCacheDir = ensureDir.bind(
44
44
  null,
45
45
  'XDG_CACHE_HOME',
46
- path.join(HOME_DIR, '.cache')
46
+ path.join(HOME_DIR, '.cache'),
47
47
  );
48
48
  ensureConfigDir = ensureDir.bind(
49
49
  null,
50
50
  'XDG_CONFIG_HOME',
51
- path.join(HOME_DIR, '.config')
51
+ path.join(HOME_DIR, '.config'),
52
52
  );
53
53
  }
54
54