zapier-platform-cli 16.5.1 → 17.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.
@@ -55,7 +55,7 @@
55
55
  "build": {
56
56
  "aliases": [],
57
57
  "args": {},
58
- "description": "Build a pushable zip from the current directory.\n\nThis command does the following:\n\n* Creates a temporary folder\n* Copies all code into the temporary folder\n* Adds an entry point: `zapierwrapper.js`\n* Generates and validates app definition.\n* Detects dependencies via browserify (optional, on by default)\n* Zips up all needed `.js` files. If you want to include more files, add a \"includeInBuild\" property (array with strings of regexp paths) to your `.zapierapprc`.\n* Moves the zip to `build/build.zip` and `build/source.zip` and deletes the temp folder\n\nThis command is typically followed by `zapier upload`.",
58
+ "description": "Build a pushable zip from the current directory.\n\nThis command does the following:\n\n* Creates a temporary folder\n* Copies all code into the temporary folder\n* Adds an entry point: `zapierwrapper.js`\n* Generates and validates app definition.\n* Detects dependencies via esbuild (optional, on by default)\n* Zips up all needed `.js` files. If you want to include more files, add a \"includeInBuild\" property (array with strings of regexp paths) to your `.zapierapprc`.\n* Moves the zip to `build/build.zip` and `build/source.zip` and deletes the temp folder\n\nThis command is typically followed by `zapier upload`.",
59
59
  "flags": {
60
60
  "disable-dependency-detection": {
61
61
  "description": "Disable \"smart\" file inclusion. By default, Zapier only includes files that are required by `index.js`. If you (or your dependencies) require files dynamically (such as with `require(someVar)`), then you may see \"Cannot find module\" errors. Disabling this may make your `build.zip` too large. If that's the case, try using the `includeInBuild` option in your `.zapierapprc`. See the docs about `includeInBuild` for more info.",
@@ -337,7 +337,8 @@
337
337
  "description": "Initialize a new Zapier integration with a project template.\n\nAfter running this, you'll have a new integration in the specified directory. If you re-run this command on an existing directory, it will prompt before overwriting any existing files.\n\nThis doesn't register or deploy the integration with Zapier - try the `zapier register` and `zapier push` commands for that!",
338
338
  "examples": [
339
339
  "zapier init myapp",
340
- "zapier init ./path/myapp --template oauth2"
340
+ "zapier init ./path/myapp --template oauth2",
341
+ "zapier init ./path/myapp --template minimal --module esm"
341
342
  ],
342
343
  "flags": {
343
344
  "template": {
@@ -363,6 +364,18 @@
363
364
  ],
364
365
  "type": "option"
365
366
  },
367
+ "module": {
368
+ "char": "m",
369
+ "description": "Choose module type: CommonJS or ES Modules. Only enabled for Typescript and Minimal templates.",
370
+ "name": "module",
371
+ "hasDynamicHelp": false,
372
+ "multiple": false,
373
+ "options": [
374
+ "commonjs",
375
+ "esm"
376
+ ],
377
+ "type": "option"
378
+ },
366
379
  "debug": {
367
380
  "char": "d",
368
381
  "description": "Show extra debugging output.",
@@ -2417,5 +2430,5 @@
2417
2430
  ]
2418
2431
  }
2419
2432
  },
2420
- "version": "16.5.1"
2433
+ "version": "17.0.1"
2421
2434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-cli",
3
- "version": "16.5.1",
3
+ "version": "17.0.1",
4
4
  "description": "The CLI for managing integrations in Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -40,56 +40,56 @@
40
40
  "validate": "yarn test && yarn smoke-test && yarn lint"
41
41
  },
42
42
  "dependencies": {
43
- "@oclif/core": "4.0.34",
44
- "@oclif/plugin-autocomplete": "3.2.11",
45
- "@oclif/plugin-help": "6.2.18",
46
- "@oclif/plugin-not-found": "3.2.29",
47
- "@oclif/plugin-version": "2.2.16",
43
+ "@oclif/core": "4.3.0",
44
+ "@oclif/plugin-autocomplete": "3.2.28",
45
+ "@oclif/plugin-help": "6.2.28",
46
+ "@oclif/plugin-not-found": "3.2.51",
47
+ "@oclif/plugin-version": "2.2.28",
48
48
  "adm-zip": "0.5.16",
49
49
  "archiver": "7.0.1",
50
- "browserify": "17.0.1",
51
- "chrono-node": "2.7.7",
50
+ "chrono-node": "2.8.0",
52
51
  "cli-table3": "0.6.5",
53
52
  "colors": "1.4.0",
54
- "debug": "4.3.7",
55
- "dotenv": "16.4.6",
53
+ "debug": "4.4.0",
54
+ "dotenv": "16.5.0",
55
+ "esbuild": "0.25.4",
56
56
  "fs-extra": "11.2.0",
57
57
  "gulp-filter": "7.0.0",
58
- "gulp-prettier": "4.0.0",
58
+ "gulp-prettier": "5.0.0",
59
59
  "ignore": "5.2.4",
60
60
  "inquirer": "8.2.5",
61
- "jscodeshift": "^17.0.0",
61
+ "jscodeshift": "^17.3.0",
62
62
  "klaw": "4.1.0",
63
63
  "lodash": "4.17.21",
64
- "luxon": "3.5.0",
64
+ "luxon": "3.6.1",
65
65
  "marked": "14.1.4",
66
66
  "marked-terminal": "7.2.1",
67
67
  "minimatch": "9.0.3",
68
68
  "node-fetch": "2.7.0",
69
- "open": "10.1.0",
69
+ "open": "10.1.2",
70
70
  "ora": "5.4.0",
71
71
  "parse-gitignore": "0.5.1",
72
- "prettier": "3.4.1",
73
- "read": "4.0.0",
74
- "semver": "7.6.3",
72
+ "prettier": "3.5.3",
73
+ "read": "4.1.0",
74
+ "semver": "7.7.1",
75
75
  "string-length": "4.0.2",
76
76
  "through2": "4.0.2",
77
77
  "tmp": "0.2.3",
78
- "traverse": "0.6.10",
78
+ "traverse": "0.6.11",
79
79
  "update-notifier": "5.1.0",
80
80
  "yeoman-environment": "3.19.3",
81
81
  "yeoman-generator": "5.9.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@oclif/test": "^4.0.9",
84
+ "@oclif/test": "^4.1.12",
85
85
  "@types/jscodeshift": "^0.12.0",
86
86
  "@types/mocha": "^10.0.9",
87
87
  "chai": "^4.3.7",
88
88
  "decompress": "4.2.1",
89
- "mock-fs": "^5.2.0",
90
- "nock": "^13.3.1",
91
- "oclif": "^4.15.30",
92
- "typescript": "^5.6.3",
89
+ "mock-fs": "^5.5.0",
90
+ "nock": "^14.0.4",
91
+ "oclif": "^4.17.46",
92
+ "typescript": "^5.8.3",
93
93
  "yamljs": "0.3.0"
94
94
  },
