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,5 +1,5 @@
1
1
  const colors = require('colors/safe');
2
- const { flags } = require('@oclif/command');
2
+ const { Flags } = require('@oclif/core');
3
3
 
4
4
  const BaseCommand = require('../ZapierBaseCommand');
5
5
  const { buildFlags } = require('../buildFlags');
@@ -29,7 +29,7 @@ class ValidateCommand extends BaseCommand {
29
29
 
30
30
  if (newErrors.length) {
31
31
  this.log(
32
- 'Your integration is structurally invalid. Address concerns and run this command again.'
32
+ 'Your integration is structurally invalid. Address concerns and run this command again.',
33
33
  );
34
34
  process.exitCode = 1;
35
35
  } else {
@@ -41,8 +41,8 @@ class ValidateCommand extends BaseCommand {
41
41
  if (process.exitCode === 1) {
42
42
  this.log(
43
43
  colors.grey(
44
- '\nSkipping integration checks because schema did not validate.'
45
- )
44
+ '\nSkipping integration checks because schema did not validate.',
45
+ ),
46
46
  );
47
47
  }
48
48
  return;
@@ -85,24 +85,32 @@ class ValidateCommand extends BaseCommand {
85
85
  const suggestionDisplay = checkResult.suggestions.display_label;
86
86
 
87
87
  if (checkIssues.length) {
88
- this.logList([
89
- [
90
- `- ${colors.bold(errorDisplay)}`,
91
- 'Issues that will prevent your integration from functioning ' +
92
- 'properly. They block you from pushing.',
88
+ this.logTable({
89
+ headers: [
90
+ ['', 'type'],
91
+ ['', 'description'],
93
92
  ],
94
- [
95
- `- ${colors.bold(warningDisplay)}`,
96
- 'To-dos that must be addressed before your integration can be ' +
97
- 'included in the App Directory. They block you from promoting and ' +
98
- 'publishing.',
93
+ rows: [
94
+ {
95
+ type: `- ${colors.bold(errorDisplay)}`,
96
+ description:
97
+ 'Issues that will prevent your integration from functioning properly. They block you from pushing.',
98
+ },
99
+ {
100
+ type: `- ${colors.bold(warningDisplay)}`,
101
+ description:
102
+ 'To-dos that must be addressed before your integration can be included in the App Directory. They block you from promoting and publishing.',
103
+ },
104
+ {
105
+ type: `- ${colors.bold(suggestionDisplay)}`,
106
+ description:
107
+ "Issues and recommendations that need human reviews by Zapier before publishing your integration. They don't block.",
108
+ },
99
109
  ],
100
- [
101
- `- ${colors.bold(suggestionDisplay)}`,
102
- 'Issues and recommendations that need human reviews by Zapier before ' +
103
- "publishing your integration. They don't block.",
104
- ],
105
- ]);
110
+ hasBorder: false,
111
+ showHeaders: false,
112
+ style: { head: [], 'padding-left': 0, 'padding-right': 0 },
113
+ });
106
114
  }
107
115
  this.log();
108
116
  }
@@ -110,7 +118,7 @@ class ValidateCommand extends BaseCommand {
110
118
 
111
119
  ValidateCommand.flags = buildFlags({
112
120
  commandFlags: {
113
- 'without-style': flags.boolean({
121
+ 'without-style': Flags.boolean({
114
122
  description: 'Forgo pinging the Zapier server to run further checks.',
115
123
  }),
116
124
  },
@@ -1,3 +1,5 @@
1
+ const colors = require('colors/safe');
2
+
1
3
  const BaseCommand = require('../ZapierBaseCommand');
2
4
  const { buildFlags } = require('../buildFlags');
3
5
 
@@ -23,9 +25,32 @@ class VersionCommand extends BaseCommand {
23
25
  'No versions to show. Try adding one with the `zapier push` command',
24
26
  });
25
27
 
28
+ this.logTable({
29
+ headers: [],
30
+ rows: [
31
+ {
32
+ version: `- ${colors.bold('Errors')}`,
33
+ platform_version:
34
+ 'Issues that will prevent your integration from functioning properly. They block you from pushing.',
35
+ },
36
+ {
37
+ version: `- ${colors.bold('Publishing Tasks')}`,
38
+ platform_version:
39
+ 'To-dos that must be addressed before your integration can be included in the App Directory. They block you from promoting and publishing.',
40
+ },
41
+ {
42
+ version: `- ${colors.bold('Warnings')}`,
43
+ platform_version:
44
+ "Issues and recommendations that need human reviews by Zapier before publishing your integration. They don't block.",
45
+ },
46
+ ],
47
+ hasBorder: false,
48
+ style: { head: [], 'padding-left': 0, 'padding-right': 0 },
49
+ });
50
+
26
51
  if (versions.map((v) => v.user_count).filter((c) => c === null).length) {
27
52
  this.warn(
28
- 'Some user counts are still being calculated - run this command again in ~10 seconds (or longer if your integration has lots of users).'
53
+ 'Some user counts are still being calculated - run this command again in ~10 seconds (or longer if your integration has lots of users).',
29
54
  );
30
55
  }
31
56
  }
@@ -5,7 +5,7 @@ const { LAMBDA_VERSION } = require('../../constants');
5
5
  module.exports = function () {
6
6
  if (!isValidNodeVersion()) {
7
7
  this.error(
8
- `Requires node version >= ${LAMBDA_VERSION}, found ${process.versions.node}. Please upgrade Node.js.`
8
+ `Requires node version >= ${LAMBDA_VERSION}, found ${process.versions.node}. Please upgrade Node.js.`,
9
9
  );
10
10
  }
11
11
  };
@@ -11,7 +11,7 @@ module.exports = function (options) {
11
11
  this.warn(
12
12
  `The \`${options.id}\` command is deprecated. Use the \`${
13
13
  deprecatedCommands[options.id]
14
- }\` command instead.`
14
+ }\` command instead.`,
15
15
  );
16
16
  console.log();
17
17
  }
@@ -13,7 +13,7 @@ module.exports = async function (options) {
13
13
  formFields = await callAPI('/apps/fields-choices', { skipDeployKey: true });
14
14
  } catch (e) {
15
15
  this.error(
16
- `Unable to connect to Zapier API. Please check your connection and try again. ${e}`
16
+ `Unable to connect to Zapier API. Please check your connection and try again. ${e}`,
17
17
  );
18
18
  }
19
19
 
@@ -28,17 +28,17 @@ module.exports = async function (options) {
28
28
  if (cmd && cmd.flags) {
29
29
  if (cmd.flags.audience) {
30
30
  cmd.flags.audience.options = formFields.intention.map(
31
- (audienceOption) => audienceOption.value
31
+ (audienceOption) => audienceOption.value,
32
32
  );
33
33
  }
34
34
  if (cmd.flags.role) {
35
35
  cmd.flags.role.options = formFields.role.map(
36
- (roleOption) => roleOption.value
36
+ (roleOption) => roleOption.value,
37
37
  );
38
38
  }
39
39
  if (cmd.flags.category) {
40
40
  cmd.flags.category.options = formFields.app_category.map(
41
- (categoryOption) => categoryOption.value
41
+ (categoryOption) => categoryOption.value,
42
42
  );
43
43
  }
44
44
  }
@@ -1,12 +1,11 @@
1
1
  const chalk = require('chalk');
2
2
  const { marked } = require('marked');
3
3
  const TerminalRenderer = require('marked-terminal');
4
- const { stdtermwidth } = require('@oclif/help/lib/screen');
5
4
 
6
5
  marked.setOptions({
7
6
  renderer: new TerminalRenderer({
8
7
  tab: 2,
9
- width: stdtermwidth - 2,
8
+ width: process.stdout.getWindowSize()[0] - 2,
10
9
  reflowText: true,
11
10
  codespan: chalk.underline.bold,
12
11
  }),
@@ -18,7 +18,7 @@ module.exports = (options) => {
18
18
  `* CLI version: ${options.config.version}`,
19
19
  `* Node.js version: ${process.version}`,
20
20
  `* OS info: ${options.config.platform}-${options.config.arch}`,
21
- ].join('\n')
21
+ ].join('\n'),
22
22
  );
23
23
 
24
24
  try {
@@ -28,7 +28,7 @@ module.exports = (options) => {
28
28
  const maybeCoreDepVersion = get(pJson, ['dependencies', PLATFORM_PACKAGE]);
29
29
  if (maybeCoreDepVersion) {
30
30
  console.log(
31
- `* \`${PLATFORM_PACKAGE}\` dependency: ${maybeCoreDepVersion}`
31
+ `* \`${PLATFORM_PACKAGE}\` dependency: ${maybeCoreDepVersion}`,
32
32
  );
33
33
  }
34
34
  } catch {}
@@ -20,7 +20,7 @@ const shouldSkipAnalytics = (mode) =>
20
20
  process.env.DISABLE_ZAPIER_ANALYTICS ||
21
21
  mode === ANALYTICS_MODES.disabled;
22
22
 
23
- const recordAnalytics = async (command, isValidCommand, args, flags) => {
23
+ const recordAnalytics = async (command, isValidCommand, argNames, flags) => {
24
24
  const analyticsMode = await currentAnalyticsMode();
25
25
 
26
26
  if (shouldSkipAnalytics(analyticsMode)) {
@@ -34,10 +34,10 @@ const recordAnalytics = async (command, isValidCommand, args, flags) => {
34
34
  const analyticsBody = {
35
35
  command,
36
36
  isValidCommand,
37
- numArgs: args.length,
37
+ numArgs: argNames.length,
38
38
  flags: {
39
39
  ...flags,
40
- ...(command === 'help' ? { helpCommand: args[0] } : {}), // include the beginning of args so we know what they want help on
40
+ ...(command === 'help' ? { helpCommand: argNames[0] } : {}), // include the beginning of args so we know what they want help on
41
41
  },
42
42
  cliVersion: pkg.version,
43
43
  os: shouldRecordAnonymously ? undefined : process.platform,
@@ -54,7 +54,7 @@ const recordAnalytics = async (command, isValidCommand, args, flags) => {
54
54
  skipDeployKey: shouldRecordAnonymously,
55
55
  },
56
56
  true,
57
- false
57
+ false,
58
58
  )
59
59
  .then(({ success }) => debug('success:', success))
60
60
  .catch(({ errText }) => debug('err:', errText));
package/src/utils/api.js CHANGED
@@ -32,7 +32,7 @@ const readCredentials = (explodeIfMissing = true) => {
32
32
  return Promise.resolve(
33
33
  readFile(
34
34
  constants.AUTH_LOCATION,
35
- `Please run \`${colors.cyan('zapier login')}\`.`
35
+ `Please run \`${colors.cyan('zapier login')}\`.`,
36
36
  )
37
37
  .then((buf) => {
38
38
  return JSON.parse(buf.toString());
@@ -43,7 +43,7 @@ const readCredentials = (explodeIfMissing = true) => {
43
43
  } else {
44
44
  return {};
45
45
  }
46
- })
46
+ }),
47
47
  );
48
48
  }
49
49
  };
@@ -54,7 +54,7 @@ const callAPI = async (
54
54
  options,
55
55
  rawError = false,
56
56
  credentialsRequired = true,
57
- returnStreamBody = false
57
+ returnStreamBody = false,
58
58
  ) => {
59
59
  // temp manual enable while we're not all the way moved over
60
60
  if (_.get(global, ['argOpts', 'debug'])) {
@@ -149,7 +149,7 @@ const createCredentials = (username, password, totpCode) => {
149
149
  },
150
150
  },
151
151
  // if totp is empty, we want a raw request so we can supress an error. If it's here, we want it to be "non-raw"
152
- !totpCode
152
+ !totpCode,
153
153
  );
154
154
  };
155
155
 
@@ -162,22 +162,19 @@ const createCanary = async (versionFrom, versionTo, percent, duration) => {
162
162
  method: 'POST',
163
163
  body: {
164
164
  percent,
165
- duration
166
- }
167
- }
168
- )
169
- }
165
+ duration,
166
+ },
167
+ },
168
+ );
169
+ };
170
170
 
171
171
  const listCanaries = async () => {
172
172
  const linkedAppId = (await getLinkedAppConfig(undefined, true))?.id;
173
173
 
174
- return callAPI(
175
- `/apps/${linkedAppId}/canaries`,
176
- {
177
- method: 'GET',
178
- }
179
- )
180
- }
174
+ return callAPI(`/apps/${linkedAppId}/canaries`, {
175
+ method: 'GET',
176
+ });
177
+ };
181
178
 
182
179
  const deleteCanary = async (versionFrom, versionTo) => {
183
180
  const linkedAppId = (await getLinkedAppConfig(undefined, true))?.id;
@@ -186,9 +183,9 @@ const deleteCanary = async (versionFrom, versionTo) => {
186
183
  `/apps/${linkedAppId}/versions/${versionFrom}/canary-to/${versionTo}`,
187
184
  {
188
185
  method: 'DELETE',
189
- }
190
- )
191
- }
186
+ },
187
+ );
188
+ };
192
189
 
