zapier-platform-cli 16.3.1 → 16.5.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.
@@ -5,19 +5,43 @@ const { buildFlags } = require('../buildFlags');
5
5
 
6
6
  const { listVersions } = require('../../utils/api');
7
7
 
8
+ const deploymentToLifecycleState = (deployment) => {
9
+ switch (deployment) {
10
+ case 'non-production':
11
+ return 'private';
12
+ case 'production':
13
+ return 'promoted';
14
+ case 'demoted':
15
+ return 'available';
16
+ case 'legacy':
17
+ return 'legacy';
18
+ case 'deprecating':
19
+ return 'deprecating';
20
+ case 'deprecated':
21
+ return 'deprecated';
22
+ default:
23
+ return deployment;
24
+ }
25
+ };
26
+
8
27
  class VersionCommand extends BaseCommand {
9
28
  async perform() {
10
29
  this.startSpinner('Loading versions');
11
30
  const { versions } = await listVersions();
12
31
  this.stopSpinner();
32
+ const rows = versions.map((v) => ({
33
+ ...v,
34
+ state: deploymentToLifecycleState(v.lifecycle.status),
35
+ }));
13
36
 
14
37
  this.logTable({
15
- rows: versions,
38
+ rows,
16
39
  headers: [
17
40
  ['Version', 'version'],
18
41
  ['Platform', 'platform_version'],
19
- ['Users', 'user_count'],
20
- ['Deployment', 'deployment'],
42
+ ['Zap Users', 'user_count'],
43
+ ['State', 'state'],
44
+ ['Legacy Date', 'legacy_date'],
21
45
  ['Deprecation Date', 'deprecation_date'],
22
46
  ['Timestamp', 'date'],
23
47
  ],
@@ -58,6 +82,6 @@ class VersionCommand extends BaseCommand {
58
82
 
59
83
  VersionCommand.skipValidInstallCheck = true;
60
84
  VersionCommand.flags = buildFlags({ opts: { format: true } });
61
- VersionCommand.description = `List the versions of your integration available for use in the Zapier editor.`;
85
+ VersionCommand.description = `List the versions of your integration available for use in Zapier automations.`;
62
86
 
63
87
  module.exports = VersionCommand;
@@ -26,6 +26,7 @@ module.exports = {
26
26
  integrations: require('./commands/integrations'),
27
27
  invoke: require('./commands/invoke'),
28
28
  link: require('./commands/link'),
29
+ legacy: require('./commands/legacy'),
29
30
  login: require('./commands/login'),
30
31
  logs: require('./commands/logs'),
31
32
  logout: require('./commands/logout'),
package/src/utils/api.js CHANGED
@@ -293,6 +293,11 @@ const getVersionInfo = () => {
293
293
  });
294
294
  };
295
295
 
296
+ const getSpecificVersionInfo = async (version) => {
297
+ const app = await getWritableApp();
298
+ return callAPI(`/apps/${app.id}/versions/${version}`);
299
+ };
300
+
296
301
  // Intended to match logic of https://gitlab.com/zapier/team-developer-platform/dev-platform/-/blob/9fa28d8bacd04ebdad5937bd039c71aede4ede47/web/frontend/assets/app/entities/CliApp/CliApp.ts#L96
297
302
  const isPublished = (appStatus) => {
298
303
  const publishedStatuses = ['public', 'beta'];
@@ -363,6 +368,8 @@ const listEnv = (version) =>
363
368
 
364
369
  const listMigrations = () => listEndpoint('migrations');
365
370
 
371
+ const listAuthentications = () => listEndpoint('authentications');
372
+
366
373
  // the goal of this is to call `/check` with as much info as possible
367
374
  // if the app is registered and auth is available, then we can send app id
368
375
  // otherwise, we should just send the definition and get back checks about that
@@ -500,8 +507,10 @@ module.exports = {
500
507
  getLinkedAppConfig,
501
508
  getWritableApp,
502
509
  getVersionInfo,
510
+ getSpecificVersionInfo,
503
511
  isPublished,
504
512
  listApps,
513
+ listAuthentications,
505
514
  listCanaries,
506
515
  listEndpoint,
507
516
  listEndpointMulti,
@@ -2,8 +2,231 @@ const _ = require('lodash');
2
2
  const path = require('path');
3
3
 
4
4
  const { findCorePackageDir } = require('./misc');
5
+ const { BASE_ENDPOINT } = require('../constants');
5
6
 
6
- const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
7
+ /**
8
+ * Wraps Node's http.request() / https.request() so that all requests go via a relay URL.
9
+ * It decides whether to use the http or https module based on the relay URL's protocol.
10
+ *
11
+ * @param {Function} originalHttpRequest - The original http.request function.
12
+ * @param {Function} originalHttpsRequest - The original https.request function.
13
+ * @param {string} relayUrl - The base URL to which we relay. (e.g., 'http://my-relay.test')
14
+ * @param {Object} relayHeaders - Extra headers to add to each request sent to the relay.
15
+ * @returns {Function} A function with the same signature as http(s).request that relays instead.
16
+ *
17
+ * Usage:
18
+ * const http = require('http');
19
+ * const https = require('https');
20
+ *
21
+ * // Replace https.request with our wrapped version:
22
+ * https.request = wrapHttpRequestFuncWithRelay(
23
+ * http.request,
24
+ * https.request,
25
+ * 'https://my-relay.test',
26
+ * { 'X-Relayed-By': 'MyRelayProxy' }
27
+ * );
28
+ *
29
+ * // Now, calling https.request('https://example.com/hello') will actually
30
+ * // send a request to "https://my-relay.test/example.com/hello"
31
+ * // with X-Relayed-By header attached.
32
+ */
33
+ function wrapHttpRequestFuncWithRelay(
34
+ originalHttpRequest,
35
+ originalHttpsRequest,
36
+ relayUrl,
37
+ relayHeaders,
38
+ ) {
39
+ const parsedRelayUrl = new URL(relayUrl);
40
+
41
+ // Decide if the relay itself is HTTP or HTTPS
42
+ const isRelayHttps = parsedRelayUrl.protocol === 'https:';
43
+
44
+ // Pick which request function to use to talk to the relay
45
+ const relayRequestFunc = isRelayHttps
46
+ ? originalHttpsRequest
47
+ : originalHttpRequest;
48
+
49
+ /**
50
+ * The actual wrapped request function.
51
+ * Accepts the same arguments as http(s).request:
52
+ * (options[, callback]) or (url[, options][, callback])
53
+ */
54
+ return function wrappedRequest(originalOptions, originalCallback) {
55
+ let options;
56
+ let callback;
57
+
58
+ // 1. Normalize arguments (string URL vs. options object)
59
+ if (typeof originalOptions === 'string') {
60
+ // Called like request(urlString, [...])
61
+ try {
62
+ const parsedOriginalUrl = new URL(originalOptions);
63
+
64
+ if (typeof originalCallback === 'object') {
65
+ // request(urlString, optionsObject, callback)
66
+ options = { ...originalCallback };
67
+ callback = arguments[2];
68
+ } else {
69
+ // request(urlString, callback) or request(urlString)
70
+ options = {};
71
+ callback = originalCallback;
72
+ }
73
+
74
+ // Merge in the URL parts if not explicitly set in options
75
+ options.protocol = options.protocol || parsedOriginalUrl.protocol;
76
+ options.hostname = options.hostname || parsedOriginalUrl.hostname;
77
+ options.port = options.port || parsedOriginalUrl.port;
78
+ options.path =
79
+ options.path || parsedOriginalUrl.pathname + parsedOriginalUrl.search;
80
+ } catch (err) {
81
+ // If it's not a valid absolute URL, treat it as a path
82
+ // or re-throw if you prefer strictness.
83
+ options = {};
84
+ callback = originalCallback;
85
+ options.path = originalOptions;
86
+ }
87
+ } else {
88
+ // Called like request(optionsObject, [callback])
89
+ options = { ...originalOptions };
90
+ callback = originalCallback;
91
+ }
92
+
93
+ // 2. Default method and headers
94
+ if (!options.method) {
95
+ options.method = 'GET';
96
+ }
97
+ if (!options.headers) {
98
+ options.headers = {};
99
+ }
100
+
101
+ // 3. Decide whether to relay or not
102
+ // If the request is being sent to the same host:port as our relay,
103
+ // we do NOT want to relay again. We just call the original request.
104
+ const targetHost = (options.hostname || options.host)
105
+ .replaceAll('lcurly-', '{{')
106
+ .replaceAll('-rcurly', '}}');
107
+ const targetPort = options.port ? String(options.port) : '';
108
+ const relayHost = parsedRelayUrl.hostname;
109
+ const relayPort = parsedRelayUrl.port ? String(parsedRelayUrl.port) : '';
110
+
111
+ const isAlreadyRelay =
112
+ targetHost === relayHost &&
113
+ // If no port was specified, assume default port comparison as needed
114
+ (targetPort === relayPort || (!targetPort && !relayPort));
115
+
116
+ if (isAlreadyRelay) {
117
+ // Just call the original function; do *not* re-relay
118
+ const originalFn =
119
+ options.protocol === 'https:'
120
+ ? originalHttpsRequest
121
+ : originalHttpRequest;
122
+ return originalFn(options, callback);
123
+ }
124
+
125
+ // 4. Otherwise, build the path we want to relay to
126
+ let finalHost = targetHost;
127
+ if (targetPort && targetPort !== '443') {
128
+ finalHost += `:${targetPort}`;
129
+ }
130
+ const combinedPath = `${parsedRelayUrl.pathname}/${finalHost}${options.path}`;
131
+
132
+ // 5. Build final options for the relay request
133
+ const relayedOptions = {
134
+ protocol: parsedRelayUrl.protocol,
135
+ hostname: relayHost,
136
+ port: relayPort,
137
+ path: combinedPath,
138
+ method: options.method,
139
+ headers: {
140
+ ...options.headers,
141
+ ...relayHeaders,
142
+ },
143
+ };
144
+
145
+ // 6. Make the relay request
146
+ const relayReq = relayRequestFunc(relayedOptions, callback);
147
+ return relayReq;
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Wraps a fetch function so that all requests get relayed to a specified relay URL.
153
+ * The final relay URL includes: relayUrl + "/" + originalHost + originalPath
154
+ *
155
+ * @param {Function} fetchFunc - The original fetch function (e.g., global.fetch).
156
+ * @param {string} relayUrl - The base URL to which we relay. (e.g. 'https://my-relay.test')
157
+ * @param {Object} relayHeaders - Extra headers to add to each request sent to the relay.
158
+ * @returns {Function} A function with the same signature as `fetch(url, options)`.
159
+ *
160
+ * Usage:
161
+ * const wrappedFetch = wrapFetchWithRelay(
162
+ * fetch,
163
+ * 'https://my-relay.test',
164
+ * { 'X-Relayed-By': 'MyRelayProxy' },
165
+ * );
166
+ *
167
+ * // Now when you do:
168
+ * // wrappedFetch('https://example.com/api/user?id=123', { method: 'POST' });
169
+ * // it actually sends a request to:
170
+ * // https://my-relay.test/example.com/api/user?id=123
171
+ * // with "X-Relayed-By" header included.
172
+ */
173
+ function wrapFetchWithRelay(fetchFunc, relayUrl, relayHeaders) {
174
+ const parsedRelayUrl = new URL(relayUrl);
175
+
176
+ return async function wrappedFetch(originalUrl, originalOptions = {}) {
177
+ // Attempt to parse the originalUrl as an absolute URL
178
+ const parsedOriginalUrl = new URL(originalUrl);
179
+
180
+ // Build the portion that includes the original host (and port if present)
181
+ let host = parsedOriginalUrl.hostname;
182
+ // If there's a port that isn't 443 (for HTTPS) or 80 (for HTTP), append it
183
+ // (Adjust to your preferences; here we loosely check for 443 only.)
184
+ if (parsedOriginalUrl.port && parsedOriginalUrl.port.toString() !== '443') {
185
+ host += `:${parsedOriginalUrl.port}`;
186
+ }
187
+
188
+ const isAlreadyRelay =
189
+ parsedOriginalUrl.hostname === parsedRelayUrl.hostname &&
190
+ parsedOriginalUrl.port === parsedRelayUrl.port;
191
+ if (isAlreadyRelay) {
192
+ // Just call the original fetch function; do *not* re-relay
193
+ return fetchFunc(originalUrl, originalOptions);
194
+ }
195
+
196
+ // Combine the relay's pathname with the "host + path" from the original
197
+ // For example: relayUrl = http://my-relay.test
198
+ // => parsedRelayUrl.pathname might be '/'
199
+ // => combinedPath = '/example.com:8080/some/path'
200
+ const combinedPath = `${parsedRelayUrl.pathname}/${host}${parsedOriginalUrl.pathname}`;
201
+
202
+ // Merge in the search strings: the relay's own search (if any) plus the original URL's search
203
+ const finalUrl = `${parsedRelayUrl.origin}${combinedPath}${parsedRelayUrl.search}${parsedOriginalUrl.search}`;
204
+
205
+ // Merge the user's headers with the relayHeaders
206
+ const mergedHeaders = {
207
+ ...(originalOptions.headers || {}),
208
+ ...relayHeaders,
209
+ };
210
+
211
+ const finalOptions = {
212
+ ...originalOptions,
213
+ headers: mergedHeaders,
214
+ };
215
+
216
+ // Call the real fetch with our new URL and merged options
217
+ return fetchFunc(finalUrl, finalOptions);
218
+ };
219
+ }
220
+
221
+ const getLocalAppHandler = ({
222
+ reload = false,
223
+ baseEvent = {},
224
+ appId = null,
225
+ deployKey = null,
226
+ relayAuthenticationId = null,
227
+ beforeRequest = null,
228
+ afterResponse = null,
229
+ } = {}) => {
7
230
  const entryPath = `${process.cwd()}/index`;
8
231
  const rootPath = path.dirname(require.resolve(entryPath));
9
232
  const corePackageDir = findCorePackageDir();
@@ -24,6 +247,41 @@ const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
24
247
  // maybe we could do require('syntax-error') in the future
25
248
  return (event, ctx, callback) => callback(err);
26
249
  }
250
+
251
+ if (beforeRequest) {
252
+ appRaw.beforeRequest = [...(appRaw.beforeRequest || []), ...beforeRequest];
253
+ }
254
+ if (afterResponse) {
255
+ appRaw.afterResponse = [...afterResponse, ...(appRaw.afterResponse || [])];
256
+ }
257
+
258
+ if (appId && deployKey && relayAuthenticationId) {
259
+ const relayUrl = `${BASE_ENDPOINT}/api/platform/cli/apps/${appId}/relay`;
260
+ const relayHeaders = {
261
+ 'x-relay-authentication-id': relayAuthenticationId,
262
+ 'x-deploy-key': deployKey,
263
+ };
264
+
265
+ const http = require('http');
266
+ const https = require('https');
267
+ const origHttpRequest = http.request;
268
+ const origHttpsRequest = https.request;
269
+ http.request = wrapHttpRequestFuncWithRelay(
270
+ origHttpRequest,
271
+ origHttpsRequest,
272
+ relayUrl,
273
+ relayHeaders,
274
+ );
275
+ https.request = wrapHttpRequestFuncWithRelay(
276
+ origHttpRequest,
277
+ origHttpsRequest,
278
+ relayUrl,
279
+ relayHeaders,
280
+ );
281
+
282
+ global.fetch = wrapFetchWithRelay(global.fetch, relayUrl, relayHeaders);
283
+ }
284
+
27
285
  const handler = zapier.createAppHandler(appRaw);
28
286
  return (event, ctx, callback) => {
29
287
  event = _.merge(
@@ -40,7 +298,13 @@ const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
40
298
 
41
299
  // Runs a local app command (./index.js) like {command: 'validate'};
42
300
  const localAppCommand = (event) => {
43
- const handler = getLocalAppHandler();
301
+ const handler = getLocalAppHandler({
302
+ appId: event.appId,
303
+ deployKey: event.deployKey,
304
+ relayAuthenticationId: event.relayAuthenticationId,
305
+ beforeRequest: event.beforeRequest,
306
+ afterResponse: event.afterResponse,
307
+ });
44
308
  return new Promise((resolve, reject) => {
45
309
  handler(event, {}, (err, resp) => {
46
310
  if (err) {