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.
- package/oclif.manifest.json +203 -134
- package/package.json +1 -1
- package/src/oclif/ZapierBaseCommand.js +1 -1
- package/src/oclif/commands/deprecate.js +42 -8
- package/src/oclif/commands/invoke.js +278 -14
- package/src/oclif/commands/legacy.js +61 -0
- package/src/oclif/commands/promote.js +0 -5
- package/src/oclif/commands/versions.js +28 -4
- package/src/oclif/oCommands.js +1 -0
- package/src/utils/api.js +9 -0
- package/src/utils/local.js +266 -2
|
@@ -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
|
|
38
|
+
rows,
|
|
16
39
|
headers: [
|
|
17
40
|
['Version', 'version'],
|
|
18
41
|
['Platform', 'platform_version'],
|
|
19
|
-
['Users', 'user_count'],
|
|
20
|
-
['
|
|
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
|
|
85
|
+
VersionCommand.description = `List the versions of your integration available for use in Zapier automations.`;
|
|
62
86
|
|
|
63
87
|
module.exports = VersionCommand;
|
package/src/oclif/oCommands.js
CHANGED
|
@@ -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,
|
package/src/utils/local.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|