193
190
  /**
194
191
  * read local `apprc` file
@@ -242,10 +239,10 @@ const getWritableApp = async () => {
242
239
  if (!linkedAppConfig.id) {
243
240
  throw new Error(
244
241
  `This project hasn't yet been associated with an existing Zapier integration.\n\nIf it's a brand new integration, run \`${colors.cyan(
245
- 'zapier register'
242
+ 'zapier register',
246
243
  )}\`.\n\nIf this project already exists in your Zapier account, run \`${colors.cyan(
247
- 'zapier link'
248
- )}\` instead.`
244
+ 'zapier link',
245
+ )}\` instead.`,
249
246
  );
250
247
  }
251
248
 
@@ -261,7 +258,7 @@ const getWritableApp = async () => {
261
258
  throw new Error(
262
259
  `Your credentials are present, but invalid${
263
260
  process.env.ZAPIER_BASE_ENDPOINT ? ' in this environment' : ''
264
- }. Please run \`${colors.cyan('zapier login')}\` to resolve.`
261
+ }. Please run \`${colors.cyan('zapier login')}\` to resolve.`,
265
262
  );
266
263
  } else if (errOrRejectedResponse.status === 404) {
267
264
  // if this fails, we know the issue is they can't see this app
@@ -272,9 +269,9 @@ const getWritableApp = async () => {
272
269
  }). Try running \`${colors.cyan('zapier link')}\` to correct that.${
273
270
  process.env.ZAPIER_BASE_ENDPOINT
274
271
  ? `\n\nFor local dev: make sure you've run \`${colors.cyan(
275
- 'zapier login'
272
+ 'zapier login',
276
273
  )}\` and \`${colors.cyan(
277
- 'zapier register'
274
+ 'zapier register',
278
275
  )}\` while providing ZAPIER_BASE_ENDPOINT.`
279
276
  : ''
280
277
  }`;
@@ -399,10 +396,10 @@ const downloadSourceZip = async (dst) => {
399
396
  if (!linkedAppConfig.id) {
400
397
  throw new Error(
401
398
  `This project hasn't yet been associated with an existing Zapier integration.\n\nIf it's a brand new integration, run \`${colors.cyan(
402
- 'zapier register'
399
+ 'zapier register',
403
400
  )}\`.\n\nIf this project already exists in your Zapier account, run \`${colors.cyan(
404
- 'zapier link'
405
- )}\` instead.`
401
+ 'zapier link',
402
+ )}\` instead.`,
406
403
  );
407
404
  }
408
405
 
@@ -434,7 +431,7 @@ const upload = async (app, { skipValidation = false } = {}) => {
434
431
 
435
432
  if (!fs.existsSync(fullZipPath)) {
436
433
  throw new Error(
437
- 'Missing a built integration. Try running `zapier build` first.\nAlternatively, run `zapier push`, which will build and upload in one command.'
434
+ 'Missing a built integration. Try running `zapier build` first.\nAlternatively, run `zapier push`, which will build and upload in one command.',
438
435
  );
439
436
  }
440
437
 
package/src/utils/ast.js CHANGED
@@ -1,6 +1,9 @@
1
- // tools for modifiyng an AST
1
+ // @ts-check
2
+
3
+ // tools for modifying an AST
2
4
 
3
5
  const j = require('jscodeshift');
6
+ const ts = j.withParser('ts');
4
7
 
5
8
  // simple helper functions used for searching for nodes
6
9
  // can't use j.identifier(name) because it has extra properties and we have to have no extras to find nodes
@@ -21,7 +24,7 @@ const typeHelpers = {
21
24
  /**
22
25
  * adds a `const verName = require(path)` to the root of a codeStr
23
26
  */