95
95
  "bin": {
@@ -1,21 +1,31 @@
1
- import type { Create, PerformFunction } from 'zapier-platform-core';
1
+ import {
2
+ defineInputFields,
3
+ defineCreate,
4
+ type CreatePerform,
5
+ type InferInputData,
6
+ } from 'zapier-platform-core';
7
+
8
+ const inputFields = defineInputFields([
9
+ { key: 'name', required: true },
10
+ { key: 'fave_meal', label: 'Favorite Meal', required: false },
11
+ ]);
2
12
 
3
13
  // create a particular <%= LOWER_NOUN %> by name
4
- const perform: PerformFunction = async (z, bundle) => {
14
+ const perform = (async (z, bundle) => {
5
15
  const response = await z.request({
6
16
  method: 'POST',
7
17
  url: 'https://jsonplaceholder.typicode.com/posts',
8
18
  // if `body` is an object, it'll automatically get run through JSON.stringify
9
19
  // if you don't want to send JSON, pass a string in your chosen format here instead
10
20
  body: {
11
- name: bundle.inputData.name
12
- }
21
+ name: bundle.inputData.name,
22
+ },
13
23
  });
14
24
  // this should return a single object
15
25
  return response.data;
16
- };
26
+ }) satisfies CreatePerform<InferInputData<typeof inputFields>>;
17
27
 
18
- export default {
28
+ export default defineCreate({
19
29
  // see here for a full list of available properties:
20
30
  // https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#createschema
21
31
  key: '<%= KEY %>',
@@ -34,10 +44,7 @@ export default {
34
44
  '// Zapier will pass them in as `bundle.inputData` later. They\'re optional.',
35
45
  '// End-users will map data into these fields. In general, they should have any fields that the API can accept. Be sure to accurately mark which fields are required!'
36
46
  ].join('\n ') : '' %>
37
- inputFields: [
38
- {key: 'name', required: true},
39
- {key: 'fave_meal', label: 'Favorite Meal', required: false}
40
- ],
47
+ inputFields,
41
48
 
42
49
  <%= INCLUDE_INTRO_COMMENTS ? [
43
50
  '// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
@@ -46,7 +53,7 @@ export default {
46
53
  ].join('\n ') : '' %>
47
54
  sample: {
48
55
  id: 1,
49
- name: 'Test'
56
+ name: 'Test',
50
57
  },
51
58
 
52
59
  <%= INCLUDE_INTRO_COMMENTS ? [
@@ -59,6 +66,6 @@ export default {
59
66
  // these are placeholders to match the example `perform` above
60
67
  // {key: 'id', label: 'Person ID'},
61
68
  // {key: 'name', label: 'Person Name'}
62
- ]
63
- }
64
- } satisfies Create;
69
+ ],
70
+ },
71
+ });
@@ -1,7 +1,20 @@
1
- import type { PerformFunction, Search } from 'zapier-platform-core';
1
+ import {
2
+ defineInputFields,
3
+ defineSearch,
4
+ type SearchPerform,
5
+ type InferInputData,
6
+ } from 'zapier-platform-core';
7
+
8
+ const inputFields = defineInputFields([
9
+ {
10
+ key: 'name',
11
+ required: true,
12
+ helpText: 'Find the <%= NOUN %> with this name.',
13
+ },
14
+ ]);
2
15
 
3
16
  // find a particular <%= LOWER_NOUN %> by name
