zapier-platform-cli 17.7.0 → 17.7.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.
@@ -1157,7 +1157,7 @@
1157
1157
  "pull": {
1158
1158
  "aliases": [],
1159
1159
  "args": {},
1160
- "description": "Retrieve and update your local integration files with the latest version.\n\nThis command updates your local integration files with the latest version. You will be prompted with a confirmation dialog before continuing if there any destructive file changes.\n\nZapier may release new versions of your integration with bug fixes or new features. In the event this occurs, you will be unable to do the following until your local files are updated by running `zapier pull`:\n\n* push to the promoted version\n* promote a new version\n* migrate users from one version to another",
1160
+ "description": "Retrieve and update your local integration files with the promoted version (or latest version if not public).\n\nThis command updates your local integration files with the promoted version (or latest version if not public). You will be prompted with a confirmation dialog before continuing if there any destructive file changes.\n\nZapier may release new versions of your integration with bug fixes or new features. In the event this occurs, you will be unable to do the following until your local files are updated by running `zapier pull`:\n\n* push to the promoted version\n* promote a new version\n* migrate users from one version to another",
1161
1161
  "flags": {
1162
1162
  "debug": {
1163
1163
  "char": "d",
@@ -1549,6 +1549,7 @@
1549
1549
  "examples": [
1550
1550
  "zapier validate",
1551
1551
  "zapier validate --without-style",
1552
+ "zapier validate --skip-build",
1552
1553
  "zapier validate --format json"
1553
1554
  ],
1554
1555
  "flags": {
@@ -1558,6 +1559,12 @@
1558
1559
  "allowNo": false,
1559
1560
  "type": "boolean"
1560
1561
  },
1562
+ "skip-build": {
1563
+ "description": "Skip running the _zapier-build script before validation.",
1564
+ "name": "skip-build",
1565
+ "allowNo": false,
1566
+ "type": "boolean"
1567
+ },
1561
1568
  "debug": {
1562
1569
  "char": "d",
1563
1570
  "description": "Show extra debugging output.",
@@ -1900,6 +1907,88 @@
1900
1907
  "list.js"
1901
1908
  ]
1902
1909
  },
1910
+ "delete:integration": {
1911
+ "aliases": [
1912
+ "delete:app"
1913
+ ],
1914
+ "args": {},
1915
+ "description": "Delete your integration (including all versions).\n\nThis only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the `zapier delete:version` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.",
1916
+ "flags": {
1917
+ "debug": {
1918
+ "char": "d",
1919
+ "description": "Show extra debugging output.",
1920
+ "name": "debug",
1921
+ "allowNo": false,
1922
+ "type": "boolean"
1923
+ },
1924
+ "invokedFromAnotherCommand": {
1925
+ "hidden": true,
1926
+ "name": "invokedFromAnotherCommand",
1927
+ "allowNo": false,
1928
+ "type": "boolean"
1929
+ }
1930
+ },
1931
+ "hasDynamicHelp": false,
1932
+ "hiddenAliases": [],
1933
+ "id": "delete:integration",
1934
+ "pluginAlias": "zapier-platform-cli",
1935
+ "pluginName": "zapier-platform-cli",
1936
+ "pluginType": "core",
1937
+ "strict": true,
1938
+ "enableJsonFlag": false,
1939
+ "skipValidInstallCheck": true,
1940
+ "isESM": false,
1941
+ "relativePath": [
1942
+ "src",
1943
+ "oclif",
1944
+ "commands",
1945
+ "delete",
1946
+ "integration.js"
1947
+ ]
1948
+ },
1949
+ "delete:version": {
1950
+ "aliases": [],
1951
+ "args": {
1952
+ "version": {
1953
+ "description": "Specify the version to delete. It must have no users or Zaps.",
1954
+ "name": "version",
1955
+ "required": true
1956
+ }
1957
+ },
1958
+ "description": "Delete a specific version of your integration.\n\nThis only works if there are no users or Zaps on that version. You will probably need to have run `zapier migrate` and `zapier deprecate` before this command will work.",
1959
+ "flags": {
1960
+ "debug": {
1961
+ "char": "d",
1962
+ "description": "Show extra debugging output.",
1963
+ "name": "debug",
1964
+ "allowNo": false,
1965
+ "type": "boolean"
1966
+ },
1967
+ "invokedFromAnotherCommand": {
1968
+ "hidden": true,
1969
+ "name": "invokedFromAnotherCommand",
1970
+ "allowNo": false,
1971
+ "type": "boolean"
1972
+ }
1973
+ },
1974
+ "hasDynamicHelp": false,
1975
+ "hiddenAliases": [],
1976
+ "id": "delete:version",
1977
+ "pluginAlias": "zapier-platform-cli",
1978
+ "pluginName": "zapier-platform-cli",
1979
+ "pluginType": "core",
1980
+ "strict": true,
1981
+ "enableJsonFlag": false,
1982
+ "skipValidInstallCheck": true,
1983
+ "isESM": false,
1984
+ "relativePath": [
1985
+ "src",
1986
+ "oclif",
1987
+ "commands",
1988
+ "delete",
1989
+ "version.js"
1990
+ ]
1991
+ },
1903
1992
  "env:get": {
1904
1993
  "aliases": [],
1905
1994
  "args": {
@@ -2076,88 +2165,6 @@
2076
2165
  "unset.js"
2077
2166
  ]
2078
2167
  },
2079
- "delete:integration": {
2080
- "aliases": [
2081
- "delete:app"
2082
- ],
2083
- "args": {},
2084
- "description": "Delete your integration (including all versions).\n\nThis only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the `zapier delete:version` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.",
2085
- "flags": {
2086
- "debug": {
2087
- "char": "d",
2088
- "description": "Show extra debugging output.",
2089
- "name": "debug",
2090
- "allowNo": false,
2091
- "type": "boolean"
2092
- },
2093
- "invokedFromAnotherCommand": {
2094
- "hidden": true,
2095
- "name": "invokedFromAnotherCommand",
2096
- "allowNo": false,
2097
- "type": "boolean"
2098
- }
2099
- },
2100
- "hasDynamicHelp": false,
2101
- "hiddenAliases": [],
2102
- "id": "delete:integration",
2103
- "pluginAlias": "zapier-platform-cli",
2104
- "pluginName": "zapier-platform-cli",
2105
- "pluginType": "core",
2106
- "strict": true,
2107
- "enableJsonFlag": false,
2108
- "skipValidInstallCheck": true,
2109
- "isESM": false,
2110
- "relativePath": [
2111
- "src",
2112
- "oclif",
2113
- "commands",
2114
- "delete",
2115
- "integration.js"
2116
- ]
2117
- },
2118
- "delete:version": {
2119
- "aliases": [],
2120
- "args": {
2121
- "version": {
2122
- "description": "Specify the version to delete. It must have no users or Zaps.",
2123
- "name": "version",
2124
- "required": true
2125
- }
2126
- },
2127
- "description": "Delete a specific version of your integration.\n\nThis only works if there are no users or Zaps on that version. You will probably need to have run `zapier migrate` and `zapier deprecate` before this command will work.",
2128
- "flags": {
2129
- "debug": {
2130
- "char": "d",
2131
- "description": "Show extra debugging output.",
2132
- "name": "debug",
2133
- "allowNo": false,
2134
- "type": "boolean"
2135
- },
2136
- "invokedFromAnotherCommand": {
2137
- "hidden": true,
2138
- "name": "invokedFromAnotherCommand",
2139
- "allowNo": false,
2140
- "type": "boolean"
2141
- }
2142
- },
2143
- "hasDynamicHelp": false,
2144
- "hiddenAliases": [],
2145
- "id": "delete:version",
2146
- "pluginAlias": "zapier-platform-cli",
2147
- "pluginName": "zapier-platform-cli",
2148
- "pluginType": "core",
2149
- "strict": true,
2150
- "enableJsonFlag": false,
2151
- "skipValidInstallCheck": true,
2152
- "isESM": false,
2153
- "relativePath": [
2154
- "src",
2155
- "oclif",
2156
- "commands",
2157
- "delete",
2158
- "version.js"
2159
- ]
2160
- },
2161
2168
  "team:add": {
2162
2169
  "aliases": [
2163
2170
  "team:invite"
@@ -2537,5 +2544,5 @@
2537
2544
  ]
2538
2545
  }
2539
2546
  },
2540
- "version": "17.7.0"
2547
+ "version": "17.7.1"
2541
2548
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-cli",
3
- "version": "17.7.0",
3
+ "version": "17.7.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/",
@@ -79,6 +79,7 @@ const writeGenericTypeScriptPackageJson = (gen, packageJsonExtension) => {
79
79
  test: 'npm run build && vitest --run',
80
80
  clean: 'rimraf ./dist ./build',
81
81
  build: 'npm run clean && tsc',
82
+ dev: 'npm run build -- --watch',
82
83
  '_zapier-build': 'npm run build',
83
84
  },
84
85
  dependencies: {
@@ -252,13 +253,26 @@ class ProjectGenerator extends Generator {
252
253
 
253
254
  async prompting() {
254
255
  if (!this.options.template) {
256
+ // Filter template choices based on language and module type
257
+ let templateChoices = TEMPLATE_CHOICES;
258
+ let defaultTemplate = 'minimal';
259
+
260
+ // TypeScript filtering takes precedence over ESM filtering
261
+ if (this.options.language === 'typescript') {
262
+ templateChoices = TS_SUPPORTED_TEMPLATES;
263
+ defaultTemplate = 'basic-auth';
264
+ } else if (this.options.module === 'esm') {
265
+ templateChoices = ESM_SUPPORTED_TEMPLATES;
266
+ defaultTemplate = 'minimal'; // minimal is the only ESM template
267
+ }
268
+
255
269
  this.answers = await this.prompt([
256
270
  {
257
271
  type: 'list',
258
272
  name: 'template',
259
- choices: TEMPLATE_CHOICES,
273
+ choices: templateChoices,
260
274
  message: 'Choose a project template to start with:',
261
- default: 'minimal',
275
+ default: defaultTemplate,
262
276
  },
263
277
  ]);
264
278
  this.options.template = this.answers.template;
@@ -320,6 +334,8 @@ class ProjectGenerator extends Generator {
320
334
 
321
335
  module.exports = {
322
336
  TEMPLATE_CHOICES,
337
+ ESM_SUPPORTED_TEMPLATES,
338
+ TS_SUPPORTED_TEMPLATES,
323
339
  PullGenerator,
324
340
  ProjectGenerator,
325
341
  };
@@ -90,9 +90,14 @@ class ZapierBaseCommand extends Command {
90
90
 
91
91
  // validate that user input looks like a semver version
92
92
  throwForInvalidVersion(version) {
93
- if (!version.match(/^\d+\.\d+\.\d+$/g)) {
93
+ if (
94
+ !version.match(
95
+ // this is mirrored in schemas/VersionSchema.js and developer_cli/constants.py
96
+ /^(?:0|[1-9]\d{0,2})\.(?:0|[1-9]\d{0,2})\.(?:0|[1-9]\d{0,2})(?:-(?=.{1,12}$)[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)?$/g,
97
+ )
98
+ ) {
94
99
  throw new Error(
95
- `${version} is an invalid version str. Try something like \`1.2.3\``,
100
+ `${version} is an invalid version str. Try something like \`1.2.3\` or \`0.0.0-TICKET\``,
96
101
  );
97
102
  }
98
103
  }
@@ -240,15 +240,26 @@ const resolveInputDataTypes = (inputData, inputFields, timezone) => {
240
240
  };
241
241
 
242
242
  const appendEnv = async (vars, prefix = '') => {
243
- await fs.appendFile(
244
- '.env',
245
- Object.entries(vars)
246
- .filter(([k, v]) => v !== undefined)
247
- .map(
248
- ([k, v]) =>
249
- `${prefix}${k}='${typeof v === 'object' && v !== null ? JSON.stringify(v) : v || ''}'\n`,
250
- ),
251
- );
243
+ const envFile = '.env';
244
+ let content = Object.entries(vars)
245
+ .filter(([k, v]) => v !== undefined)
246
+ .map(
247
+ ([k, v]) =>
248
+ `${prefix}${k}='${typeof v === 'object' && v !== null ? JSON.stringify(v) : v || ''}'\n`,
249
+ )
250
+ .join('');
251
+
252
+ // Check if .env file exists and doesn't end with newline
253
+ try {
254
+ const existingContent = await fs.readFile(envFile, 'utf8');
255
+ if (existingContent.length > 0 && !existingContent.endsWith('\n')) {
256
+ content = '\n' + content;
257
+ }
258
+ } catch (error) {
259
+ // File doesn't exist or can't be read, proceed as normal
260
+ }
261
+
262
+ await fs.appendFile(envFile, content);
252
263
  };
253
264
 
254
265
  const replaceDoubleCurlies = async (request) => {
@@ -1,6 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('debug')('zapier:migrate');
3
3
  const { Args, Flags } = require('@oclif/core');
4
+ const colors = require('colors/safe');
4
5
 
5
6
  const BaseCommand = require('../ZapierBaseCommand');
6
7
  const PromoteCommand = require('./promote');
@@ -118,17 +119,15 @@ class MigrateCommand extends BaseCommand {
118
119
 
119
120
  await this.run_require_confirmation_pre_checks(app, body);
120
121
 
122
+ let message;
121
123
  if (user || account) {
122
- this.startSpinner(
123
- `Starting migration from ${fromVersion} to ${toVersion} for ${
124
- user || account
125
- }`,
126
- );
124
+ message = `Requesting migration from ${fromVersion} to ${toVersion} for ${user || account}`;
127
125
  } else {
128
- this.startSpinner(
129
- `Starting migration from ${fromVersion} to ${toVersion} for ${percent}%`,
130
- );
126
+ message = `Requesting migration from ${fromVersion} to ${toVersion} for ${percent}%`;
131
127
  }
128
+
129
+ this.startSpinner(message);
130
+
132
131
  if (percent) {
133
132
  body.job.percent_human = percent;
134
133
  }
@@ -137,12 +136,14 @@ class MigrateCommand extends BaseCommand {
137
136
 
138
137
  try {
139
138
  await callAPI(url, { method: 'POST', body });
140
- } finally {
141
- this.stopSpinner();
139
+ } catch (err) {
140
+ this.stopSpinner({ success: false });
141
+ throw err;
142
142
  }
143
+ this.stopSpinner();
143
144
 
144
145
  this.log(
145
- '\nMigration successfully queued, please check `zapier jobs` to track the status. Migrations usually take between 5-10 minutes.',
146
+ `\nMigration successfully queued, check ${colors.bold.underline('zapier jobs')} to track the status. Migrations usually take between 5-10 minutes.`,
146
147
  );
147
148
  }
148
149
  }
@@ -53,9 +53,9 @@ class PullCommand extends ZapierBaseCommand {
53
53
  }
54
54
 
55
55
  PullCommand.flags = buildFlags();
56
- PullCommand.description = `Retrieve and update your local integration files with the latest version.
56
+ PullCommand.description = `Retrieve and update your local integration files with the promoted version (or latest version if not public).
57
57
 
58
- This command updates your local integration files with the latest version. You will be prompted with a confirmation dialog before continuing if there any destructive file changes.
58
+ This command updates your local integration files with the promoted version (or latest version if not public). You will be prompted with a confirmation dialog before continuing if there any destructive file changes.
59
59
 
60
60
  Zapier may release new versions of your integration with bug fixes or new features. In the event this occurs, you will be unable to do the following until your local files are updated by running \`zapier pull\`:
61
61
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable camelcase */
2
1
  // @ts-check
3
2
 
4
3
  const path = require('path');
@@ -6,9 +6,14 @@ const { buildFlags } = require('../buildFlags');
6
6
  const { flattenCheckResult } = require('../../utils/display');
7
7
  const { localAppCommand } = require('../../utils/local');
8
8
  const { validateApp } = require('../../utils/api');
9
+ const { maybeRunBuildScript } = require('../../utils/build');
9
10
 
10
11
  class ValidateCommand extends BaseCommand {
11
12
  async perform() {
13
+ if (!this.flags['skip-build']) {
14
+ await maybeRunBuildScript({ printProgress: true });
15
+ }
16
+
12
17
  this.log('Validating project locally');
13
18
 
14
19
  const errors = await localAppCommand({ command: 'validate' });
@@ -121,6 +126,9 @@ ValidateCommand.flags = buildFlags({
121
126
  'without-style': Flags.boolean({
122
127
  description: 'Forgo pinging the Zapier server to run further checks.',
123
128
  }),
129
+ 'skip-build': Flags.boolean({
130
+ description: 'Skip running the _zapier-build script before validation.',
131
+ }),
124
132
  },
125
133
  opts: {
126
134
  format: true,
@@ -130,6 +138,7 @@ ValidateCommand.flags = buildFlags({
130
138
  ValidateCommand.examples = [
131
139
  'zapier validate',
132
140
  'zapier validate --without-style',
141
+ 'zapier validate --skip-build',
133
142
  'zapier validate --format json',
134
143
  ];
135
144
  ValidateCommand.description = `Validate your integration.
@@ -51,7 +51,7 @@ const recordAnalytics = async (command, isValidCommand, args, flags) => {
51
51
  numArgs: argKeys.length,
52
52
  appId: linkedAppId,
53
53
  argsKeys: argKeys,
54
- flagKeys: flagKeys,
54
+ flagKeys,
55
55
  cliVersion: pkg.version,
56
56
  os: shouldRecordAnonymously ? undefined : process.platform,
57
57
  };
package/src/utils/ast.js CHANGED
@@ -115,17 +115,23 @@ const registerActionInJsApp = (codeStr, property, varName) => {
115
115
 
116
116
  // check if this object already has the property at the top level
117
117
  const existingProp = objToModify.properties.find(
118
- (props) => props.key.name === property,
118
+ (props) => props.key && props.key.name === property,
119
119
  );
120
120
  if (existingProp) {
121
- // `triggers: myTriggers` means we shouldn't bother
122
121
  const value = existingProp.value;
123
- if (value.type !== 'ObjectExpression') {
122
+ if (value.type === 'Identifier') {
123
+ // Handle shorthand syntax like `creates` instead of `creates: { ... }`
124
+ // Transform it into an object with spread operator: `creates: { ...creates, [newAction.key]: newAction }`
125
+ const spreadProperty = j.spreadElement(j.identifier(value.name));
126
+ existingProp.value = j.objectExpression([spreadProperty, newProperty]);
127
+ existingProp.shorthand = false; // Disable shorthand since we're changing the value
128
+ } else if (value.type === 'ObjectExpression') {
129
+ value.properties.push(newProperty);
130
+ } else {
124
131
  throw new Error(
125
132
  `Tried to edit the ${property} key, but the value wasn't an object`,
126
133
  );
127
134
  }
128
- value.properties.push(newProperty);
129
135
  } else {
130
136
  objToModify.properties.push(
131
137
  j.property(
@@ -209,16 +215,23 @@ const registerActionInTsApp = (codeStr, actionTypePlural, identifierName) => {
209
215
 
210
216
  // Check if this object already has the actionType group inside it.
211
217
  const existingProp = appObj.properties.find(
212
- (props) => props.key.name === actionTypePlural,
218
+ (props) => props.key && props.key.name === actionTypePlural,
213
219
  );
214
220
  if (existingProp) {
215
221
  const value = existingProp.value;
216
- if (value.type !== 'ObjectExpression') {
222
+ if (value.type === 'Identifier') {
223
+ // Handle shorthand syntax like `creates` instead of `creates: { ... }`
224
+ // Transform it into an object with spread operator: `creates: { ...creates, [newAction.key]: newAction }`
225
+ const spreadProperty = j.spreadElement(j.identifier(value.name));
226
+ existingProp.value = j.objectExpression([spreadProperty, newProperty]);
227
+ existingProp.shorthand = false; // Disable shorthand since we're changing the value
228
+ } else if (value.type === 'ObjectExpression') {
229
+ value.properties.push(newProperty);
230
+ } else {
217
231
  throw new Error(
218
232
  `Tried to edit the ${actionTypePlural} key, but the value wasn't an object`,
219
233
  );
220
234
  }
221
- value.properties.push(newProperty);
222
235
  } else {
223
236
  appObj.properties.push(
224
237
  j.property(
@@ -126,6 +126,16 @@ const writeTemplateFile = async ({
126
126
  const getRelativeRequirePath = (entryFilePath, newFilePath) =>
127
127
  path.relative(path.dirname(entryFilePath), newFilePath);
128
128
 
129
+ /**
130
+ * Detect if a JavaScript file uses ES Module syntax (export default) vs CommonJS (module.exports)
131
+ * @param {string} codeStr - The JavaScript code to check
132
+ * @returns {boolean} - True if the file uses ESM syntax
133
+ */
134
+ const isEsmJavaScript = (codeStr) => {
135
+ // Look for export default statement
136
+ return /^\s*export\s+default\s/m.test(codeStr);
137
+ };
138
+
129
139
  const isValidEntryFileUpdate = (
130
140
  language,
131
141
  indexFileResolved,
@@ -193,16 +203,32 @@ const updateEntryFileJs = async ({
193
203
  let codeStr = (await readFile(indexFileResolved)).toString();
194
204
  const originalCodeStr = codeStr; // untouched copy in case we need to bail
195
205
 
196
- codeStr = importActionInJsApp(
197
- codeStr,
198
- actionImportName,
199
- actionRelativeImportPath,
200
- );
201
- codeStr = registerActionInJsApp(
202
- codeStr,
203
- plural(actionType),
204
- actionImportName,
205
- );
206
+ // Check if this JavaScript file uses ESM syntax (export default)
207
+ // If so, use the TypeScript functions which handle ESM correctly
208
+ if (isEsmJavaScript(codeStr)) {
209
+ codeStr = importActionInTsApp(
210
+ codeStr,
211
+ actionImportName,
212
+ actionRelativeImportPath,
213
+ );
214
+ codeStr = registerActionInTsApp(
215
+ codeStr,
216
+ plural(actionType),
217
+ actionImportName,
218
+ );
219
+ } else {
220
+ // Use traditional CommonJS functions for module.exports
221
+ codeStr = importActionInJsApp(
222
+ codeStr,
223
+ actionImportName,
224
+ actionRelativeImportPath,
225
+ );
226
+ codeStr = registerActionInJsApp(
227
+ codeStr,
228
+ plural(actionType),
229
+ actionImportName,
230
+ );
231
+ }
206
232
  await writeFile(indexFileResolved, codeStr);
207
233
  return originalCodeStr;
208
234
  };
@@ -283,11 +309,22 @@ const createScaffoldingContext = ({
283
309
  )}.test.${language}`;
284
310
  const testFileLocal = `${path.join(testDirLocal, key)}.${language}`;
285
311
  const testFileLocalStem = path.join(testDirLocal, key);
286
- const actionRelativeImportPath = `./${getRelativeRequirePath(
312
+
313
+ // Generate the relative import path
314
+ let actionRelativeImportPath = `./${getRelativeRequirePath(
287
315
  indexFileResolved,
288
316
  actionFileResolvedStem,
289
317
  )}`;
290
318
 
319
+ // Normalize path separators to forward slashes for import statements
320
+ // (ES modules always use forward slashes, regardless of OS)
321
+ actionRelativeImportPath = actionRelativeImportPath.replace(/\\/g, '/');
322
+
323
+ // For TypeScript with ESM, imports must use .js extension
324
+ if (language === 'ts') {
325
+ actionRelativeImportPath += '.js';
326
+ }
327
+
291
328
  return {
292
329
  actionType,
293
330
  actionTypePlural: plural(actionType),
@@ -324,6 +361,7 @@ module.exports = {
324
361
  updateEntryFile,
325
362
  isValidEntryFileUpdate,
326
363
  writeTemplateFile,
364
+ isEsmJavaScript,
327
365
  };
328
366
 
329
367
  /**