24
- const createRootRequire = (codeStr, varName, path) => {
27
+ const importActionInJsApp = (codeStr, varName, path) => {
25
28
  if (codeStr.match(new RegExp(`${varName} ?= ?require`))) {
26
29
  // duplicate identifier, no need to re-add
27
30
  // this would fail if they used this variable name for something else; we'd keep going and double-declare that variable
@@ -42,7 +45,7 @@ const createRootRequire = (codeStr, varName, path) => {
42
45
  const newRequireStatement = j.variableDeclaration('const', [
43
46
  j.variableDeclarator(
44
47
  j.identifier(varName),
45
- j.callExpression(j.identifier('require'), [j.literal(path)])
48
+ j.callExpression(j.identifier('require'), [j.literal(path)]),
46
49
  ),
47
50
  ]);
48
51
 
@@ -61,7 +64,7 @@ const createRootRequire = (codeStr, varName, path) => {
61
64
  return root.toSource();
62
65
  };
63
66
 
64
- const addKeyToPropertyOnApp = (codeStr, property, varName) => {
67
+ const registerActionInJsApp = (codeStr, property, varName) => {
65
68
  // to play with this, use https://astexplorer.net/#/gist/cb4986b3f1c6eb975339608109a48e7d/0fbf2fabbcf27d0b6ebd8910f979bd5d97dd9404
66
69
 
67
70
  const root = j(codeStr);
@@ -77,13 +80,13 @@ const addKeyToPropertyOnApp = (codeStr, property, varName) => {
77
80
  const exportAssignment = root.find(j.AssignmentExpression, {
78
81
  left: typeHelpers.memberExpression(
79
82
  typeHelpers.identifier('module'),
80
- typeHelpers.identifier('exports')
83
+ typeHelpers.identifier('exports'),
81
84
  ),
82
85
  });
83
86
 
84
87
  if (!exportAssignment.length) {
85
88
  throw new Error(
86
- 'Nothing is exported from this file; unable to find an object to modify'
89
+ 'Nothing is exported from this file; unable to find an object to modify',
87
90
  );
88
91
  }
89
92
 
@@ -112,14 +115,14 @@ const addKeyToPropertyOnApp = (codeStr, property, varName) => {
112
115
 
113
116
  // check if this object already has the property at the top level
114
117
  const existingProp = objToModify.properties.find(
115
- (props) => props.key.name === property
118
+ (props) => props.key.name === property,
116
119
  );
117
120
  if (existingProp) {
118
121
  // `triggers: myTriggers` means we shouldn't bother
119
122
  const value = existingProp.value;
120
123
  if (value.type !== 'ObjectExpression') {
121
124
  throw new Error(
122
- `Tried to edit the ${property} key, but the value wasn't an object`
125
+ `Tried to edit the ${property} key, but the value wasn't an object`,
123
126
  );
124
127
  }
125
128
  value.properties.push(newProperty);
@@ -128,12 +131,110 @@ const addKeyToPropertyOnApp = (codeStr, property, varName) => {
128
131
  j.property(
129
132
  'init',
130
133
  j.identifier(property),
131
- j.objectExpression([newProperty])
132
- )
134
+ j.objectExpression([newProperty]),
135
+ ),
133
136
  );
134
137
  }
135
138
 
136
139
  return root.toSource();
137
140
  };
138
141
 
139
- module.exports = { createRootRequire, addKeyToPropertyOnApp };
142
+ /**
143
+ * Adds an import statement to the top of an index.ts file to import a
144
+ * new action, such as `import some_trigger from './triggers/some_trigger';`
145
+ *
146
+ * @param {string} codeStr - The code of the index.ts file to modify.
147
+ * @param {string} identifierName - The name of imported action used as a variable in the code.
148
+ * @param {string} actionRelativeImportPath - The relative path to import the action from
149
+ * @returns {string}
150
+ */
151
+ const importActionInTsApp = (
152
+ codeStr,
153
+ identifierName,
154
+ actionRelativeImportPath,
155
+ ) => {
156
+ const root = ts(codeStr);
157
+
158
+ const imports = root.find(ts.ImportDeclaration);
159
+
160
+ const newImportStatement = j.importDeclaration(
161
+ [j.importDefaultSpecifier(j.identifier(identifierName))],
162
+ j.literal(actionRelativeImportPath),
163
+ );
164
+
165
+ if (imports.length) {
166
+ imports.at(-1).insertAfter(newImportStatement);
167
+ } else {
168
+ const body = root.find(ts.Program).get().node.body;
169
+ body.unshift(newImportStatement);
170
+ // Add newline after import?
171
+ }
172
+
173
+ return root.toSource({ quote: 'single' });
174
+ };
175
+
176
+ /**
177
+ *
178
+ * @param {string} codeStr
179
+ * @param {'creates' | 'searches' | 'triggers'} actionTypePlural - The type of action to register within the app
180
+ * @param {string} identifierName - Name of the action imported to be registered
181
+ * @returns {string}
182
+ */
183
+ const registerActionInTsApp = (codeStr, actionTypePlural, identifierName) => {
184
+ const root = ts(codeStr);
185
+
186
+ // the `[thing.key]: thing` entry we'd like to insert.
187
+ const newProperty = ts.property.from({
188
+ kind: 'init',
189
+ key: j.memberExpression(j.identifier(identifierName), j.identifier('key')),
190
+ value: j.identifier(identifierName),
191
+ computed: true,
192
+ });
193
+
194
+ // Find the top level app Object; the one with the `platformVersion`
195
+ // key. This is where we'll insert our new property.
196
+ const appObjectCandidates = root
197
+ .find(ts.ObjectExpression)
198
+ .filter((path) =>
199
+ path.value.properties.some(
200
+ (prop) => prop.key && prop.key.name === 'platformVersion',
201
+ ),
202
+ );
203
+ if (appObjectCandidates.length !== 1) {
204
+ throw new Error('Unable to find the app definition to modify');
205
+ }
206
+ const appObj = appObjectCandidates.get().node;
207
+
208
+ // Now we have an app object to modify.
209
+
210
+ // Check if this object already has the actionType group inside it.
211
+ const existingProp = appObj.properties.find(
212
+ (props) => props.key.name === actionTypePlural,
213
+ );
214
+ if (existingProp) {
215
+ const value = existingProp.value;
216
+ if (value.type !== 'ObjectExpression') {
217
+ throw new Error(
218
+ `Tried to edit the ${actionTypePlural} key, but the value wasn't an object`,
219
+ );
220
+ }
221
+ value.properties.push(newProperty);
222
+ } else {
223
+ appObj.properties.push(
224
+ j.property(
225
+ 'init',
226
+ j.identifier(actionTypePlural),
227
+ j.objectExpression([newProperty]),
228
+ ),
229
+ );
230
+ }
231
+
232
+ return root.toSource({ quote: 'single' });
233
+ };
234
+
235
+ module.exports = {
236
+ importActionInJsApp,
237
+ registerActionInJsApp,
238
+ importActionInTsApp,
239
+ registerActionInTsApp,
240
+ };