4
- const perform: PerformFunction = async (z, bundle) => {
17
+ const perform = (async (z, bundle) => {
5
18
  const response = await z.request({
6
19
  url: 'https://jsonplaceholder.typicode.com/posts',
7
20
  params: {
@@ -10,9 +23,9 @@ const perform: PerformFunction = async (z, bundle) => {
10
23
  });
11
24
  // this should return an array of objects (but only the first will be used)
12
25
  return response.data;
13
- };
26
+ }) satisfies SearchPerform<InferInputData<typeof inputFields>>;
14
27
 
15
- export default {
28
+ export default defineSearch({
16
29
  // see here for a full list of available properties:
17
30
  // https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#searchschema
18
31
  key: '<%= KEY %>',
@@ -30,13 +43,7 @@ export default {
30
43
  '// `inputFields` defines the fields a user could provide',
31
44
  '// Zapier will pass them in as `bundle.inputData` later. Searches need at least one `inputField`.'
32
45
  ].join('\n ') : '' %>
33
- inputFields: [
34
- {
35
- key: 'name',
36
- required: true,
37
- helpText: 'Find the <%= NOUN %> with this name.',
38
- },
39
- ],
46
+ inputFields,
40
47
 
41
48
  <%= INCLUDE_INTRO_COMMENTS ? [
42
49
  '// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
@@ -60,4 +67,4 @@ export default {
60
67
  // {key: 'name', label: 'Person Name'}
61
68
  ],
62
69
  },
63
- } satisfies Search;
70
+ });
@@ -1,7 +1,10 @@
1
- import type { PerformFunction, Trigger } from 'zapier-platform-core';
1
+ import {
2
+ defineTrigger,
3
+ type PollingTriggerPerform,
4
+ } from 'zapier-platform-core';
2
5
 
3
6
  // triggers on a new <%= LOWER_NOUN %> with a certain tag
4
- const perform: PerformFunction = async (z, bundle) => {
7
+ const perform = (async (z, bundle) => {
5
8
  const response = await z.request({
6
9
  url: 'https://jsonplaceholder.typicode.com/posts',
7
10
  params: {
@@ -10,9 +13,9 @@ const perform: PerformFunction = async (z, bundle) => {
10
13
  });
11
14
  // this should return an array of objects
12
15
  return response.data;
13
- };
16
+ }) satisfies PollingTriggerPerform;
14
17
 
15
- export default {
18
+ export default defineTrigger({
16
19
  // see here for a full list of available properties:
17
20
  // https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema
18
21
  key: '<%= KEY %>' as const,
@@ -55,4 +58,4 @@ export default {
55
58
  // {key: 'name', label: 'Person Name'}
56
59
  ],
57
60
  },
58
- } satisfies Trigger;
61
+ });
@@ -32,6 +32,18 @@ const writeGitignore = (gen) => {
32
32
  };
33
33
 
34
34
  const writeGenericPackageJson = (gen, packageJsonExtension) => {
35
+ const moduleExtension =
36
+ gen.options.module === 'esm'
37
+ ? {
38
+ exports: './index.js',
39
+ type: 'module',
40
+ }
41
+ : {
42
+ main: 'index.js',
43
+ };
44
+
45
+ const fullExtension = merge(moduleExtension, packageJsonExtension);
46
+
35
47
  gen.fs.writeJSON(
36
48
  gen.destinationPath('package.json'),
37
49
  merge(
@@ -39,7 +51,6 @@ const writeGenericPackageJson = (gen, packageJsonExtension) => {
39
51
  name: gen.options.packageName,
40
52
  version: '1.0.0',
41
53
  description: '',
42
- main: 'index.js',
43
54
  scripts: {
44
55
  test: 'jest --testTimeout 10000',
45
56
  },
@@ -51,12 +62,24 @@ const writeGenericPackageJson = (gen, packageJsonExtension) => {
51
62
  },
52
63
  private: true,
53
64
  },
54
- packageJsonExtension,
65
+ fullExtension,
55
66
  ),
56
67
  );
57
68
  };
58
69
 
59
70
  const writeTypeScriptPackageJson = (gen, packageJsonExtension) => {
71
+ const moduleExtension =
72
+ gen.options.module === 'esm'
73
+ ? {
74
+ exports: './dist/index.js',
75
+ type: 'module',
76
+ }
77
+ : {
78
+ main: 'index.js',
79
+ };
80
+
81
+ const fullExtension = merge(moduleExtension, packageJsonExtension);
82
+
60
83
  gen.fs.writeJSON(
61
84
  gen.destinationPath('package.json'),
62
85
  merge(
@@ -64,7 +87,6 @@ const writeTypeScriptPackageJson = (gen, packageJsonExtension) => {
64
87
  name: gen.options.packageName,
65
88
  version: '1.0.0',
66
89
  description: '',
67
- main: 'src/index.js',
68
90
  scripts: {
69
91
  test: 'vitest',
70
92
  },
@@ -76,19 +98,40 @@ const writeTypeScriptPackageJson = (gen, packageJsonExtension) => {
76
98
  },
77
99
  private: true,
78
100
  },
79
- packageJsonExtension,
101
+ fullExtension,
80
102
  ),
81
103
  );
82
104
  };
83
105
 
84
106
  const writeGenericIndex = (gen, hasAuth) => {
107
+ const templatePath =
108
+ gen.options.module === 'esm'
109
+ ? 'index-esm.template.js'
110
+ : 'index.template.js';
85
111
  gen.fs.copyTpl(
86
- gen.templatePath('index.template.js'),
112
+ gen.templatePath(templatePath),
87
113
  gen.destinationPath('index.js'),
88
114
  { corePackageName: PLATFORM_PACKAGE, hasAuth },
89
115
  );
90
116
  };
91
117
 
118
+ const writeTypeScriptIndex = (gen) => {
119
+ const templatePath =
120
+ gen.options.module === 'esm'
121
+ ? 'index-esm.template.ts'
122
+ : 'index.template.ts';
123
+ gen.fs.copyTpl(
124
+ gen.templatePath(templatePath),
125
+ gen.destinationPath('src/index.ts'),
126
+ );
127
+
128
+ // create root directory index.js if it's commonjs
129
+ if (gen.options.module === 'commonjs') {
130
+ const content = `module.exports = require('./dist').default;`;
131
+ gen.fs.write(gen.destinationPath('index.js'), content);
132
+ }
133
+ };
134
+
92
135
  const authTypes = {
93
136
  'basic-auth': 'basic',
94
137
  'custom-auth': 'custom',
@@ -170,7 +213,7 @@ const writeForStandaloneTypeScriptTemplate = (gen) => {
170
213
  const packageJsonExtension = {
171
214
  typescript: {
172
215
  scripts: {
173
- test: 'vitest',
216
+ test: 'vitest --run',
174
217
  clean: 'rimraf ./dist ./build',
175
218
  build: 'npm run clean && tsc',
176
219
  '_zapier-build': 'npm run build',
@@ -189,6 +232,7 @@ const writeForStandaloneTypeScriptTemplate = (gen) => {
189
232
  gen.templatePath(gen.options.template, '**', '*.{js,json,ts}'),
190
233
  gen.destinationPath(),
191
234
  );
235
+ writeTypeScriptIndex(gen);
192
236
  };
193
237
 
194
238
  const TEMPLATE_ROUTES = {
@@ -207,6 +251,8 @@ const TEMPLATE_ROUTES = {
207
251
  typescript: writeForStandaloneTypeScriptTemplate,
208
252
  };
209
253
 
254
+ const ESM_SUPPORTED_TEMPLATES = ['minimal', 'typescript'];
255
+
210
256
  const TEMPLATE_CHOICES = Object.keys(TEMPLATE_ROUTES);
211
257
 
212
258
  class ProjectGenerator extends Generator {
@@ -235,6 +281,31 @@ class ProjectGenerator extends Generator {
235
281
  ]);
236
282
  this.options.template = this.answers.template;
237
283
  }
284
+
285
+ if (
286
+ ESM_SUPPORTED_TEMPLATES.includes(this.options.template) &&
287
+ !this.options.module
288
+ ) {
289
+ this.answers = await this.prompt([
290
+ {
291
+ type: 'list',
292
+ name: 'module',
293
+ choices: ['commonjs', 'esm'],
294
+ message: 'Choose module type:',
295
+ default: 'commonjs',
296
+ },
297
+ ]);
298
+ this.options.module = this.answers.module;
299
+ }
300
+
301
+ if (
302
+ !ESM_SUPPORTED_TEMPLATES.includes(this.options.template) &&
303
+ this.options.module === 'esm'
304
+ ) {
305
+ throw new Error(
306
+ 'ESM is not supported for this template, please use a different template or set the module to commonjs',
307
+ );
308
+ }
238
309
  }
239
310
 
240
311
  writing() {
@@ -0,0 +1,36 @@
1
+ <% if (hasAuth) { %>
2
+ import {
3
+ config as authentication,
4
+ befores = [],
5
+ afters = [],
6
+ } from './authentication';
7
+ <% } %>
8
+
9
+ import packageJson from './package.json' with { type: 'json' };
10
+ import zapier from '<%= corePackageName %>';
11
+
12
+ export default {
13
+ // This is just shorthand to reference the installed dependencies you have.
14
+ // Zapier will need to know these before we can upload.
15
+ version: packageJson.version,
16
+ platformVersion: zapier.version,
17
+
18
+ <% if (hasAuth) { %>
19
+ authentication,
20
+
21
+ beforeRequest: [...befores],
22
+
23
+ afterResponse: [...afters],
24
+ <% } %>
25
+
26
+ // If you want your trigger to show up, you better include it here!
27
+ triggers: {},
28
+
29
+ // If you want your searches to show up, you better include it here!
30
+ searches: {},
31
+
32
+ // If you want your creates to show up, you better include it here!
33
+ creates: {},
34
+
35
+ resources: {},
36
+ };
@@ -0,0 +1,24 @@
1
+ import zapier, { defineApp } from 'zapier-platform-core';
2
+
3
+ import packageJson from '../package.json' with { type: 'json' };
4
+
5
+ import MovieCreate from './creates/movie.js';
6
+ import MovieTrigger from './triggers/movie.js';
7
+ import authentication from './authentication.js';
8
+ import { addBearerHeader } from './middleware.js';
9
+
10
+ export default defineApp({
11
+ version: packageJson.version,
12
+ platformVersion: zapier.version,
13
+
14
+ authentication,
15
+ beforeRequest: [addBearerHeader],
16
+
17
+ triggers: {
18
+ [MovieTrigger.key]: MovieTrigger,
19
+ },
20
+
21
+ creates: {
22
+ [MovieCreate.key]: MovieCreate,
23
+ },
24
+ });
@@ -1,5 +1,4 @@
1
- import type { App } from 'zapier-platform-core';
2
- import { version as platformVersion } from 'zapier-platform-core';
1
+ import { version as platformVersion, defineApp } from 'zapier-platform-core';
3
2
 
4
3
  import packageJson from '../package.json';
5
4
 
@@ -8,7 +7,7 @@ import MovieTrigger from './triggers/movie';
8
7
  import authentication from './authentication';
9
8
  import { addBearerHeader } from './middleware';
10
9
 
11
- export default {
10
+ export default defineApp({
12
11
  version: packageJson.version,
13
12
  platformVersion,
14
13
 
@@ -22,4 +21,4 @@ export default {
22
21
  creates: {
23
22
  [MovieCreate.key]: MovieCreate,
24
23
  },
25
- } satisfies App;
24
+ });
@@ -1,6 +1,6 @@
1
1
  import type { Authentication } from 'zapier-platform-core';
2
2
 
3
- import { API_URL, SCOPES } from './constants';
3
+ import { API_URL, SCOPES } from './constants.js';
4
4
 
5
5
  export default {
6
6
  type: 'oauth2',
@@ -1,7 +1,17 @@
1
- import type { Create, PerformFunction } from 'zapier-platform-core';
2
- import { API_URL } from '../constants';
1
+ import {
2
+ defineInputFields,
3
+ defineCreate,
4
+ type CreatePerform,
5
+ type InferInputData,
6
+ } from 'zapier-platform-core';
7
+ import { API_URL } from '../constants.js';
3
8
 
4
- const perform: PerformFunction = async (z, bundle) => {
9
+ const inputFields = defineInputFields([
10
+ { key: 'title', required: true },
11
+ { key: 'year', type: 'integer' },
12
+ ]);
13
+
14
+ const perform = (async (z, bundle) => {
5
15
  const response = await z.request({
6
16
  method: 'POST',
7
17
  url: `${API_URL}/movies`,
@@ -11,9 +21,9 @@ const perform: PerformFunction = async (z, bundle) => {
11
21
  },
12
22
  });
13
23
  return response.data;
14
- };
24
+ }) satisfies CreatePerform<InferInputData<typeof inputFields>>;
15
25
 
16
- export default {
26
+ export default defineCreate({
17
27
  key: 'movie',
18
28
  noun: 'Movie',
19
29
 
@@ -24,13 +34,10 @@ export default {
24
34
 
25
35
  operation: {
26
36
  perform,
27
- inputFields: [
28
- { key: 'title', required: true },
29
- { key: 'year', type: 'integer' },
30
- ],
37
+ inputFields,
31
38
  sample: {
32
39
  id: '1',
33
40
  title: 'example',
34
41
  },
35
42
  },
36
- } satisfies Create;
43
+ });
@@ -10,7 +10,7 @@ describe('movie', () => {
10
10
  test('create a movie', async () => {
11
11
  const bundle = {
12
12
  inputData: { title: 'hello', year: 2020 },
13
- authData: { access_token: 'a_token' },
13
+ authData: { access_token: 'a_token' },
14
14
  };
15
15
  const result = await appTester(App.creates.movie.operation.perform, bundle);
16
16
  expect(result).toMatchObject({
@@ -1,12 +1,15 @@
1
- import type { PerformFunction, Trigger } from 'zapier-platform-core';
2
- import { API_URL } from '../constants';
1
+ import {
2
+ defineTrigger,
3
+ type PollingTriggerPerform,
4
+ } from 'zapier-platform-core';
5
+ import { API_URL } from '../constants.js';
3
6
 
4
- const perform: PerformFunction = async (z, bundle) => {
7
+ const perform = (async (z, bundle) => {
5
8
  const response = await z.request(`${API_URL}/movies`);
6
9
  return response.data;
7
- };
10
+ }) satisfies PollingTriggerPerform;
8
11
 
9
- export default {
12
+ export default defineTrigger({
10
13
  key: 'movie',
11
14
  noun: 'Movie',
12
15
 
@@ -23,4 +26,4 @@ export default {
23
26
  title: 'example',
24
27
  },
25
28
  },
26
- } satisfies Trigger;
29
+ });
@@ -51,7 +51,7 @@ This command does the following:
51
51
  * Copies all code into the temporary folder
52
52
  * Adds an entry point: \`zapierwrapper.js\`
53
53
  * Generates and validates app definition.
54
- * Detects dependencies via browserify (optional, on by default)
54
+ * Detects dependencies via esbuild (optional, on by default)
55
55
  * Zips up all needed \`.js\` files. If you want to include more files, add a "includeInBuild" property (array with strings of regexp paths) to your \`${CURRENT_APP_FILE}\`.
56
56
  * Moves the zip to \`${BUILD_PATH}\` and \`${SOURCE_PATH}\` and deletes the temp folder
57
57
 
@@ -10,12 +10,12 @@ const { TEMPLATE_CHOICES, ProjectGenerator } = require('../../generators');
10
10
  class InitCommand extends BaseCommand {
11
11
  async perform() {
12
12
  const { path } = this.args;
13
- const { template } = this.flags;
13
+ const { template, module } = this.flags;
14
14
 
15
15
  const env = yeoman.createEnv();
16
16
  env.registerStub(ProjectGenerator, 'zapier:integration');
17
17
 
18
- await env.run('zapier:integration', { path, template });
18
+ await env.run('zapier:integration', { path, template, module });
19
19
 
20
20
  this.log();
21
21
  this.log(`A new integration has been created in directory "${path}".`);
@@ -30,6 +30,12 @@ InitCommand.flags = buildFlags({
30
30
  description: 'The template to start your integration with.',
31
31
  options: TEMPLATE_CHOICES,
32
32
  }),
33
+ module: Flags.string({
34
+ char: 'm',
35
+ description:
36
+ 'Choose module type: CommonJS or ES Modules. Only enabled for Typescript and Minimal templates.',
37
+ options: ['commonjs', 'esm'],
38
+ }),
33
39
  },
34
40
  });
35
41
  InitCommand.args = {
@@ -42,6 +48,7 @@ InitCommand.args = {
42
48
  InitCommand.examples = [
43
49
  'zapier init myapp',
44
50
  'zapier init ./path/myapp --template oauth2',
51
+ 'zapier init ./path/myapp --template minimal --module esm',
45
52
  ];
46
53
  InitCommand.description = `Initialize a new Zapier integration with a project template.
47
54
 
@@ -13,7 +13,7 @@ const { DateTime, IANAZone } = require('luxon');
13
13
 
14
14
  const BaseCommand = require('../ZapierBaseCommand');
15
15
  const { buildFlags } = require('../buildFlags');
16
- const { localAppCommand, getLocalAppHandler } = require('../../utils/local');
16
+ const { localAppCommand } = require('../../utils/local');
17
17
  const { startSpinner, endSpinner } = require('../../utils/display');
18
18
  const {
19
19
  getLinkedAppConfig,
@@ -510,10 +510,6 @@ class InvokeCommand extends BaseCommand {
510
510
  }
511
511
 
512
512
  if (!_.isEmpty(env)) {
513
- // process.env changed, so we need to reload the modules that have loaded
514
- // the old values of process.env
515
- getLocalAppHandler({ reload: true });
516
-
517
513
  // Save envs so the user won't have to re-enter them if the command fails
518
514
  await appendEnv(env);
519
515
  console.warn('CLIENT_ID and CLIENT_SECRET saved to .env file.');
@@ -2,10 +2,9 @@ const crypto = require('crypto');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
4
 
5
- const browserify = require('browserify');
6
- const through = require('through2');
7
5
  const _ = require('lodash');
8
6
  const archiver = require('archiver');
7
+ const esbuild = require('esbuild');
9
8
  const fs = require('fs');
10
9
  const fse = require('fs-extra');
11
10
  const klaw = require('klaw');
@@ -20,13 +19,7 @@ const {
20
19
 
21
20
  const constants = require('../constants');
22
21
 
23
- const {
24
- writeFile,
25
- readFile,
26
- copyDir,
27
- ensureDir,
28
- removeDir,
29
- } = require('./files');
22
+ const { writeFile, copyDir, ensureDir, removeDir } = require('./files');
30
23
 
31
24
  const {
32
25
  prettyJSONstringify,
@@ -42,67 +35,39 @@ const {
42
35
  validateApp,
43
36
  } = require('./api');
44
37
 
38
+ const { copyZapierWrapper } = require('./zapierwrapper');
39
+
45
40
  const checkMissingAppInfo = require('./check-missing-app-info');
46
41
 
47
42
  const { runCommand, isWindows, findCorePackageDir } = require('./misc');
48
43
  const { respectGitIgnore } = require('./ignore');
44
+ const { localAppCommand } = require('./local');
49
45
 
50
46
  const debug = require('debug')('zapier:build');
51
47
 
52
48
  const stripPath = (cwd, filePath) => filePath.split(cwd).pop();
53
49
 
54
50
  // given entry points in a directory, return a list of files that uses
55
- // could probably be done better with module-deps...
56
- // TODO: needs to include package.json files too i think
57
- // https://github.com/serverless/serverless-optimizer-plugin?
58
- const requiredFiles = (cwd, entryPoints) => {
51
+ const requiredFiles = async ({ cwd, entryPoints }) => {
59
52
  if (!_.endsWith(cwd, path.sep)) {
60
53
  cwd += path.sep;
61
54
  }
62
55
 
63
- const argv = {
64
- noParse: [undefined],
65
- extensions: [],
66
- ignoreTransform: [],
67
- entries: entryPoints,
68
- fullPaths: false,
69
- builtins: false,
70
- commondir: false,
71
- bundleExternal: true,
72
- basedir: cwd,
73
- browserField: false,
74
- detectGlobals: true,
75
- insertGlobals: false,
76
- insertGlobalVars: {
77
- process: undefined,
78
- global: undefined,
79
- 'Buffer.isBuffer': undefined,
80
- Buffer: undefined,
81
- },
82
- ignoreMissing: true,
83
- debug: false,
84
- standalone: undefined,
85
- };
86
- const b = browserify(argv);
87
-
88
- return new Promise((resolve, reject) => {
89
- b.on('error', reject);
90
-
91
- const paths = [];
92
- b.pipeline.get('deps').push(
93
- through
94
- .obj((row, enc, next) => {
95
- const filePath = row.file || row.id;
96
- paths.push(stripPath(cwd, filePath));
97
- next();
98
- })
99
- .on('end', () => {
100
- paths.sort();
101
- resolve(paths);
102
- }),
103
- );
104
- b.bundle();
56
+ const result = await esbuild.build({
57
+ entryPoints,
58
+ outdir: './build',
59
+ bundle: true,
60
+ platform: 'node',
61
+ metafile: true,
62
+ logLevel: 'warning',
63
+ external: ['../test/userapp'],
64
+ format: 'esm',
65
+ write: false, // no need to write outfile
105
66
  });
67
+
68
+ return Object.keys(result.metafile.inputs).map((path) =>
69
+ stripPath(cwd, path),
70
+ );
106
71
  };
107
72
 
108
73
  const listFiles = (dir) => {
@@ -194,16 +159,21 @@ const writeZipFromPaths = (dir, zipPath, paths) => {
194
159
  };
195
160
 
196
161
  const makeZip = async (dir, zipPath, disableDependencyDetection) => {
197
- const entryPoints = [
198
- path.resolve(dir, 'zapierwrapper.js'),
199
- path.resolve(dir, 'index.js'),
200
- ];
162
+ const entryPoints = [path.resolve(dir, 'zapierwrapper.js')];
163
+
164
+ const indexPath = path.resolve(dir, 'index.js');
165
+ if (fs.existsSync(indexPath)) {
166
+ // Necessary for CommonJS integrations. The zapierwrapper they use require()
167
+ // the index.js file using a variable. esbuild can't detect it, so we need
168
+ // to add it here specifically.
169
+ entryPoints.push(indexPath);
170
+ }
201
171
 
202
172
  let paths;
203
173
 
204
174
  const [dumbPaths, smartPaths, appConfig] = await Promise.all([
205
175
  listFiles(dir),
206
- requiredFiles(dir, entryPoints),
176
+ requiredFiles({ cwd: dir, entryPoints }),
207
177
  getLinkedAppConfig(dir).catch(() => ({})),
208
178
  ]);
209
179
 
@@ -234,24 +204,6 @@ const makeSourceZip = async (dir, zipPath) => {
234
204
  await writeZipFromPaths(dir, zipPath, finalPaths);
235
205
  };
236
206
 
237
- // Similar to utils.appCommand, but given a ready to go app
238
- // with a different location and ready to go zapierwrapper.js.
239
- const _appCommandZapierWrapper = (dir, event) => {
240
- const app = require(`${dir}/zapierwrapper.js`);
241
- event = Object.assign({}, event, {
242
- calledFromCli: true,
243
- });
244
- return new Promise((resolve, reject) => {
245
- app.handler(event, {}, (err, resp) => {
246
- if (err) {
247
- reject(err);
248
- } else {
249
- resolve(resp);
250
- }
251
- });
252
- });
253
- };
254
-
255
207
  const maybeNotifyAboutOutdated = () => {
256
208
  // find a package.json for the app and notify on the core dep
257
209
  // `build` won't run if package.json isn't there, so if we get to here we're good
@@ -417,35 +369,21 @@ const _buildFunc = async ({
417
369
 
418
370
  if (printProgress) {
419
371
  endSpinner();
420
- startSpinner('Applying entry point file');
372
+ startSpinner('Applying entry point files');
421
373
  }
422
374
 
423
- // TODO: should this routine for include exist elsewhere?
424
- const zapierWrapperBuf = await readFile(
425
- path.join(
426
- tmpDir,
427
- 'node_modules',
428
- constants.PLATFORM_PACKAGE,
429
- 'include',
430
- 'zapierwrapper.js',
431
- ),
432
- );
433
- await writeFile(
434
- path.join(tmpDir, 'zapierwrapper.js'),
435
- zapierWrapperBuf.toString(),
436
- );
375
+ await copyZapierWrapper(corePath, tmpDir);
437
376
 
438
377
  if (printProgress) {
439
378
  endSpinner();
440
379
  startSpinner('Building app definition.json');
441
380
  }
442
381
 
443
- const rawDefinition = (
444
- await _appCommandZapierWrapper(tmpDir, {
445
- command: 'definition',
446
- })
447
- ).results;
448
-
382
+ const rawDefinition = await localAppCommand(
383
+ { command: 'definition' },
384
+ tmpDir,
385
+ false,
386
+ );
449
387
  const fileWriteError = await writeFile(
450
388
  path.join(tmpDir, 'definition.json'),
451
389
  prettyJSONstringify(rawDefinition),
@@ -472,11 +410,11 @@ const _buildFunc = async ({
472
410
  if (printProgress) {
473
411
  startSpinner('Validating project schema and style');
474
412
  }
475
- const validateResponse = await _appCommandZapierWrapper(tmpDir, {
476
- command: 'validate',
477
- });
478
-
479
- const validationErrors = validateResponse.results;
413
+ const validationErrors = await localAppCommand(
414
+ { command: 'validate' },
415
+ tmpDir,
416
+ false,
417
+ );
480
418
  if (validationErrors.length) {
481
419
  debug('\nErrors:\n', validationErrors, '\n');
482
420
  throw new Error(
@@ -1,8 +1,8 @@
1
- const _ = require('lodash');
2
- const path = require('path');
1
+ const path = require('node:path');
3
2
 
4
- const { findCorePackageDir } = require('./misc');
5
3
  const { BASE_ENDPOINT } = require('../constants');
4
+ const { findCorePackageDir, runCommand } = require('./misc');
5
+ const { copyZapierWrapper, deleteZapierWrapper } = require('./zapierwrapper');
6
6
 
7
7
  /**
8
8
  * Wraps Node's http.request() / https.request() so that all requests go via a relay URL.
@@ -218,34 +218,75 @@ function wrapFetchWithRelay(fetchFunc, relayUrl, relayHeaders) {
218
218
  };
219
219
  }
220
220
 
221
- const getLocalAppHandler = ({
222
- reload = false,
223
- baseEvent = {},
221
+ const loadAppRawUsingImport = async (
222
+ appDir,
223
+ corePackageDir,
224
+ shouldDeleteWrapper,
225
+ ) => {
226
+ const wrapperPath = await copyZapierWrapper(corePackageDir, appDir);
227
+ let appRaw;
228
+ try {
229
+ // zapierwrapper.mjs is only available since zapier-platform-core v17.
230
+ // And only zapierwrapper.mjs exposes appRaw just for this use case.
231
+ appRaw = (await import(wrapperPath)).appRaw;
232
+ } catch (err) {
233
+ if (err.name === 'SyntaxError') {
234
+ // Run a separate process to print the line number of the SyntaxError.
235
+ // This workaround is needed because `err` doesn't provide the location
236
+ // info about the SyntaxError. However, if the error is thrown to
237
+ // Node.js's built-in error handler, it will print the location info.
238
+ // See: https://github.com/nodejs/node/issues/49441
239
+ await runCommand(process.execPath, ['zapierwrapper.js'], {
240
+ cwd: appDir,
241
+ });
242
+ }
243
+ throw err;
244
+ } finally {
245
+ if (shouldDeleteWrapper) {
246
+ await deleteZapierWrapper(appDir);
247
+ }
248
+ }
249
+
250
+ return appRaw;
251
+ };
252
+
253
+ const loadAppRawUsingRequire = (appDir) => {
254
+ let appRaw = require(appDir);
255
+ if (appRaw && appRaw.default) {
256
+ // Node.js 22+ supports using require() to import ESM.
257
+ // For Node.js < 20.17.0, require() will throw an error on ESM.
258
+ // https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require
259
+ appRaw = appRaw.default;
260
+ }
261
+ return appRaw;
262
+ };
263
+
264
+ const getLocalAppHandler = async ({
265
+ appDir = null,
224
266
  appId = null,
225
267
  deployKey = null,
226
268
  relayAuthenticationId = null,
227
269
  beforeRequest = null,
228
270
  afterResponse = null,
271
+ shouldDeleteWrapper = true,
229
272
  } = {}) => {
230
- const entryPath = `${process.cwd()}/index`;
231
- const rootPath = path.dirname(require.resolve(entryPath));
232
- const corePackageDir = findCorePackageDir();
273
+ appDir = path.resolve(appDir || process.cwd());
233
274
 
234
- if (reload) {
235
- Object.keys(require.cache).forEach((cachePath) => {
236
- if (cachePath.startsWith(rootPath)) {
237
- delete require.cache[cachePath];
238
- }
239
- });
240
- }
241
- let appRaw, zapier;
275
+ const corePackageDir = findCorePackageDir();
276
+ let appRaw;
242
277
  try {
243
- appRaw = require(entryPath);
244
- zapier = require(corePackageDir);
278
+ appRaw = loadAppRawUsingRequire(appDir);
245
279
  } catch (err) {
246
- // this err.stack doesn't give a nice traceback at all :-(
247
- // maybe we could do require('syntax-error') in the future
248
- return (event, ctx, callback) => callback(err);
280
+ if (err.code === 'MODULE_NOT_FOUND' || err.code === 'ERR_REQUIRE_ESM') {
281
+ appRaw = await loadAppRawUsingImport(
282
+ appDir,
283
+ corePackageDir,
284
+ shouldDeleteWrapper,
285
+ );
286
+ } else {
287
+ // err.name === 'SyntaxError' or others
288
+ throw err;
289
+ }
249
290
  }
250
291
 
251
292
  if (beforeRequest) {
@@ -282,41 +323,65 @@ const getLocalAppHandler = ({
282
323
  global.fetch = wrapFetchWithRelay(global.fetch, relayUrl, relayHeaders);
283
324
  }
284
325
 
326
+ // Assumes the entry point of zapier-platform-core is index.js
327
+ const coreEntryPoint = path.join(corePackageDir, 'index.js');
328
+ const zapier = (await import(coreEntryPoint)).default;
329
+
285
330
  const handler = zapier.createAppHandler(appRaw);
286
- return (event, ctx, callback) => {
287
- event = _.merge(
288
- {},
289
- event,
290
- {
331
+ if (handler.length === 3) {
332
+ // < v17: function handler(event, ctx, callback)
333
+ return (event, ctx, callback) => {
334
+ event = {
335
+ ...event,
291
336
  calledFromCli: true,
292
- },
293
- baseEvent,
294
- );
295
- handler(event, _, callback);
296
- };
337
+ };
338
+ return handler(event, ctx, callback);
339
+ };
340
+ } else {
341
+ // >= v17: async function handler(event, ctx = {})
342
+ return async (event, ctx = {}) => {
343
+ event = {
344
+ ...event,
345
+ calledFromCli: true,
346
+ };
347
+ return await handler(event, ctx);
348
+ };
349
+ }
297
350
  };
298
351
 
299
352
  // Runs a local app command (./index.js) like {command: 'validate'};
300
- const localAppCommand = (event) => {
301
- const handler = getLocalAppHandler({
353
+ const localAppCommand = async (event, appDir, shouldDeleteWrapper = true) => {
354
+ const handler = await getLocalAppHandler({
302
355
  appId: event.appId,
303
356
  deployKey: event.deployKey,
304
357
  relayAuthenticationId: event.relayAuthenticationId,
305
358
  beforeRequest: event.beforeRequest,
306
359
  afterResponse: event.afterResponse,
360
+ appDir,
361
+ shouldDeleteWrapper,
307
362
  });
308
- return new Promise((resolve, reject) => {
309
- handler(event, {}, (err, resp) => {
310
- if (err) {
311
- reject(err);
312
- } else {
313
- resolve(resp.results);
314
- }
363
+ if (handler.length === 3) {
364
+ // < 17: function handler(event, ctx, callback)
365
+ return new Promise((resolve, reject) => {
366
+ event = {
367
+ ...event,
368
+ calledFromCli: true,
369
+ };
370
+ handler(event, {}, (err, response) => {
371
+ if (err) {
372
+ reject(err);
373
+ } else {
374
+ resolve(response.results);
375
+ }
376
+ });
315
377
  });
316
- });
378
+ } else {
379
+ // >= 17: async function handler(event, ctx = {})
380
+ const response = await handler(event);
381
+ return response.results;
382
+ }
317
383
  };
318
384
 
319
385
  module.exports = {
320
- getLocalAppHandler,
321
386
  localAppCommand,
322
387
  };
@@ -0,0 +1,55 @@
1
+ // Utility functions that helps you copy and delete the Lambda function entry
2
+ // point zapierwrapper.mjs or zapierwrapper.js to the integration directory.
3
+
4
+ const { existsSync } = require('node:fs');
5
+ const { readFile, writeFile, rm } = require('node:fs/promises');
6
+ const { join } = require('node:path');
7
+
8
+ // We have two different versions of zapierwrapper: the .mjs one for ESM and the
9
+ // .js one for CommonJS. To copy the right one, we check the module type in the
10
+ // integration's package.json.
11
+ //
12
+ // For esbuild to perform static analysis, zapierwrapper.mjs can only
13
+ // import('packageName') where the packageName must be a static string literal.
14
+ // It can't import(packageName) where packageName is a variable. So in
15
+ // zapierwrapper.mjs, there's a placeholder {REPLACE_ME_PACKAGE_NAME} that
16
+ // we'll replace with the actual package name.
17
+ const copyZapierWrapper = async (corePackageDir, appDir) => {
18
+ const appPackageJson = require(join(appDir, 'package.json'));
19
+ let wrapperFilename;
20
+ if (appPackageJson.type === 'module') {
21
+ wrapperFilename = 'zapierwrapper.mjs';
22
+ } else {
23
+ wrapperFilename = 'zapierwrapper.js';
24
+ }
25
+ const wrapperPath = join(corePackageDir, 'include', wrapperFilename);
26
+
27
+ if (appPackageJson.type === 'module' && !existsSync(wrapperPath)) {
28
+ throw new Error(
29
+ "Couldn't find zapierwrapper.mjs in zapier-platform-core. Are you trying to run ESM? " +
30
+ 'If so, you need to upgrade zapier-platform-core to at least v17.',
31
+ );
32
+ }
33
+
34
+ const wrapperText = (await readFile(wrapperPath, 'utf8')).replace(
35
+ '{REPLACE_ME_PACKAGE_NAME}',
36
+ appPackageJson.name,
37
+ );
38
+ const wrapperDest = join(appDir, 'zapierwrapper.js');
39
+ await writeFile(wrapperDest, wrapperText);
40
+ return wrapperDest;
41
+ };
42
+
43
+ const deleteZapierWrapper = async (appDir) => {
44
+ const wrapperPath = join(appDir, 'zapierwrapper.js');
45
+ try {
46
+ await rm(wrapperPath);
47
+ } catch (err) {
48
+ // ignore
49
+ }
50
+ };
51
+
52
+ module.exports = {
53
+ copyZapierWrapper,
54
+ deleteZapierWrapper,
55
+ };
@@ -18,4 +18,5 @@ module.exports = [
18
18
  { nodeVersion: '16', npmVersion: '>=5.6.0' }, // 13.x
19
19
  { nodeVersion: '18', npmVersion: '>=5.6.0' }, // 15.x
20
20
  { nodeVersion: '18', npmVersion: '>=10.7.0' }, // 16.x
21
+ { nodeVersion: '18', npmVersion: '>=10.7.0' }, // 17.x
21
22
  ];
@@ -1 +0,0 @@
1
- module.exports = require('./dist').default;