avo 2.0.2 → 3.0.0-beta.2
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/Avo.js +771 -1038
- package/README.md +7 -7
- package/cli.js +1676 -2087
- package/package.json +40 -27
package/cli.js
CHANGED
|
@@ -1,2321 +1,1910 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import chalk from 'chalk'
|
|
3
|
+
import chalk from 'chalk';
|
|
4
4
|
import minimatch from 'minimatch';
|
|
5
|
-
import _ from 'lodash';
|
|
6
5
|
import dateFns from 'date-fns';
|
|
7
6
|
import fs from 'fs';
|
|
8
7
|
import http from 'http';
|
|
9
8
|
import inquirer from 'inquirer';
|
|
10
9
|
import jwt from 'jsonwebtoken';
|
|
11
|
-
import loadJsonFile from 'load-json-file';
|
|
10
|
+
import { loadJsonFile } from 'load-json-file';
|
|
12
11
|
import logSymbols from 'log-symbols';
|
|
13
|
-
import
|
|
12
|
+
import open from 'open';
|
|
14
13
|
import path from 'path';
|
|
15
14
|
import pify from 'pify';
|
|
16
15
|
import portfinder from 'portfinder';
|
|
17
16
|
import querystring from 'querystring';
|
|
18
|
-
import
|
|
17
|
+
import got from 'got'; // eslint-disable-line import/no-unresolved
|
|
19
18
|
import report from 'yurnalist';
|
|
20
19
|
import semver from 'semver';
|
|
21
20
|
import updateNotifier from 'update-notifier';
|
|
22
21
|
import url from 'url';
|
|
23
22
|
import util from 'util';
|
|
24
|
-
import {v4 as uuidv4} from 'uuid';
|
|
23
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
25
24
|
import walk from 'ignore-walk';
|
|
26
25
|
import writeFile from 'write';
|
|
27
|
-
import writeJsonFile from 'write-json-file';
|
|
26
|
+
import { writeJsonFile } from 'write-json-file';
|
|
28
27
|
import Configstore from 'configstore';
|
|
29
|
-
import
|
|
28
|
+
import { AvoInspector, AvoInspectorEnv } from 'node-avo-inspector';
|
|
30
29
|
import yargs from 'yargs';
|
|
31
|
-
import { hideBin } from 'yargs/helpers'
|
|
30
|
+
import { hideBin } from 'yargs/helpers';
|
|
32
31
|
import httpShutdown from 'http-shutdown';
|
|
33
32
|
import fuzzypath from 'inquirer-fuzzy-path';
|
|
34
|
-
|
|
35
33
|
import Avo from './Avo.js';
|
|
36
|
-
const pkg = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url)));
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url), 'utf-8'));
|
|
35
|
+
const { Minimatch } = minimatch;
|
|
36
|
+
/// //////////////////////////////////////////////////////////////////////
|
|
37
|
+
// LOGGING
|
|
38
|
+
const { cyan, gray, red, bold, underline } = chalk;
|
|
39
|
+
function cmd(command) {
|
|
40
|
+
return `${gray('`')}${cyan(command)}${gray('`')}`;
|
|
41
|
+
}
|
|
42
|
+
function link(text) {
|
|
43
|
+
return underline(text);
|
|
44
|
+
}
|
|
45
|
+
function file(text) {
|
|
46
|
+
return underline(text);
|
|
47
|
+
}
|
|
48
|
+
function email(text) {
|
|
49
|
+
return underline(text);
|
|
50
|
+
}
|
|
51
|
+
// to cancel spinners globally
|
|
52
|
+
let _cancel = null;
|
|
53
|
+
let cancelWait = () => {
|
|
54
|
+
if (_cancel !== null) {
|
|
55
|
+
_cancel();
|
|
56
|
+
_cancel = null;
|
|
57
|
+
}
|
|
60
58
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
function wait(message, timeOut = 300) {
|
|
60
|
+
cancelWait();
|
|
61
|
+
let running = false;
|
|
62
|
+
let spinner;
|
|
63
|
+
let stopped = false;
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
if (stopped)
|
|
66
|
+
return;
|
|
67
|
+
spinner = ora(gray(message));
|
|
68
|
+
spinner.color = 'gray';
|
|
69
|
+
spinner.start();
|
|
70
|
+
running = true;
|
|
71
|
+
}, timeOut);
|
|
72
|
+
const cancel = () => {
|
|
73
|
+
stopped = true;
|
|
74
|
+
if (running) {
|
|
75
|
+
spinner.stop();
|
|
76
|
+
running = false;
|
|
77
|
+
}
|
|
78
|
+
process.removeListener('nowExit', cancel);
|
|
79
|
+
};
|
|
80
|
+
process.on('nowExit', cancel);
|
|
81
|
+
cancelWait = cancel;
|
|
82
|
+
}
|
|
74
83
|
// register inquirer-file-path
|
|
75
84
|
inquirer.registerPrompt('fuzzypath', fuzzypath);
|
|
76
|
-
|
|
77
|
-
updateNotifier({pkg: pkg}).notify();
|
|
78
|
-
|
|
85
|
+
updateNotifier({ pkg }).notify();
|
|
79
86
|
const conf = new Configstore(pkg.name);
|
|
80
|
-
|
|
81
87
|
if (!conf.has('avo_install_id')) {
|
|
82
|
-
|
|
88
|
+
conf.set('avo_install_id', uuidv4());
|
|
83
89
|
}
|
|
84
|
-
|
|
85
90
|
const FIFTEEN_MINUTES_IN_MS = 15 * 60 * 1000;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const nonce = (1 + Math.random() * (2 << 29)).toString();
|
|
92
|
+
function isString(str) {
|
|
93
|
+
if (str != null && typeof str.valueOf() === 'string') {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const sum = (base, value) => base + value;
|
|
92
99
|
portfinder.basePort = 9005;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.exit = options.exit || 1;
|
|
103
|
-
this.stack = new Error().stack;
|
|
104
|
-
this.original = options.original;
|
|
105
|
-
this.context = options.context;
|
|
100
|
+
const _getPort = portfinder.getPortPromise;
|
|
101
|
+
function AvoError(message, options = {}) {
|
|
102
|
+
this.name = 'AvoError';
|
|
103
|
+
this.message = message;
|
|
104
|
+
this.status = options.status ?? 500;
|
|
105
|
+
this.exit = options.exit ?? 1;
|
|
106
|
+
this.stack = new Error().stack;
|
|
107
|
+
this.original = options.original;
|
|
108
|
+
this.context = options.context;
|
|
106
109
|
}
|
|
107
110
|
AvoError.prototype = Object.create(Error.prototype);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{exit: 1}
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// in-memory cache, so we have it for successive calls
|
|
117
|
-
var lastAccessToken = {};
|
|
118
|
-
var accessToken;
|
|
119
|
-
var refreshToken;
|
|
120
|
-
var commandScopes;
|
|
121
|
-
|
|
122
|
-
/////////////////////////////////////////////////////////////////////////
|
|
111
|
+
const INVALID_CREDENTIAL_ERROR = new AvoError(`Authentication Error: Your credentials are no longer valid. Please run ${cmd('avo logout; avo login')}`, { exit: 1 });
|
|
112
|
+
let lastAccessToken = {};
|
|
113
|
+
let accessToken;
|
|
114
|
+
let refreshToken;
|
|
115
|
+
/// //////////////////////////////////////////////////////////////////////
|
|
123
116
|
// REQUEST HANDLING
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (response.statusCode >= 400 && !logOptions.skipResponseBody) {
|
|
144
|
-
if (!options.resolveOnHTTPError) {
|
|
145
|
-
return reject(responseToError(response, body, options));
|
|
117
|
+
function responseToError(response) {
|
|
118
|
+
let { body } = response;
|
|
119
|
+
if (typeof body === 'string' && response.statusCode === 404) {
|
|
120
|
+
body = {
|
|
121
|
+
error: {
|
|
122
|
+
message: 'Not Found',
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (response.statusCode < 400) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (typeof body !== 'object') {
|
|
130
|
+
try {
|
|
131
|
+
body = JSON.parse(body);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
body = {};
|
|
146
135
|
}
|
|
147
|
-
}
|
|
148
|
-
return resolve({
|
|
149
|
-
status: response.statusCode,
|
|
150
|
-
response: response,
|
|
151
|
-
body: body
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (_.size(options.files) > 0) {
|
|
156
|
-
var form = req.form();
|
|
157
|
-
_.forEach(options.files, function(details, param) {
|
|
158
|
-
form.append(param, details.stream, {
|
|
159
|
-
knownLength: details.knownLength,
|
|
160
|
-
filename: details.filename,
|
|
161
|
-
contentType: details.contentType
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
136
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (data && _.size(data) > 0) {
|
|
170
|
-
path += _.includes(path, '?') ? '&' : '?';
|
|
171
|
-
path += querystring.stringify(data);
|
|
172
|
-
}
|
|
173
|
-
return path;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
var api = {
|
|
177
|
-
authOrigin: 'https://www.avo.app',
|
|
178
|
-
|
|
179
|
-
apiOrigin: 'https://api.avo.app',
|
|
180
|
-
|
|
181
|
-
setRefreshToken: function(token) {
|
|
182
|
-
refreshToken = token;
|
|
183
|
-
},
|
|
184
|
-
setAccessToken: function(token) {
|
|
185
|
-
accessToken = token;
|
|
186
|
-
},
|
|
187
|
-
getAccessToken: function() {
|
|
188
|
-
return accessToken
|
|
189
|
-
? Promise.resolve({idToken: accessToken})
|
|
190
|
-
: getAccessToken(refreshToken, commandScopes);
|
|
191
|
-
},
|
|
192
|
-
addRequestHeaders: function(reqOptions) {
|
|
193
|
-
// Runtime fetch of Auth singleton to prevent circular module dependencies
|
|
194
|
-
_.set(reqOptions, ['headers', 'User-Agent'], 'AvoCLI/' + pkg.version);
|
|
195
|
-
_.set(reqOptions, ['headers', 'X-Client-Version'], 'AvoCLI/' + pkg.version);
|
|
196
|
-
return api.getAccessToken().then(function(result) {
|
|
197
|
-
_.set(reqOptions, 'headers.authorization', 'Bearer ' + result.idToken);
|
|
198
|
-
return reqOptions;
|
|
199
|
-
});
|
|
200
|
-
},
|
|
201
|
-
request: function(method, resource, options) {
|
|
202
|
-
options = _.extend(
|
|
203
|
-
{
|
|
204
|
-
data: {},
|
|
205
|
-
origin: undefined, // origin must be set
|
|
206
|
-
resolveOnHTTPError: false, // by default, status codes >= 400 leads to reject
|
|
207
|
-
json: true,
|
|
208
|
-
gzip: true
|
|
209
|
-
},
|
|
210
|
-
options
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
var validMethods = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'];
|
|
214
|
-
|
|
215
|
-
if (validMethods.indexOf(method) < 0) {
|
|
216
|
-
method = 'GET';
|
|
137
|
+
if (!body.error) {
|
|
138
|
+
body.error = {
|
|
139
|
+
message: response.statusCode === 404 ? 'Not Found' : 'Unknown Error',
|
|
140
|
+
};
|
|
217
141
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (options.query) {
|
|
224
|
-
resource = _appendQueryData(resource, options.query);
|
|
142
|
+
const message = `HTTP Error: ${response.statusCode}, ${body.error.message ?? body.error}`;
|
|
143
|
+
let exitCode;
|
|
144
|
+
if (response.statusCode >= 500) {
|
|
145
|
+
// 5xx errors are unexpected
|
|
146
|
+
exitCode = 2;
|
|
225
147
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
} else {
|
|
230
|
-
if (_.size(options.data) > 0) {
|
|
231
|
-
reqOptions.body = options.data;
|
|
232
|
-
} else if (_.size(options.form) > 0) {
|
|
233
|
-
reqOptions.form = options.form;
|
|
234
|
-
}
|
|
148
|
+
else {
|
|
149
|
+
// 4xx errors happen sometimes
|
|
150
|
+
exitCode = 1;
|
|
235
151
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
152
|
+
delete response.request.headers;
|
|
153
|
+
return new AvoError(message, {
|
|
154
|
+
context: {
|
|
155
|
+
body,
|
|
156
|
+
response,
|
|
157
|
+
},
|
|
158
|
+
exit: exitCode,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function _request(options) {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
got(options)
|
|
164
|
+
.then((response) => {
|
|
165
|
+
if (response.statusCode >= 400) {
|
|
166
|
+
return reject(responseToError(response));
|
|
167
|
+
}
|
|
168
|
+
return resolve(JSON.parse(response.body));
|
|
169
|
+
})
|
|
170
|
+
.catch((err) => reject(new AvoError(`Server Error. ${err.message}`, {
|
|
171
|
+
original: err,
|
|
172
|
+
exit: 2,
|
|
173
|
+
})));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const _appendQueryData = (urlPath, data) => {
|
|
177
|
+
let returnPath = urlPath;
|
|
178
|
+
if (data && Object.keys(data).length > 0) {
|
|
179
|
+
returnPath += returnPath.includes('?') ? '&' : '?';
|
|
180
|
+
returnPath += querystring.stringify(data);
|
|
257
181
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
182
|
+
return returnPath;
|
|
183
|
+
};
|
|
184
|
+
function _refreshAccessToken(refreshToken) {
|
|
185
|
+
return api // eslint-disable-line
|
|
186
|
+
.request('POST', '/auth/refresh', {
|
|
187
|
+
origin: api.apiOrigin,
|
|
188
|
+
json: {
|
|
189
|
+
token: refreshToken,
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
.then((data) => {
|
|
193
|
+
if (!isString(data.idToken)) {
|
|
194
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
195
|
+
}
|
|
196
|
+
lastAccessToken = {
|
|
197
|
+
expiresAt: Date.now() + data.expiresIn * 1000,
|
|
198
|
+
refreshToken,
|
|
199
|
+
...data,
|
|
200
|
+
};
|
|
201
|
+
const currentRefreshToken = conf.get('tokens').refreshToken;
|
|
202
|
+
if (refreshToken === currentRefreshToken) {
|
|
203
|
+
conf.set('tokens', lastAccessToken);
|
|
204
|
+
}
|
|
205
|
+
return lastAccessToken;
|
|
206
|
+
}, () => {
|
|
207
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
272
208
|
});
|
|
273
|
-
|
|
209
|
+
}
|
|
210
|
+
function _haveValidAccessToken(refreshToken) {
|
|
211
|
+
if (Object.keys(lastAccessToken).length === 0) {
|
|
212
|
+
const tokens = conf.get('tokens');
|
|
213
|
+
if (refreshToken === tokens.refreshToken) {
|
|
214
|
+
lastAccessToken = tokens;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return (lastAccessToken.idToken &&
|
|
218
|
+
lastAccessToken.refreshToken === refreshToken &&
|
|
219
|
+
lastAccessToken.expiresAt &&
|
|
220
|
+
lastAccessToken.expiresAt > Date.now() + FIFTEEN_MINUTES_IN_MS);
|
|
221
|
+
}
|
|
222
|
+
function getAccessToken(refreshToken) {
|
|
223
|
+
if (_haveValidAccessToken(refreshToken)) {
|
|
224
|
+
return Promise.resolve(lastAccessToken);
|
|
225
|
+
}
|
|
226
|
+
return _refreshAccessToken(refreshToken);
|
|
227
|
+
}
|
|
228
|
+
const api = {
|
|
229
|
+
authOrigin: 'https://www.avo.app',
|
|
230
|
+
apiOrigin: 'https://api.avo.app',
|
|
231
|
+
setRefreshToken(token) {
|
|
232
|
+
refreshToken = token;
|
|
233
|
+
},
|
|
234
|
+
setAccessToken(token) {
|
|
235
|
+
accessToken = token;
|
|
236
|
+
},
|
|
237
|
+
getAccessToken() {
|
|
238
|
+
return accessToken
|
|
239
|
+
? Promise.resolve({ idToken: accessToken })
|
|
240
|
+
: getAccessToken(refreshToken);
|
|
241
|
+
},
|
|
242
|
+
addRequestHeaders(reqOptions) {
|
|
243
|
+
// Runtime fetch of Auth singleton to prevent circular module dependencies
|
|
244
|
+
return api.getAccessToken().then((result) => ({
|
|
245
|
+
...reqOptions,
|
|
246
|
+
headers: {
|
|
247
|
+
...reqOptions.headers,
|
|
248
|
+
'User-Agent': `AvoCLI/${pkg.version}`,
|
|
249
|
+
'X-Client-Version': `AvoCLI/${pkg.version}`,
|
|
250
|
+
authorization: `Bearer ${result.idToken}`,
|
|
251
|
+
},
|
|
252
|
+
}));
|
|
253
|
+
},
|
|
254
|
+
request(method, resource, options) {
|
|
255
|
+
const validMethods = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'];
|
|
256
|
+
const reqOptions = {
|
|
257
|
+
method: validMethods.includes(method) ? method : 'GET',
|
|
258
|
+
decompress: true,
|
|
259
|
+
headers: options.headers ?? {},
|
|
260
|
+
};
|
|
261
|
+
let urlPath = resource;
|
|
262
|
+
if (options.query) {
|
|
263
|
+
urlPath = _appendQueryData(urlPath, options.query);
|
|
264
|
+
}
|
|
265
|
+
if (reqOptions.method === 'GET') {
|
|
266
|
+
urlPath = _appendQueryData(urlPath, options.json);
|
|
267
|
+
}
|
|
268
|
+
else if (Object.keys(options.json).length > 0) {
|
|
269
|
+
reqOptions.json = options.json;
|
|
270
|
+
}
|
|
271
|
+
else if (Object.keys(options.form).length > 0) {
|
|
272
|
+
reqOptions.form = options.form;
|
|
273
|
+
}
|
|
274
|
+
reqOptions.url = options.origin + urlPath;
|
|
275
|
+
let requestFunction = () => _request(reqOptions);
|
|
276
|
+
if (options.auth === true) {
|
|
277
|
+
requestFunction = () => api
|
|
278
|
+
.addRequestHeaders(reqOptions)
|
|
279
|
+
.then((reqOptionsWithToken) => _request(reqOptionsWithToken));
|
|
280
|
+
}
|
|
281
|
+
return requestFunction().catch((err) => {
|
|
282
|
+
if (options.retryCodes &&
|
|
283
|
+
options.retryCodes.includes(err.context.response.statusCode)) {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
setTimeout(resolve, 1000);
|
|
286
|
+
}).then(requestFunction);
|
|
287
|
+
}
|
|
288
|
+
return Promise.reject(err);
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
const customAnalyticsDestination = {
|
|
293
|
+
make: function make(production) {
|
|
294
|
+
this.production = production;
|
|
295
|
+
},
|
|
296
|
+
logEvent: (userId, eventName, eventProperties) => {
|
|
297
|
+
api
|
|
298
|
+
.request('POST', '/c/v1/track', {
|
|
299
|
+
origin: api.apiOrigin,
|
|
300
|
+
json: {
|
|
301
|
+
userId,
|
|
302
|
+
eventName,
|
|
303
|
+
eventProperties,
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
.catch(() => {
|
|
307
|
+
// don't crash on tracking errors
|
|
308
|
+
});
|
|
309
|
+
return undefined;
|
|
310
|
+
},
|
|
311
|
+
setUserProperties: () => undefined, // noop
|
|
274
312
|
};
|
|
275
|
-
|
|
313
|
+
const inspector = new AvoInspector({
|
|
314
|
+
apiKey: '3UWtteG9HenZ825cYoYr',
|
|
315
|
+
env: AvoInspectorEnv.Prod,
|
|
316
|
+
version: pkg.version,
|
|
317
|
+
appName: 'Avo CLI',
|
|
318
|
+
});
|
|
319
|
+
// setup Avo analytics
|
|
320
|
+
Avo.initAvo({ env: Avo.AvoEnv.Prod, inspector }, { client: Avo.Client.CLI, version: pkg.version }, {}, customAnalyticsDestination);
|
|
276
321
|
function isLegacyAvoJson(json) {
|
|
277
|
-
|
|
278
|
-
|
|
322
|
+
// check if legacy avo.json or un-initialized project
|
|
323
|
+
return json.types ?? !json.schema;
|
|
279
324
|
}
|
|
280
|
-
|
|
281
325
|
function avoNeedsUpdate(json) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
json.avo && json.avo.version && semver.major(pkg.version) < json.avo.version
|
|
285
|
-
);
|
|
326
|
+
// if avo.json has version, and this binary has lower version number it needs updating
|
|
327
|
+
return (json.avo && json.avo.version && semver.major(pkg.version) < json.avo.version);
|
|
286
328
|
}
|
|
287
|
-
|
|
288
329
|
const MERGE_CONFLICT_ANCESTOR = '|||||||';
|
|
289
330
|
const MERGE_CONFLICT_END = '>>>>>>>';
|
|
290
331
|
const MERGE_CONFLICT_SEP = '=======';
|
|
291
332
|
const MERGE_CONFLICT_START = '<<<<<<<';
|
|
292
333
|
function hasMergeConflicts(str) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
str.includes(MERGE_CONFLICT_END)
|
|
297
|
-
);
|
|
334
|
+
return (str.includes(MERGE_CONFLICT_START) &&
|
|
335
|
+
str.includes(MERGE_CONFLICT_SEP) &&
|
|
336
|
+
str.includes(MERGE_CONFLICT_END));
|
|
298
337
|
}
|
|
299
|
-
|
|
300
338
|
function extractConflictingFiles(str) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
339
|
+
const files = [[], []];
|
|
340
|
+
const lines = str.split(/\r?\n/g);
|
|
341
|
+
let skip = false;
|
|
342
|
+
while (lines.length) {
|
|
343
|
+
const line = lines.shift();
|
|
344
|
+
if (line.startsWith(MERGE_CONFLICT_START)) {
|
|
345
|
+
while (lines.length) {
|
|
346
|
+
const conflictLine = lines.shift();
|
|
347
|
+
if (conflictLine === MERGE_CONFLICT_SEP) {
|
|
348
|
+
skip = false;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
|
|
352
|
+
skip = true;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
files[0].push(conflictLine);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
while (lines.length) {
|
|
359
|
+
const conflictLine = lines.shift();
|
|
360
|
+
if (conflictLine.startsWith(MERGE_CONFLICT_END)) {
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
files[1].push(conflictLine);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
318
367
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const conflictLine = lines.shift();
|
|
323
|
-
if (conflictLine.startsWith(MERGE_CONFLICT_END)) {
|
|
324
|
-
break;
|
|
325
|
-
} else {
|
|
326
|
-
files[1].push(conflictLine);
|
|
368
|
+
else {
|
|
369
|
+
files[0].push(line);
|
|
370
|
+
files[1].push(line);
|
|
327
371
|
}
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
files[0].push(line);
|
|
331
|
-
files[1].push(line);
|
|
332
372
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return [files[0].join('\n'), files[1].join('\n')];
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function validateAvoJson(json) {
|
|
339
|
-
if (avoNeedsUpdate(json)) {
|
|
340
|
-
throw new AvoError(`Your avo CLI is outdated, please update`);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (isLegacyAvoJson(json)) {
|
|
344
|
-
return init();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// augment the latest major version into avo.json
|
|
348
|
-
json.avo = Object.assign({}, json.avo || {}, {
|
|
349
|
-
version: semver.major(pkg.version)
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
return json;
|
|
373
|
+
return [files[0].join('\n'), files[1].join('\n')];
|
|
353
374
|
}
|
|
354
|
-
|
|
355
375
|
const BRANCH_UP_TO_DATE = 'branch-up-to-date';
|
|
356
376
|
const BRANCH_NOT_UP_TO_DATE = 'branch-not-up-to-date';
|
|
357
|
-
|
|
358
377
|
function getMasterStatus(json) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
if (json.branch.id === 'master') {
|
|
379
|
+
return Promise.resolve(BRANCH_UP_TO_DATE);
|
|
380
|
+
}
|
|
362
381
|
return api
|
|
363
|
-
|
|
382
|
+
.request('POST', '/c/v1/master', {
|
|
364
383
|
origin: api.apiOrigin,
|
|
365
384
|
auth: true,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return res.body.pullRequired
|
|
373
|
-
? BRANCH_NOT_UP_TO_DATE
|
|
374
|
-
: BRANCH_UP_TO_DATE;
|
|
375
|
-
});
|
|
376
|
-
}
|
|
385
|
+
json: {
|
|
386
|
+
schemaId: json.schema.id,
|
|
387
|
+
branchId: json.branch.id,
|
|
388
|
+
},
|
|
389
|
+
})
|
|
390
|
+
.then(({ pullRequired }) => pullRequired ? BRANCH_NOT_UP_TO_DATE : BRANCH_UP_TO_DATE);
|
|
377
391
|
}
|
|
378
|
-
|
|
379
392
|
function pullMaster(json) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
393
|
+
if (json.branch.name === 'main') {
|
|
394
|
+
report.info('Your current branch is main');
|
|
395
|
+
return Promise.resolve(json);
|
|
396
|
+
}
|
|
384
397
|
wait(json.force ? 'Force pulling main into branch' : 'Pulling main into branch');
|
|
385
398
|
return api
|
|
386
|
-
|
|
399
|
+
.request('POST', '/c/v1/master/pull', {
|
|
387
400
|
origin: api.apiOrigin,
|
|
388
401
|
auth: true,
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
402
|
+
json: {
|
|
403
|
+
schemaId: json.schema.id,
|
|
404
|
+
branchId: json.branch.id,
|
|
405
|
+
force: json.force,
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
.then(() => {
|
|
396
409
|
cancelWait();
|
|
397
410
|
report.success('Branch is up to date with main');
|
|
398
411
|
return json;
|
|
399
|
-
|
|
400
|
-
}
|
|
412
|
+
});
|
|
401
413
|
}
|
|
402
|
-
|
|
403
414
|
function promptPullMaster(json) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
415
|
+
wait('Check if branch is up to date with main');
|
|
416
|
+
return getMasterStatus(json)
|
|
417
|
+
.then((branchStatus) => {
|
|
418
|
+
cancelWait();
|
|
419
|
+
if (branchStatus === BRANCH_NOT_UP_TO_DATE) {
|
|
420
|
+
return inquirer
|
|
421
|
+
.prompt([
|
|
422
|
+
{
|
|
423
|
+
type: 'confirm',
|
|
424
|
+
name: 'pull',
|
|
425
|
+
default: true,
|
|
426
|
+
message: `Your branch '${bold(json.branch.name)}' is not up to date with the Avo main branch. Would you like to pull main into your branch?`,
|
|
427
|
+
},
|
|
428
|
+
])
|
|
429
|
+
.then((answer) => Promise.resolve([branchStatus, answer]));
|
|
430
|
+
}
|
|
431
|
+
// We're expecting branchStatus === BRANCH_UP_TO_DATE
|
|
409
432
|
return Promise.resolve([branchStatus]);
|
|
410
|
-
} else if (branchStatus == BRANCH_NOT_UP_TO_DATE) {
|
|
411
|
-
return inquirer
|
|
412
|
-
.prompt([
|
|
413
|
-
{
|
|
414
|
-
type: 'confirm',
|
|
415
|
-
name: 'pull',
|
|
416
|
-
default: true,
|
|
417
|
-
message: `Your branch '${bold(
|
|
418
|
-
json.branch.name
|
|
419
|
-
)}' is not up to date with the Avo main branch. Would you like to pull main into your branch?`
|
|
420
|
-
}
|
|
421
|
-
])
|
|
422
|
-
.then(answer => Promise.resolve([branchStatus, answer]));
|
|
423
|
-
}
|
|
424
433
|
})
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
434
|
+
.then(([branchStatus, answer]) => {
|
|
435
|
+
if (branchStatus === BRANCH_UP_TO_DATE) {
|
|
436
|
+
report.success('Branch is up to date with main');
|
|
437
|
+
return Promise.resolve(json);
|
|
438
|
+
}
|
|
439
|
+
if (answer.pull) {
|
|
440
|
+
return pullMaster(json);
|
|
441
|
+
}
|
|
442
|
+
report.info('Did not pull main into branch');
|
|
433
443
|
return Promise.resolve(json);
|
|
434
|
-
}
|
|
435
444
|
});
|
|
436
445
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
446
|
+
function installIdOrUserId() {
|
|
447
|
+
const installId = conf.get('avo_install_id');
|
|
448
|
+
const user = conf.get('user');
|
|
449
|
+
if (user && user.user_id) {
|
|
450
|
+
return user.user_id;
|
|
451
|
+
}
|
|
452
|
+
return installId;
|
|
453
|
+
}
|
|
454
|
+
function invokedByCi() {
|
|
455
|
+
return process.env.CI !== undefined;
|
|
456
|
+
}
|
|
457
|
+
function requireAuth(argv, cb) {
|
|
458
|
+
const tokens = conf.get('tokens');
|
|
459
|
+
const user = conf.get('user');
|
|
460
|
+
const tokenOpt = argv.token ?? process.env.AVO_TOKEN;
|
|
461
|
+
if (tokenOpt) {
|
|
462
|
+
api.setRefreshToken(tokenOpt);
|
|
463
|
+
return cb();
|
|
464
|
+
}
|
|
465
|
+
if (!user || !tokens) {
|
|
466
|
+
report.error(`Command requires authentication. Run ${cmd('avo login')}`);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
argv.user = user; // eslint-disable-line no-param-reassign
|
|
470
|
+
argv.tokens = tokens; // eslint-disable-line no-param-reassign
|
|
471
|
+
api.setRefreshToken(tokens.refreshToken);
|
|
472
|
+
return cb();
|
|
473
|
+
}
|
|
474
|
+
function init() {
|
|
475
|
+
const makeAvoJson = (schema) => {
|
|
476
|
+
report.success(`Initialized for workspace ${cyan(schema.name)}`);
|
|
477
|
+
return {
|
|
478
|
+
avo: {
|
|
479
|
+
version: semver.major(pkg.version),
|
|
480
|
+
},
|
|
481
|
+
schema: {
|
|
482
|
+
id: schema.id,
|
|
483
|
+
name: schema.name,
|
|
484
|
+
},
|
|
485
|
+
branch: {
|
|
486
|
+
id: 'master',
|
|
487
|
+
name: 'main',
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
wait('Initializing');
|
|
492
|
+
return api
|
|
493
|
+
.request('GET', '/c/v1/workspaces', {
|
|
494
|
+
origin: api.apiOrigin,
|
|
495
|
+
auth: true,
|
|
496
|
+
})
|
|
497
|
+
.then(({ workspaces }) => {
|
|
498
|
+
cancelWait();
|
|
499
|
+
const schemas = [...workspaces].sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
500
|
+
if (schemas.length > 1) {
|
|
501
|
+
const choices = schemas.map((schema) => ({
|
|
502
|
+
value: schema,
|
|
503
|
+
name: schema.name,
|
|
504
|
+
}));
|
|
505
|
+
return inquirer
|
|
506
|
+
.prompt([
|
|
507
|
+
{
|
|
508
|
+
type: 'list',
|
|
509
|
+
name: 'schema',
|
|
510
|
+
message: 'Select a workspace to initialize',
|
|
511
|
+
choices,
|
|
512
|
+
},
|
|
513
|
+
])
|
|
514
|
+
.then((answer) => makeAvoJson(answer.schema));
|
|
515
|
+
}
|
|
516
|
+
if (schemas.length === 0) {
|
|
517
|
+
throw new AvoError(`No workspaces to initialize. Go to ${link('wwww.avo.app')} to create one`);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
const schema = schemas[0];
|
|
521
|
+
return makeAvoJson(schema);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function validateAvoJson(json) {
|
|
526
|
+
if (avoNeedsUpdate(json)) {
|
|
527
|
+
throw new AvoError('Your avo CLI is outdated, please update');
|
|
528
|
+
}
|
|
529
|
+
if (isLegacyAvoJson(json)) {
|
|
530
|
+
return init();
|
|
531
|
+
}
|
|
532
|
+
// augment the latest major version into avo.json
|
|
533
|
+
return { ...json, avo: { ...json.avo, version: semver.major(pkg.version) } };
|
|
534
|
+
}
|
|
535
|
+
function fetchBranches(json) {
|
|
536
|
+
wait('Fetching open branches');
|
|
537
|
+
const payload = {
|
|
538
|
+
origin: api.apiOrigin,
|
|
539
|
+
auth: true,
|
|
540
|
+
json: {
|
|
541
|
+
schemaId: json.schema.id,
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
return api
|
|
545
|
+
.request('POST', '/c/v1/branches', payload)
|
|
546
|
+
.then((data) => {
|
|
547
|
+
cancelWait();
|
|
548
|
+
const branches = [...data.branches].sort((a, b) => {
|
|
549
|
+
if (a.name < b.name)
|
|
550
|
+
return -1;
|
|
551
|
+
if (a.name > b.name)
|
|
552
|
+
return 1;
|
|
553
|
+
return 0;
|
|
554
|
+
});
|
|
555
|
+
// The api still returns master for backwards comparability so we manually
|
|
556
|
+
// update the branch name to main
|
|
557
|
+
return branches.map((branch) => branch.name === 'master' ? { ...branch, name: 'main' } : branch);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function checkout(branchToCheckout, json) {
|
|
561
|
+
return fetchBranches(json).then((branches) => {
|
|
562
|
+
if (!branchToCheckout) {
|
|
563
|
+
const choices = branches.map((branch) => ({
|
|
564
|
+
value: branch,
|
|
565
|
+
name: branch.name,
|
|
566
|
+
}));
|
|
567
|
+
const currentBranch = branches.find(({ id }) => id === json.branch.id);
|
|
568
|
+
return inquirer
|
|
569
|
+
.prompt([
|
|
570
|
+
{
|
|
571
|
+
type: 'list',
|
|
572
|
+
name: 'branch',
|
|
573
|
+
message: 'Select a branch',
|
|
574
|
+
default: currentBranch ?? branches.find(({ id }) => id === 'master'),
|
|
575
|
+
choices,
|
|
576
|
+
pageSize: 15,
|
|
577
|
+
},
|
|
578
|
+
])
|
|
579
|
+
.then((answer) => {
|
|
580
|
+
if (answer.branch === currentBranch) {
|
|
581
|
+
report.info(`Already on '${currentBranch.name}'`);
|
|
582
|
+
return json;
|
|
583
|
+
}
|
|
584
|
+
const { branch } = answer;
|
|
585
|
+
report.success(`Switched to branch '${branch.name}'`);
|
|
586
|
+
return {
|
|
587
|
+
...json,
|
|
588
|
+
branch: {
|
|
589
|
+
id: branch.id,
|
|
590
|
+
name: branch.name,
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
if (branchToCheckout === 'master') {
|
|
596
|
+
report.info("The master branch has been renamed to main. Continuing checkout with main branch...'");
|
|
597
|
+
}
|
|
598
|
+
const adjustedBranchToCheckout = branchToCheckout === 'master' ? 'main' : branchToCheckout;
|
|
599
|
+
if (adjustedBranchToCheckout === json.branch.name) {
|
|
600
|
+
// XXX should check here if json.branch.id === branch.id from server
|
|
601
|
+
// if not, it indicates branch delete, same branch re-created and client is out of sync
|
|
602
|
+
report.info(`Already on '${adjustedBranchToCheckout}'`);
|
|
603
|
+
return json;
|
|
604
|
+
}
|
|
605
|
+
const branch = branches.find(({ name }) => name === adjustedBranchToCheckout);
|
|
606
|
+
if (!branch) {
|
|
607
|
+
report.error(`Branch '${adjustedBranchToCheckout}' does not exist. Run ${cmd('avo checkout')} to list available branches`);
|
|
608
|
+
}
|
|
609
|
+
report.success(`Switched to branch '${branch.name}'`);
|
|
610
|
+
return {
|
|
611
|
+
...json,
|
|
612
|
+
branch: {
|
|
613
|
+
id: branch.id,
|
|
614
|
+
name: branch.name,
|
|
615
|
+
},
|
|
616
|
+
};
|
|
464
617
|
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
branchId: head.branch.id,
|
|
479
|
-
branchName: head.branch.name
|
|
618
|
+
}
|
|
619
|
+
function resolveAvoJsonConflicts(avoFile, { argv, skipPullMaster }) {
|
|
620
|
+
report.info('Resolving Avo merge conflicts');
|
|
621
|
+
const files = extractConflictingFiles(avoFile);
|
|
622
|
+
const head = JSON.parse(files[0]);
|
|
623
|
+
const incoming = JSON.parse(files[1]);
|
|
624
|
+
Avo.cliConflictResolveAttempted({
|
|
625
|
+
userId_: installIdOrUserId(),
|
|
626
|
+
cliInvokedByCi: invokedByCi(),
|
|
627
|
+
schemaId: head.schema.id,
|
|
628
|
+
schemaName: head.schema.name,
|
|
629
|
+
branchId: head.branch.id,
|
|
630
|
+
branchName: head.branch.name,
|
|
480
631
|
});
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
632
|
+
if (head.avo.version !== incoming.avo.version ||
|
|
633
|
+
head.schema.id !== incoming.schema.id) {
|
|
634
|
+
Avo.cliConflictResolveFailed({
|
|
635
|
+
userId_: installIdOrUserId(),
|
|
636
|
+
cliInvokedByCi: invokedByCi(),
|
|
637
|
+
schemaId: head.schema.id,
|
|
638
|
+
schemaName: head.schema.name,
|
|
639
|
+
branchId: head.branch.id,
|
|
640
|
+
branchName: head.branch.name,
|
|
641
|
+
});
|
|
642
|
+
throw new Error("Could not automatically resolve merge conflicts in avo.json. Resolve merge conflicts in avo.json before running 'avo pull' again.");
|
|
643
|
+
}
|
|
644
|
+
if (JSON.stringify(head.sources.map((s) => s.id)) !==
|
|
645
|
+
JSON.stringify(incoming.sources.map((s) => s.id))) {
|
|
646
|
+
Avo.cliConflictResolveFailed({
|
|
647
|
+
userId_: installIdOrUserId(),
|
|
648
|
+
cliInvokedByCi: invokedByCi(),
|
|
649
|
+
schemaId: head.schema.id,
|
|
650
|
+
schemaName: head.schema.name,
|
|
651
|
+
branchId: head.branch.id,
|
|
652
|
+
branchName: head.branch.name,
|
|
653
|
+
});
|
|
654
|
+
throw new Error("Could not automatically resolve merge conflicts in avo.json. Resolve merge conflicts in sources list in avo.json before running 'avo pull' again.");
|
|
655
|
+
}
|
|
656
|
+
const nextAvoJson = {
|
|
657
|
+
avo: head.avo,
|
|
658
|
+
schema: head.schema,
|
|
659
|
+
branch: head.branch,
|
|
660
|
+
sources: head.sources,
|
|
661
|
+
};
|
|
662
|
+
return requireAuth(argv, () => fetchBranches(nextAvoJson).then((branches) => {
|
|
663
|
+
const isHeadBranchOpen = branches.find((branch) => branch.id === nextAvoJson.branch.id);
|
|
664
|
+
const isIncomingBranchOpen = branches.find((branch) => branch.id === incoming.branch.id);
|
|
665
|
+
function switchBranchIfRequired(json) {
|
|
666
|
+
if (isHeadBranchOpen) {
|
|
667
|
+
return Promise.resolve(json);
|
|
668
|
+
}
|
|
669
|
+
report.info(`Your current branch '${json.branch.name}' has been closed or merged. Go to another branch:`);
|
|
670
|
+
return checkout(null, json);
|
|
510
671
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
incoming.branch.id == 'master'
|
|
518
|
-
) {
|
|
519
|
-
return Promise.resolve([true, json]);
|
|
520
|
-
} else {
|
|
672
|
+
return switchBranchIfRequired(nextAvoJson)
|
|
673
|
+
.then((json) => {
|
|
674
|
+
if (head.branch.id === incoming.branch.id ||
|
|
675
|
+
incoming.branch.id === 'master') {
|
|
676
|
+
return Promise.resolve([true, json]);
|
|
677
|
+
}
|
|
521
678
|
return Promise.resolve([false, json]);
|
|
522
|
-
}
|
|
523
679
|
})
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
throw new Error(
|
|
544
|
-
`Incoming branch, ${
|
|
545
|
-
incoming.branch.name
|
|
546
|
-
}, has not been merged to Avo main.\n\nTo review and merge go to:\n${link(
|
|
547
|
-
`https://www.avo.app/schemas/${nextAvoJson.schema.id}/branches/${incoming.branch.id}/diff`
|
|
548
|
-
)}\n\nOnce merged, run 'avo pull'. To skip this check use the --force flag.`
|
|
549
|
-
);
|
|
550
|
-
} else {
|
|
551
|
-
return Promise.resolve(json);
|
|
552
|
-
}
|
|
680
|
+
.then(([isDone, json]) => {
|
|
681
|
+
if (!isDone && isIncomingBranchOpen && argv.force) {
|
|
682
|
+
report.warn(`Incoming branch, ${incoming.branch.name}, has not been merged to Avo main. To review and merge go to: ${link(`https://www.avo.app/schemas/${nextAvoJson.schema.id}/branches/${incoming.branch.id}/diff`)}`);
|
|
683
|
+
return Promise.resolve(json);
|
|
684
|
+
}
|
|
685
|
+
if (!isDone && isIncomingBranchOpen) {
|
|
686
|
+
Avo.cliConflictResolveFailed({
|
|
687
|
+
userId_: installIdOrUserId(),
|
|
688
|
+
cliInvokedByCi: invokedByCi(),
|
|
689
|
+
schemaId: head.schema.id,
|
|
690
|
+
schemaName: head.schema.name,
|
|
691
|
+
branchId: head.branch.id,
|
|
692
|
+
branchName: head.branch.name,
|
|
693
|
+
});
|
|
694
|
+
throw new Error(`Incoming branch, ${incoming.branch.name}, has not been merged to Avo main.\n\nTo review and merge go to:\n${link(`https://www.avo.app/schemas/${nextAvoJson.schema.id}/branches/${incoming.branch.id}/diff`)}\n\nOnce merged, run 'avo pull'. To skip this check use the --force flag.`);
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
return Promise.resolve(json);
|
|
698
|
+
}
|
|
553
699
|
})
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
700
|
+
.then((json) => {
|
|
701
|
+
if (skipPullMaster) {
|
|
702
|
+
return Promise.resolve(json);
|
|
703
|
+
}
|
|
558
704
|
return promptPullMaster(json);
|
|
559
|
-
}
|
|
560
705
|
})
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
706
|
+
.then((json) => {
|
|
707
|
+
Avo.cliConflictResolveSucceeded({
|
|
708
|
+
userId_: installIdOrUserId(),
|
|
709
|
+
cliInvokedByCi: invokedByCi(),
|
|
710
|
+
schemaId: head.schema.id,
|
|
711
|
+
schemaName: head.schema.name,
|
|
712
|
+
branchId: head.branch.id,
|
|
713
|
+
branchName: head.branch.name,
|
|
714
|
+
});
|
|
715
|
+
report.success('Successfully resolved Avo merge conflicts');
|
|
716
|
+
return validateAvoJson(json);
|
|
572
717
|
});
|
|
573
|
-
});
|
|
574
|
-
});
|
|
718
|
+
}));
|
|
575
719
|
}
|
|
576
|
-
|
|
577
720
|
function loadAvoJson() {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
721
|
+
return loadJsonFile('avo.json')
|
|
722
|
+
.then(validateAvoJson)
|
|
723
|
+
.catch((err) => {
|
|
724
|
+
if (err.code === 'ENOENT') {
|
|
725
|
+
throw new AvoError(`File ${file('avo.json')} does not exist. Run ${cmd('avo init')}`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
throw err;
|
|
729
|
+
}
|
|
588
730
|
});
|
|
589
731
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return Promise.resolve(JSON.parse(file));
|
|
601
|
-
}
|
|
602
|
-
})
|
|
603
|
-
.then(json => {
|
|
604
|
-
json.force = argv.f === true;
|
|
605
|
-
return Promise.resolve(json);
|
|
732
|
+
function loadAvoJsonOrInit({ argv, skipPullMaster, skipInit }) {
|
|
733
|
+
return pify(fs.readFile)('avo.json', 'utf8')
|
|
734
|
+
.then((avoFile) => {
|
|
735
|
+
if (hasMergeConflicts(avoFile)) {
|
|
736
|
+
return resolveAvoJsonConflicts(avoFile, {
|
|
737
|
+
argv,
|
|
738
|
+
skipPullMaster,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
return Promise.resolve(JSON.parse(avoFile));
|
|
606
742
|
})
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
743
|
+
.then((json) => Promise.resolve({ ...json, force: argv.f === true }))
|
|
744
|
+
.then(validateAvoJson)
|
|
745
|
+
.catch((error) => {
|
|
746
|
+
if (error.code === 'ENOENT' && skipInit) {
|
|
747
|
+
return Promise.resolve();
|
|
748
|
+
}
|
|
749
|
+
if (error.code === 'ENOENT') {
|
|
750
|
+
report.info('Avo not initialized');
|
|
751
|
+
return requireAuth(argv, init);
|
|
752
|
+
}
|
|
615
753
|
throw error;
|
|
616
|
-
}
|
|
617
754
|
});
|
|
618
755
|
}
|
|
619
|
-
|
|
620
756
|
function writeAvoJson(json) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function init() {
|
|
627
|
-
let makeAvoJson = schema => {
|
|
628
|
-
report.success(`Initialized for workspace ${cyan(schema.name)}`);
|
|
629
|
-
|
|
630
|
-
return {
|
|
631
|
-
avo: {
|
|
632
|
-
version: semver.major(pkg.version)
|
|
633
|
-
},
|
|
634
|
-
schema: {
|
|
635
|
-
id: schema.id,
|
|
636
|
-
name: schema.name
|
|
637
|
-
},
|
|
638
|
-
branch: {
|
|
639
|
-
id: 'master',
|
|
640
|
-
name: 'main'
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
};
|
|
644
|
-
wait('Initializing');
|
|
645
|
-
return api
|
|
646
|
-
.request('GET', '/c/v1/workspaces', {
|
|
647
|
-
origin: api.apiOrigin,
|
|
648
|
-
auth: true
|
|
649
|
-
})
|
|
650
|
-
.then(res => {
|
|
651
|
-
cancelWait();
|
|
652
|
-
let result = res.body;
|
|
653
|
-
let schemas = _.orderBy(result.workspaces, 'lastUsedAt', 'desc');
|
|
654
|
-
if (schemas.length > 1) {
|
|
655
|
-
let choices = schemas.map(schema => ({
|
|
656
|
-
value: schema,
|
|
657
|
-
name: schema.name
|
|
658
|
-
}));
|
|
659
|
-
return inquirer
|
|
660
|
-
.prompt([
|
|
661
|
-
{
|
|
662
|
-
type: 'list',
|
|
663
|
-
name: 'schema',
|
|
664
|
-
message: 'Select a workspace to initialize',
|
|
665
|
-
choices: choices
|
|
666
|
-
}
|
|
667
|
-
])
|
|
668
|
-
.then(answer => {
|
|
669
|
-
return makeAvoJson(answer.schema);
|
|
670
|
-
});
|
|
671
|
-
} else if (schemas.length === 0) {
|
|
672
|
-
throw new AvoError(
|
|
673
|
-
`No workspaces to initialize. Go to ${link(
|
|
674
|
-
'wwww.avo.app'
|
|
675
|
-
)} to create one`
|
|
676
|
-
);
|
|
677
|
-
} else {
|
|
678
|
-
let schema = schemas[0];
|
|
679
|
-
return makeAvoJson(schema);
|
|
680
|
-
}
|
|
681
|
-
});
|
|
757
|
+
return writeJsonFile('avo.json', json, {
|
|
758
|
+
indent: 2,
|
|
759
|
+
}).then(() => json);
|
|
682
760
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
id: target.id,
|
|
698
|
-
path: source.path,
|
|
699
|
-
branchId: target.branchId,
|
|
700
|
-
updatedAt: target.updatedAt
|
|
701
|
-
});
|
|
702
|
-
} else {
|
|
703
|
-
return source;
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
let sourceTasks = targets.map(target => {
|
|
708
|
-
return Promise.all(
|
|
709
|
-
target.code.map(code => writeFile(code.path, code.content))
|
|
710
|
-
);
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
let avoJsonTask = writeAvoJson(newJson);
|
|
714
|
-
|
|
715
|
-
Promise.all(_.concat([avoJsonTask], sourceTasks)).then(() => {
|
|
716
|
-
if (errors !== undefined && errors !== null && errors !== "") {
|
|
717
|
-
report.warn(errors + "\n");
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
if (warnings !== undefined && warnings !== null && Array.isArray(warnings)) {
|
|
721
|
-
warnings.forEach(warning => {
|
|
722
|
-
report.warn(warning);
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
report.success(
|
|
726
|
-
`Analytics ${
|
|
727
|
-
targets.length > 1 ? 'wrappers' : 'wrapper'
|
|
728
|
-
} successfully updated`
|
|
729
|
-
);
|
|
730
|
-
targets.forEach(target => {
|
|
731
|
-
let source = _.find(newJson.sources, source => source.id === target.id);
|
|
732
|
-
report.tree('sources', [
|
|
733
|
-
{
|
|
734
|
-
name: source.name,
|
|
735
|
-
children: target.code.map(code => {
|
|
736
|
-
return {name: code.path};
|
|
737
|
-
})
|
|
761
|
+
function codegen(json, { schema, sources: targets, warnings, errors }) {
|
|
762
|
+
const newJson = { ...JSON.parse(JSON.stringify(json)), schema };
|
|
763
|
+
newJson.sources = newJson.sources.map((source) => {
|
|
764
|
+
const target = targets.find(({ id }) => id === source.id);
|
|
765
|
+
if (target) {
|
|
766
|
+
return {
|
|
767
|
+
...source,
|
|
768
|
+
actionId: target.actionId,
|
|
769
|
+
name: target.name,
|
|
770
|
+
id: target.id,
|
|
771
|
+
path: source.path,
|
|
772
|
+
branchId: target.branchId,
|
|
773
|
+
updatedAt: target.updatedAt,
|
|
774
|
+
};
|
|
738
775
|
}
|
|
739
|
-
|
|
776
|
+
return source;
|
|
740
777
|
});
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
return api
|
|
747
|
-
.request('POST', '/c/v1/sources', {
|
|
748
|
-
origin: api.apiOrigin,
|
|
749
|
-
auth: true,
|
|
750
|
-
data: {
|
|
751
|
-
schemaId: json.schema.id,
|
|
752
|
-
branchId: json.branch.id
|
|
753
|
-
}
|
|
754
|
-
})
|
|
755
|
-
.then(res => {
|
|
756
|
-
cancelWait();
|
|
757
|
-
let result = res.body;
|
|
758
|
-
let existingSources = json.sources || [];
|
|
759
|
-
let sources = _.sortBy(
|
|
760
|
-
_.filter(
|
|
761
|
-
result.sources,
|
|
762
|
-
source =>
|
|
763
|
-
_.find(
|
|
764
|
-
existingSources,
|
|
765
|
-
existingSource => source.id === existingSource.id
|
|
766
|
-
) === undefined
|
|
767
|
-
),
|
|
768
|
-
'name'
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
let prompts = [
|
|
772
|
-
{
|
|
773
|
-
type: 'fuzzypath',
|
|
774
|
-
name: 'folder',
|
|
775
|
-
excludePath: path =>
|
|
776
|
-
path.startsWith('node_modules') || path.startsWith('.git'),
|
|
777
|
-
itemType: 'directory',
|
|
778
|
-
rootPath: '.',
|
|
779
|
-
message: 'Select a folder to save the analytics wrapper in',
|
|
780
|
-
default: '.',
|
|
781
|
-
suggestOnly: false,
|
|
782
|
-
depthLimit: 10
|
|
778
|
+
const sourceTasks = targets.map((target) => Promise.all(target.code.map((code) => writeFile(code.path, code.content))));
|
|
779
|
+
const avoJsonTask = writeAvoJson(newJson);
|
|
780
|
+
Promise.all([avoJsonTask].concat(sourceTasks)).then(() => {
|
|
781
|
+
if (errors !== undefined && errors !== null && errors !== '') {
|
|
782
|
+
report.warn(`${errors}\n`);
|
|
783
783
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
prompts.unshift({
|
|
792
|
-
type: 'list',
|
|
793
|
-
name: 'source',
|
|
794
|
-
message: 'Select a source to set up',
|
|
795
|
-
choices: choices,
|
|
796
|
-
pageSize: 15
|
|
797
|
-
});
|
|
798
|
-
prompts.push({
|
|
799
|
-
type: 'input',
|
|
800
|
-
name: 'filename',
|
|
801
|
-
message: 'Select a filename for the analytics wrapper',
|
|
802
|
-
default: function(answers) {
|
|
803
|
-
return answers.source.filenameHint;
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
} else {
|
|
807
|
-
let source = _.find(sources, source =>
|
|
808
|
-
matchesSource(source, sourceToAdd)
|
|
809
|
-
);
|
|
810
|
-
if (!source) {
|
|
811
|
-
throw new AvoError(`Source ${sourceToAdd} does not exist`);
|
|
784
|
+
if (warnings !== undefined &&
|
|
785
|
+
warnings !== null &&
|
|
786
|
+
Array.isArray(warnings)) {
|
|
787
|
+
warnings.forEach((warning) => {
|
|
788
|
+
report.warn(warning);
|
|
789
|
+
});
|
|
812
790
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
791
|
+
report.success(`Analytics ${targets.length > 1 ? 'wrappers' : 'wrapper'} successfully updated`);
|
|
792
|
+
targets.forEach((target) => {
|
|
793
|
+
const source = newJson.sources.find(({ id }) => id === target.id);
|
|
794
|
+
report.tree('sources', [
|
|
795
|
+
{
|
|
796
|
+
name: source.name,
|
|
797
|
+
children: target.code.map((code) => ({ name: code.path })),
|
|
798
|
+
},
|
|
799
|
+
]);
|
|
820
800
|
});
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
return inquirer.prompt(prompts).then(answer => {
|
|
824
|
-
let relativePath = path.relative(
|
|
825
|
-
process.cwd(),
|
|
826
|
-
path.join(path.resolve(answer.folder), answer.filename)
|
|
827
|
-
);
|
|
828
|
-
let source;
|
|
829
|
-
if (sourceToAdd) {
|
|
830
|
-
source = _.find(sources, source =>
|
|
831
|
-
matchesSource(source, sourceToAdd)
|
|
832
|
-
);
|
|
833
|
-
source = {id: source.id, name: source.name, path: relativePath};
|
|
834
|
-
} else {
|
|
835
|
-
source = {
|
|
836
|
-
id: answer.source.id,
|
|
837
|
-
name: answer.source.name,
|
|
838
|
-
path: relativePath
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
sources = _.concat(json.sources || [], [source]);
|
|
842
|
-
let newJson = Object.assign({}, json, {sources: sources});
|
|
843
|
-
report.info(`Added source ${source.name} to the project`);
|
|
844
|
-
report.info(
|
|
845
|
-
`Run 'avo pull "${source.name}"' to pull the latest analytics wrapper for this source`
|
|
846
|
-
);
|
|
847
|
-
return newJson;
|
|
848
|
-
});
|
|
849
801
|
});
|
|
850
802
|
}
|
|
851
|
-
|
|
852
|
-
function fetchBranches(json) {
|
|
853
|
-
const schemaId = json.schema.id;
|
|
854
|
-
wait('Fetching open branches');
|
|
855
|
-
const payload = {
|
|
856
|
-
origin: api.apiOrigin,
|
|
857
|
-
auth: true,
|
|
858
|
-
data: {
|
|
859
|
-
schemaId: json.schema.id
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
return api.request('POST', '/c/v1/branches', payload).then(res => {
|
|
863
|
-
cancelWait();
|
|
864
|
-
let result = res.body;
|
|
865
|
-
let branches = _.sortBy(result.branches, 'name');
|
|
866
|
-
// The api still returns master for backwards comparability so we manually
|
|
867
|
-
// update the branch name to main
|
|
868
|
-
return branches.map(
|
|
869
|
-
branch => branch.name === "master" ? {...branch, name: "main"} : branch
|
|
870
|
-
);
|
|
871
|
-
return branches;
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function checkout(branchToCheckout, json) {
|
|
876
|
-
return fetchBranches(json).then(branches => {
|
|
877
|
-
if (!branchToCheckout) {
|
|
878
|
-
let choices = branches.map(branch => {
|
|
879
|
-
return {value: branch, name: branch.name};
|
|
880
|
-
});
|
|
881
|
-
let currentBranch = _.find(
|
|
882
|
-
branches,
|
|
883
|
-
branch => branch.id == json.branch.id
|
|
884
|
-
);
|
|
885
|
-
return inquirer
|
|
886
|
-
.prompt([
|
|
887
|
-
{
|
|
888
|
-
type: 'list',
|
|
889
|
-
name: 'branch',
|
|
890
|
-
message: 'Select a branch',
|
|
891
|
-
default:
|
|
892
|
-
currentBranch ||
|
|
893
|
-
_.find(branches, branch => branch.id == 'master'),
|
|
894
|
-
choices: choices,
|
|
895
|
-
pageSize: 15
|
|
896
|
-
}
|
|
897
|
-
])
|
|
898
|
-
.then(answer => {
|
|
899
|
-
if (answer.branch === currentBranch) {
|
|
900
|
-
report.info(`Already on '${currentBranch.name}'`);
|
|
901
|
-
return json;
|
|
902
|
-
} else {
|
|
903
|
-
let branch = answer.branch;
|
|
904
|
-
json = Object.assign({}, json, {
|
|
905
|
-
branch: {
|
|
906
|
-
id: branch.id,
|
|
907
|
-
name: branch.name
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
report.success(`Switched to branch '${branch.name}'`);
|
|
911
|
-
return json;
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
} else {
|
|
915
|
-
if (branchToCheckout == "master") {
|
|
916
|
-
report.info(
|
|
917
|
-
`The master branch has been renamed to main. Continuing checkout with main branch...'`
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
let adjustedBranchToCheckout =
|
|
921
|
-
branchToCheckout == "master" ? "main" : branchToCheckout;
|
|
922
|
-
if (adjustedBranchToCheckout == json.branch.name) {
|
|
923
|
-
// XXX should check here if json.branch.id === branch.id from server
|
|
924
|
-
// if not, it indicates branch delete, same branch re-created and client is out of sync
|
|
925
|
-
report.info(`Already on '${adjustedBranchToCheckout}'`);
|
|
926
|
-
return json;
|
|
927
|
-
}
|
|
928
|
-
let branch = _.find(branches, branch => branch.name == adjustedBranchToCheckout);
|
|
929
|
-
if (!branch) {
|
|
930
|
-
report.error(
|
|
931
|
-
`Branch '${adjustedBranchToCheckout}' does not exist. Run ${cmd(
|
|
932
|
-
'avo checkout'
|
|
933
|
-
)} to list available branches`
|
|
934
|
-
);
|
|
935
|
-
} else {
|
|
936
|
-
json = Object.assign({}, json, {
|
|
937
|
-
branch: {
|
|
938
|
-
id: branch.id,
|
|
939
|
-
name: branch.name
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
report.success(`Switched to branch '${branch.name}'`);
|
|
943
|
-
return json;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
|
|
949
803
|
function matchesSource(source, filter) {
|
|
950
|
-
|
|
804
|
+
return source.name.toLowerCase() === filter.toLowerCase();
|
|
951
805
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
: json.sources;
|
|
957
|
-
let sourceNames = _.map(sources, source => source.name);
|
|
958
|
-
wait(`Pulling ${sourceNames.join(', ')}`);
|
|
959
|
-
|
|
960
|
-
return getMasterStatus(json)
|
|
961
|
-
.then(status => {
|
|
962
|
-
if (status == BRANCH_NOT_UP_TO_DATE) {
|
|
963
|
-
report.warn(
|
|
964
|
-
`Your branch '${json.branch.name}' is not up to date with Avo main. To merge latest Avo main into the branch, run 'avo merge main'.`
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
return Promise.resolve();
|
|
968
|
-
})
|
|
969
|
-
.then(() => {
|
|
970
|
-
return api.request('POST', '/c/v1/pull', {
|
|
806
|
+
function selectSource(sourceToAdd, json) {
|
|
807
|
+
wait('Fetching sources');
|
|
808
|
+
return api
|
|
809
|
+
.request('POST', '/c/v1/sources', {
|
|
971
810
|
origin: api.apiOrigin,
|
|
972
811
|
auth: true,
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
return {id: source.id, path: source.path};
|
|
978
|
-
}),
|
|
979
|
-
force: json.force || false
|
|
980
|
-
}
|
|
981
|
-
});
|
|
812
|
+
json: {
|
|
813
|
+
schemaId: json.schema.id,
|
|
814
|
+
branchId: json.branch.id,
|
|
815
|
+
},
|
|
982
816
|
})
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
817
|
+
.then((data) => {
|
|
818
|
+
cancelWait();
|
|
819
|
+
const existingSources = json.sources ?? [];
|
|
820
|
+
let sources = data.sources
|
|
821
|
+
.filter((source) => !existingSources.find(({ id }) => source.id === id))
|
|
822
|
+
.sort((a, b) => {
|
|
823
|
+
if (a.name < b.name)
|
|
824
|
+
return -1;
|
|
825
|
+
if (a.name > b.name)
|
|
826
|
+
return 1;
|
|
827
|
+
return 0;
|
|
828
|
+
});
|
|
829
|
+
const prompts = [
|
|
830
|
+
{
|
|
831
|
+
type: 'fuzzypath',
|
|
832
|
+
name: 'folder',
|
|
833
|
+
excludePath: (maybeExcludePath) => maybeExcludePath.startsWith('node_modules') ||
|
|
834
|
+
maybeExcludePath.startsWith('.git'),
|
|
835
|
+
itemType: 'directory',
|
|
836
|
+
rootPath: '.',
|
|
837
|
+
message: 'Select a folder to save the analytics wrapper in',
|
|
838
|
+
default: '.',
|
|
839
|
+
suggestOnly: false,
|
|
840
|
+
depthLimit: 10,
|
|
841
|
+
},
|
|
842
|
+
];
|
|
843
|
+
if (!sourceToAdd) {
|
|
844
|
+
const choices = sources.map((source) => ({
|
|
845
|
+
value: source,
|
|
846
|
+
name: source.name,
|
|
847
|
+
}));
|
|
848
|
+
prompts.unshift({
|
|
849
|
+
type: 'list',
|
|
850
|
+
name: 'source',
|
|
851
|
+
message: 'Select a source to set up',
|
|
852
|
+
// @ts-ignore
|
|
853
|
+
choices,
|
|
854
|
+
pageSize: 15,
|
|
855
|
+
});
|
|
856
|
+
prompts.push({
|
|
857
|
+
type: 'input',
|
|
858
|
+
name: 'filename',
|
|
859
|
+
message: 'Select a filename for the analytics wrapper',
|
|
860
|
+
// @ts-ignore
|
|
861
|
+
default(answers) {
|
|
862
|
+
return answers.source.filenameHint;
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
const source = sources.find((soruceToFind) => matchesSource(soruceToFind, sourceToAdd));
|
|
868
|
+
if (!source) {
|
|
869
|
+
throw new AvoError(`Source ${sourceToAdd} does not exist`);
|
|
870
|
+
}
|
|
871
|
+
prompts.push({
|
|
872
|
+
type: 'input',
|
|
873
|
+
name: 'filename',
|
|
874
|
+
message: 'Select a filename for the library',
|
|
875
|
+
// @ts-ignore
|
|
876
|
+
default() {
|
|
877
|
+
return source.filenameHint;
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
return inquirer.prompt(prompts).then((answer) => {
|
|
882
|
+
const relativePath = path.relative(process.cwd(), path.join(path.resolve(answer.folder), answer.filename));
|
|
883
|
+
let source;
|
|
884
|
+
if (sourceToAdd) {
|
|
885
|
+
source = sources.find((sourceToFind) => matchesSource(sourceToFind, sourceToAdd));
|
|
886
|
+
source = { id: source.id, name: source.name, path: relativePath };
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
source = {
|
|
890
|
+
id: answer.source.id,
|
|
891
|
+
name: answer.source.name,
|
|
892
|
+
path: relativePath,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
sources = (json.sources ?? []).concat([source]);
|
|
896
|
+
const newJson = { ...json, sources };
|
|
897
|
+
report.info(`Added source ${source.name} to the project`);
|
|
898
|
+
report.info(`Run 'avo pull "${source.name}"' to pull the latest analytics wrapper for this source`);
|
|
899
|
+
return newJson;
|
|
1000
900
|
});
|
|
1001
|
-
}
|
|
1002
901
|
});
|
|
1003
902
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
903
|
+
function pull(sourceFilter, json) {
|
|
904
|
+
const sources = sourceFilter
|
|
905
|
+
? [json.sources.find((source) => matchesSource(source, sourceFilter))]
|
|
906
|
+
: json.sources;
|
|
907
|
+
const sourceNames = sources.map((source) => source.name);
|
|
908
|
+
wait(`Pulling ${sourceNames.join(', ')}`);
|
|
909
|
+
return getMasterStatus(json)
|
|
910
|
+
.then((masterStatus) => {
|
|
911
|
+
if (masterStatus === BRANCH_NOT_UP_TO_DATE) {
|
|
912
|
+
report.warn(`Your branch '${json.branch.name}' is not up to date with Avo main. To merge latest Avo main into the branch, run 'avo merge main'.`);
|
|
913
|
+
}
|
|
914
|
+
return Promise.resolve();
|
|
915
|
+
})
|
|
916
|
+
.then(() => api.request('POST', '/c/v1/pull', {
|
|
917
|
+
origin: api.apiOrigin,
|
|
918
|
+
auth: true,
|
|
919
|
+
json: {
|
|
920
|
+
schemaId: json.schema.id,
|
|
921
|
+
branchId: json.branch.id,
|
|
922
|
+
sources: sources.map((source) => ({
|
|
923
|
+
id: source.id,
|
|
924
|
+
path: source.path,
|
|
925
|
+
})),
|
|
926
|
+
force: json.force ?? false,
|
|
927
|
+
},
|
|
928
|
+
}))
|
|
929
|
+
.then((result) => {
|
|
930
|
+
cancelWait();
|
|
931
|
+
if (result.ok) {
|
|
932
|
+
codegen(json, result);
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
report.error(`Branch ${result.branchName} was ${result.reason} ${dateFns.formatDistance(new Date(), new Date(result.closedAt))} ago. Pick another branch.`);
|
|
936
|
+
checkout(null, json).then((data) => pull(sourceFilter, data));
|
|
937
|
+
}
|
|
938
|
+
});
|
|
1017
939
|
}
|
|
1018
|
-
|
|
1019
940
|
function findMatches(data, regex) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
if (!isGlobal) break;
|
|
941
|
+
const isGlobal = regex.global;
|
|
942
|
+
const lines = data.split('\n');
|
|
943
|
+
const fileMatches = [];
|
|
944
|
+
let lastIndex = 0;
|
|
945
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
946
|
+
const lineContents = lines[index];
|
|
947
|
+
const line = lastIndex + index;
|
|
948
|
+
let match;
|
|
949
|
+
while (true) {
|
|
950
|
+
match = regex.exec(lineContents);
|
|
951
|
+
if (!match)
|
|
952
|
+
break;
|
|
953
|
+
const start = match.index;
|
|
954
|
+
const end = match.index + match[0].length;
|
|
955
|
+
fileMatches.push({
|
|
956
|
+
line,
|
|
957
|
+
start,
|
|
958
|
+
end,
|
|
959
|
+
lineContents,
|
|
960
|
+
});
|
|
961
|
+
if (!isGlobal)
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
1045
964
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
lastIndex += lines.length;
|
|
1049
|
-
|
|
1050
|
-
return fileMatches;
|
|
965
|
+
lastIndex += lines.length;
|
|
966
|
+
return fileMatches;
|
|
1051
967
|
}
|
|
1052
|
-
|
|
1053
968
|
function getEventMap(data) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
return eventMap;
|
|
1063
|
-
} else {
|
|
969
|
+
const searchFor = 'AVOEVENTMAP:';
|
|
970
|
+
const lines = data.split('\n').filter((line) => line.indexOf(searchFor) > -1);
|
|
971
|
+
if (lines.length === 1) {
|
|
972
|
+
let line = lines[0].substring(lines[0].indexOf(searchFor) + searchFor.length);
|
|
973
|
+
line = line.substring(line.indexOf('['), line.indexOf(']') + 1);
|
|
974
|
+
const eventMap = JSON.parse(line);
|
|
975
|
+
return eventMap;
|
|
976
|
+
}
|
|
1064
977
|
return null;
|
|
1065
|
-
}
|
|
1066
978
|
}
|
|
1067
|
-
|
|
1068
979
|
function getModuleMap(data) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return moduleMap;
|
|
1078
|
-
} else {
|
|
980
|
+
const searchFor = 'AVOMODULEMAP:';
|
|
981
|
+
const lines = data.split('\n').filter((line) => line.indexOf(searchFor) > -1);
|
|
982
|
+
if (lines.length === 1) {
|
|
983
|
+
let line = lines[0].substring(lines[0].indexOf(searchFor) + searchFor.length);
|
|
984
|
+
line = line.substring(line.indexOf('"'), line.lastIndexOf('"') + 1);
|
|
985
|
+
const moduleMap = JSON.parse(line);
|
|
986
|
+
return moduleMap;
|
|
987
|
+
}
|
|
1079
988
|
return null;
|
|
1080
|
-
}
|
|
1081
989
|
}
|
|
1082
|
-
|
|
1083
990
|
function getSource(argv, json) {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
991
|
+
if (!json.sources || !json.sources.length) {
|
|
992
|
+
report.info('No sources configured.');
|
|
993
|
+
return requireAuth(argv, () => {
|
|
994
|
+
if (argv.source) {
|
|
995
|
+
report.info(`Setting up source "${argv.source}"`);
|
|
996
|
+
}
|
|
997
|
+
return selectSource(argv.source, json).then((sourceJson) => [
|
|
998
|
+
argv.source,
|
|
999
|
+
sourceJson,
|
|
1000
|
+
]);
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
if (argv.source &&
|
|
1004
|
+
!json.sources.find((source) => matchesSource(source, argv.source))) {
|
|
1005
|
+
report.error(`Source ${argv.source} not found`);
|
|
1006
|
+
return requireAuth(argv, () => selectSource(argv.source, json).then((sourceJson) => [
|
|
1007
|
+
argv.source,
|
|
1008
|
+
sourceJson,
|
|
1009
|
+
]));
|
|
1010
|
+
}
|
|
1101
1011
|
return Promise.resolve([argv.source, json]);
|
|
1102
|
-
}
|
|
1103
1012
|
}
|
|
1104
|
-
|
|
1105
1013
|
function status(source, json, argv) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
results.map(path => {
|
|
1118
|
-
return pify(fs.lstat)(path).then(stats => {
|
|
1119
|
-
if (stats.isSymbolicLink()) {
|
|
1014
|
+
let sources = source
|
|
1015
|
+
? json.sources.filter((s) => matchesSource(s, source))
|
|
1016
|
+
: json.sources;
|
|
1017
|
+
sources = sources.filter(({ analysis }) => analysis !== false);
|
|
1018
|
+
const fileCache = walk({
|
|
1019
|
+
ignoreFiles: ['.gitignore'],
|
|
1020
|
+
follow: false,
|
|
1021
|
+
}).then((results) => Promise.all(results
|
|
1022
|
+
.filter((result) => !result.startsWith('.git'))
|
|
1023
|
+
.map((resultPath) => pify(fs.lstat)(resultPath).then((stats) => {
|
|
1024
|
+
if (stats.isSymbolicLink()) {
|
|
1120
1025
|
return [];
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
fileCache
|
|
1132
|
-
.then(cache => {
|
|
1133
|
-
sources = Promise.all(
|
|
1134
|
-
sources.map(source => {
|
|
1135
|
-
return pify(fs.readFile)(source.path, 'utf8').then(data => {
|
|
1136
|
-
let eventMap = getEventMap(data);
|
|
1026
|
+
}
|
|
1027
|
+
return pify(fs.readFile)(resultPath, 'utf8').then((data) => [
|
|
1028
|
+
resultPath,
|
|
1029
|
+
data,
|
|
1030
|
+
]);
|
|
1031
|
+
}))).then((cachePairs) => Object.fromEntries(cachePairs)));
|
|
1032
|
+
fileCache
|
|
1033
|
+
.then((cache) => {
|
|
1034
|
+
sources = Promise.all(sources.map((source) => pify(fs.readFile)(source.path, 'utf8').then((data) => {
|
|
1035
|
+
const eventMap = getEventMap(data);
|
|
1137
1036
|
if (eventMap !== null) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
eventMap.map(eventName => {
|
|
1181
|
-
let re = new RegExp('(' + moduleName + '\\.' + eventName + '|\\[' + moduleName + " " + eventName +')');
|
|
1182
|
-
let results = _.flatMap(lookup, (data, path) => {
|
|
1183
|
-
if (argv.verbose) {
|
|
1184
|
-
report.info(`Looking for events in ${path}`);
|
|
1037
|
+
const moduleMap = getModuleMap(data);
|
|
1038
|
+
const sourcePath = path.parse(source.path);
|
|
1039
|
+
const moduleName = source.analysis?.module ??
|
|
1040
|
+
moduleMap ??
|
|
1041
|
+
sourcePath.name ??
|
|
1042
|
+
'Avo';
|
|
1043
|
+
const sourcePathExts = [];
|
|
1044
|
+
if (sourcePath.ext === '.js' || sourcePath.ext === '.ts') {
|
|
1045
|
+
sourcePathExts.push('js');
|
|
1046
|
+
sourcePathExts.push('jsx');
|
|
1047
|
+
sourcePathExts.push('ts');
|
|
1048
|
+
sourcePathExts.push('tsx');
|
|
1049
|
+
}
|
|
1050
|
+
else if (sourcePath.ext === '.java' ||
|
|
1051
|
+
sourcePath.ext === '.kt') {
|
|
1052
|
+
sourcePathExts.push('java');
|
|
1053
|
+
sourcePathExts.push('kt');
|
|
1054
|
+
}
|
|
1055
|
+
else if (sourcePath.ext === '.m' ||
|
|
1056
|
+
sourcePath.ext === '.swift') {
|
|
1057
|
+
sourcePathExts.push('m');
|
|
1058
|
+
sourcePathExts.push('swift');
|
|
1059
|
+
}
|
|
1060
|
+
else if (sourcePath.ext === '.re') {
|
|
1061
|
+
sourcePathExts.push('re');
|
|
1062
|
+
sourcePathExts.push('res');
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
sourcePathExts.push(sourcePath.ext.substring(1));
|
|
1066
|
+
}
|
|
1067
|
+
if (argv.verbose) {
|
|
1068
|
+
console.log('Looking in files with extensions:', sourcePathExts);
|
|
1069
|
+
}
|
|
1070
|
+
const globs = [
|
|
1071
|
+
new Minimatch(source.analysis?.glob ??
|
|
1072
|
+
`**/*.+(${sourcePathExts.join('|')})`, {}),
|
|
1073
|
+
new Minimatch(`!${source.path}`, {}),
|
|
1074
|
+
];
|
|
1075
|
+
const lookup = {};
|
|
1076
|
+
Object.entries(cache).forEach(([cachePath, value]) => {
|
|
1077
|
+
if (globs.every((mm) => mm.match(cachePath))) {
|
|
1078
|
+
lookup[cachePath] = value;
|
|
1185
1079
|
}
|
|
1186
|
-
let results = findMatches(data, re);
|
|
1187
|
-
return results.length ? [[path, results]] : [];
|
|
1188
|
-
});
|
|
1189
|
-
return [eventName, _.fromPairs(results)];
|
|
1190
|
-
})
|
|
1191
|
-
).then(results => {
|
|
1192
|
-
return Object.assign({}, source, {
|
|
1193
|
-
results: _.fromPairs(results)
|
|
1194
1080
|
});
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1081
|
+
return Promise.all(eventMap.map((eventName) => {
|
|
1082
|
+
const re = new RegExp(`(${moduleName}\\.${eventName}|\\[${moduleName} ${eventName})`);
|
|
1083
|
+
const results = Object.entries(lookup)
|
|
1084
|
+
.map(([path, data]) => {
|
|
1085
|
+
if (argv.verbose) {
|
|
1086
|
+
report.info(`Looking for events in ${path}`);
|
|
1087
|
+
}
|
|
1088
|
+
const results = findMatches(data, re);
|
|
1089
|
+
return results.length ? [[path, results]] : [];
|
|
1090
|
+
})
|
|
1091
|
+
.flat();
|
|
1092
|
+
return [eventName, Object.fromEntries(results)];
|
|
1093
|
+
})).then((results) => ({
|
|
1094
|
+
...source,
|
|
1095
|
+
results: Object.fromEntries(results),
|
|
1096
|
+
}));
|
|
1198
1097
|
}
|
|
1199
|
-
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1098
|
+
return source;
|
|
1099
|
+
})));
|
|
1100
|
+
return sources.then((sources) => {
|
|
1101
|
+
report.tree('sources', sources.map((source) => ({
|
|
1102
|
+
name: `${source.name} (${source.path})`,
|
|
1103
|
+
children: Object.entries(source.results).map(([eventName, results]) => ({
|
|
1104
|
+
name: eventName,
|
|
1105
|
+
children: Object.keys(results).length > 0
|
|
1106
|
+
? Object.entries(results).map(([matchFile, result]) => ({
|
|
1107
|
+
name: `used in ${matchFile}: ${result.length}${result.length === 1 ? ' time' : ' times'}`,
|
|
1108
|
+
}))
|
|
1109
|
+
: [
|
|
1110
|
+
{
|
|
1111
|
+
name: `${logSymbols.error} no usage found`,
|
|
1112
|
+
},
|
|
1113
|
+
],
|
|
1114
|
+
})),
|
|
1115
|
+
})));
|
|
1116
|
+
const totalEvents = sources
|
|
1117
|
+
.map(({ results }) => Object.keys(results).length)
|
|
1118
|
+
.reduce(sum, 0);
|
|
1119
|
+
const missingEvents = sources
|
|
1120
|
+
.map(({ results }) => Object.values(results).filter((missing) => Object.keys(missing).length === 0).length)
|
|
1121
|
+
.reduce(sum, 0);
|
|
1122
|
+
if (missingEvents === 0) {
|
|
1123
|
+
if (totalEvents === 0) {
|
|
1124
|
+
report.error('no events found in the avo file - please run avo pull');
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
report.info(`${totalEvents} events seen in code`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
report.info(`${totalEvents - missingEvents} of ${totalEvents} events seen in code`);
|
|
1132
|
+
}
|
|
1133
|
+
if (missingEvents > 0) {
|
|
1134
|
+
report.error(`${missingEvents} missing ${missingEvents > 1 ? 'events' : 'event'}`);
|
|
1135
|
+
report.tree('missingEvents', sources.map((source) => ({
|
|
1136
|
+
name: `${source.name} (${source.path})`,
|
|
1137
|
+
children: Object.entries(source.results)
|
|
1138
|
+
.map(([eventName, results]) => Object.keys(results).length === 0
|
|
1139
|
+
? [
|
|
1140
|
+
{
|
|
1141
|
+
name: `${red(eventName)}: no usage found`,
|
|
1142
|
+
},
|
|
1143
|
+
]
|
|
1144
|
+
: [])
|
|
1145
|
+
.flat(),
|
|
1146
|
+
})));
|
|
1147
|
+
process.exit(1);
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
})
|
|
1151
|
+
.catch((error) => {
|
|
1152
|
+
if (error.code === 'ENOENT') {
|
|
1153
|
+
report.error("Avo file not found. Run 'avo pull' to pull latest Avo files.");
|
|
1255
1154
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
`${missingEvents} missing ${missingEvents > 1 ? 'events' : 'event'}`
|
|
1259
|
-
);
|
|
1260
|
-
report.tree(
|
|
1261
|
-
'missingEvents',
|
|
1262
|
-
sources.map(source => {
|
|
1263
|
-
return {
|
|
1264
|
-
name: source.name + ' (' + source.path + ')',
|
|
1265
|
-
children:
|
|
1266
|
-
_.flatMap(source.results, (results, eventName) => {
|
|
1267
|
-
return _.size(results) === 0
|
|
1268
|
-
? [
|
|
1269
|
-
{
|
|
1270
|
-
name: `${red(eventName)}: no usage found`
|
|
1271
|
-
}
|
|
1272
|
-
]
|
|
1273
|
-
: [];
|
|
1274
|
-
})
|
|
1275
|
-
};
|
|
1276
|
-
})
|
|
1277
|
-
);
|
|
1278
|
-
process.exit(1);
|
|
1155
|
+
else {
|
|
1156
|
+
throw error;
|
|
1279
1157
|
}
|
|
1280
|
-
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
/// //////////////////////////////////////////////////////////////////////
|
|
1161
|
+
// AUTH
|
|
1162
|
+
function _getLoginUrl(callbackUrl) {
|
|
1163
|
+
return `${api.authOrigin}/auth/cli?state=${encodeURIComponent(nonce)}&redirect_uri=${encodeURIComponent(callbackUrl)}`;
|
|
1164
|
+
}
|
|
1165
|
+
function _getCallbackUrl(port) {
|
|
1166
|
+
if (port === undefined) {
|
|
1167
|
+
return 'urn:ietf:wg:oauth:2.0:oob';
|
|
1168
|
+
}
|
|
1169
|
+
return `http://localhost:${port}`;
|
|
1170
|
+
}
|
|
1171
|
+
function _getTokensFromAuthorizationCode(code, callbackUrl) {
|
|
1172
|
+
return api
|
|
1173
|
+
.request('POST', '/auth/token', {
|
|
1174
|
+
origin: api.apiOrigin,
|
|
1175
|
+
json: {
|
|
1176
|
+
token: code,
|
|
1177
|
+
redirect_uri: callbackUrl,
|
|
1178
|
+
},
|
|
1281
1179
|
})
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1180
|
+
.then((data) => {
|
|
1181
|
+
if (!data.idToken && !data.refreshToken) {
|
|
1182
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
1183
|
+
}
|
|
1184
|
+
lastAccessToken = {
|
|
1185
|
+
expiresAt: Date.now() + data.expiresIn * 1000,
|
|
1186
|
+
...data,
|
|
1187
|
+
};
|
|
1188
|
+
return lastAccessToken;
|
|
1189
|
+
}, () => {
|
|
1190
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
function _respondWithRedirect(req, res, Location) {
|
|
1194
|
+
return new Promise((resolve) => {
|
|
1195
|
+
res.writeHead(302, { Location });
|
|
1196
|
+
res.end();
|
|
1197
|
+
req.socket.destroy();
|
|
1198
|
+
resolve();
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
function _loginWithoutLocalhost() {
|
|
1202
|
+
const callbackUrl = _getCallbackUrl();
|
|
1203
|
+
const authUrl = _getLoginUrl(callbackUrl);
|
|
1204
|
+
report.info(`Visit this URL on any device to login: ${new URL(authUrl)}`);
|
|
1205
|
+
return open(authUrl);
|
|
1206
|
+
}
|
|
1207
|
+
function _loginWithLocalhost(port) {
|
|
1208
|
+
return new Promise((resolve, reject) => {
|
|
1209
|
+
const callbackUrl = _getCallbackUrl(port);
|
|
1210
|
+
const authUrl = _getLoginUrl(callbackUrl);
|
|
1211
|
+
let server = http.createServer((req, res) => {
|
|
1212
|
+
let tokens;
|
|
1213
|
+
const query = url.parse(req.url, true).query ?? {};
|
|
1214
|
+
if (query.state === nonce && isString(query.code)) {
|
|
1215
|
+
return _getTokensFromAuthorizationCode(query.code, callbackUrl)
|
|
1216
|
+
.then((result) => {
|
|
1217
|
+
tokens = result;
|
|
1218
|
+
return _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/success`);
|
|
1219
|
+
})
|
|
1220
|
+
.then(() => {
|
|
1221
|
+
cancelWait();
|
|
1222
|
+
server.close();
|
|
1223
|
+
return resolve({
|
|
1224
|
+
user: jwt.decode(tokens.idToken),
|
|
1225
|
+
tokens,
|
|
1226
|
+
});
|
|
1227
|
+
})
|
|
1228
|
+
.catch(() => _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/error`));
|
|
1229
|
+
}
|
|
1230
|
+
return _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/error`);
|
|
1231
|
+
});
|
|
1232
|
+
server = httpShutdown(server);
|
|
1233
|
+
server.listen(port, () => {
|
|
1234
|
+
report.info(`Visit this URL on any device to login: ${link(authUrl)}`);
|
|
1235
|
+
wait('Waiting for authentication...');
|
|
1236
|
+
open(authUrl);
|
|
1237
|
+
});
|
|
1238
|
+
server.on('error', () => {
|
|
1239
|
+
_loginWithoutLocalhost().then(resolve, reject);
|
|
1240
|
+
});
|
|
1290
1241
|
});
|
|
1291
1242
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1243
|
+
function login() {
|
|
1244
|
+
return _getPort().then(_loginWithLocalhost, _loginWithoutLocalhost);
|
|
1245
|
+
}
|
|
1246
|
+
function logout(refreshToken) {
|
|
1247
|
+
if (lastAccessToken.refreshToken === refreshToken) {
|
|
1248
|
+
lastAccessToken = {};
|
|
1249
|
+
}
|
|
1250
|
+
const tokens = conf.get('tokens');
|
|
1251
|
+
const currentToken = tokens.refreshToken;
|
|
1252
|
+
if (refreshToken === currentToken) {
|
|
1253
|
+
conf.delete('user');
|
|
1254
|
+
conf.delete('tokens');
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
|
|
1258
|
+
.usage('$0 command')
|
|
1259
|
+
.scriptName('avo')
|
|
1260
|
+
.version(pkg.version)
|
|
1261
|
+
.option('v', {
|
|
1298
1262
|
alias: 'verbose',
|
|
1299
1263
|
default: false,
|
|
1300
1264
|
describe: 'make output more verbose',
|
|
1301
|
-
type: 'boolean'
|
|
1302
|
-
|
|
1303
|
-
|
|
1265
|
+
type: 'boolean',
|
|
1266
|
+
})
|
|
1267
|
+
.option('f', {
|
|
1304
1268
|
alias: 'force',
|
|
1305
1269
|
describe: 'Proceed with merge when incoming branch is open',
|
|
1306
1270
|
default: false,
|
|
1307
|
-
type: 'boolean'
|
|
1308
|
-
|
|
1309
|
-
|
|
1271
|
+
type: 'boolean',
|
|
1272
|
+
})
|
|
1273
|
+
.command({
|
|
1310
1274
|
command: 'track-install',
|
|
1311
1275
|
desc: false,
|
|
1312
1276
|
handler: () => {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1277
|
+
Avo.cliInstalled({
|
|
1278
|
+
userId_: installIdOrUserId(),
|
|
1279
|
+
cliInvokedByCi: invokedByCi(),
|
|
1280
|
+
});
|
|
1281
|
+
},
|
|
1282
|
+
})
|
|
1283
|
+
.command({
|
|
1320
1284
|
command: 'init',
|
|
1321
1285
|
desc: 'Initialize an Avo workspace in the current folder',
|
|
1322
|
-
handler: argv => {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
);
|
|
1340
|
-
} else {
|
|
1286
|
+
handler: (argv) => {
|
|
1287
|
+
loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
|
|
1288
|
+
.then((json) => {
|
|
1289
|
+
if (json) {
|
|
1290
|
+
Avo.cliInvoked({
|
|
1291
|
+
schemaId: json.schema.id,
|
|
1292
|
+
schemaName: json.schema.name,
|
|
1293
|
+
branchId: json.branch.id,
|
|
1294
|
+
branchName: json.branch.name,
|
|
1295
|
+
userId_: installIdOrUserId(),
|
|
1296
|
+
cliAction: Avo.CliAction.INIT,
|
|
1297
|
+
cliInvokedByCi: invokedByCi(),
|
|
1298
|
+
force: undefined,
|
|
1299
|
+
});
|
|
1300
|
+
report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
|
|
1301
|
+
return Promise.resolve();
|
|
1302
|
+
}
|
|
1341
1303
|
Avo.cliInvoked({
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1304
|
+
schemaId: 'N/A',
|
|
1305
|
+
schemaName: 'N/A',
|
|
1306
|
+
branchId: 'N/A',
|
|
1307
|
+
branchName: 'N/A',
|
|
1308
|
+
userId_: installIdOrUserId(),
|
|
1309
|
+
cliAction: Avo.CliAction.INIT,
|
|
1310
|
+
cliInvokedByCi: invokedByCi(),
|
|
1311
|
+
force: undefined,
|
|
1349
1312
|
});
|
|
1350
|
-
return requireAuth(argv, () =>
|
|
1351
|
-
return init()
|
|
1313
|
+
return requireAuth(argv, () => init()
|
|
1352
1314
|
.then(writeAvoJson)
|
|
1353
1315
|
.then(() => {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
);
|
|
1357
|
-
});
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1316
|
+
report.info("Run 'avo pull' to pull analytics wrappers from Avo");
|
|
1317
|
+
}));
|
|
1360
1318
|
})
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1319
|
+
.catch(() => {
|
|
1320
|
+
Avo.cliInvoked({
|
|
1321
|
+
schemaId: 'N/A',
|
|
1322
|
+
schemaName: 'N/A',
|
|
1323
|
+
branchId: 'N/A',
|
|
1324
|
+
branchName: 'N/A',
|
|
1325
|
+
userId_: installIdOrUserId(),
|
|
1326
|
+
cliAction: Avo.CliAction.INIT,
|
|
1327
|
+
cliInvokedByCi: invokedByCi(),
|
|
1328
|
+
force: undefined,
|
|
1329
|
+
});
|
|
1371
1330
|
});
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1331
|
+
},
|
|
1332
|
+
})
|
|
1333
|
+
.command({
|
|
1375
1334
|
command: 'pull [source]',
|
|
1376
1335
|
desc: 'Pull analytics wrappers from Avo workspace',
|
|
1377
|
-
builder: yargs => {
|
|
1378
|
-
return yargs.option('branch', {
|
|
1336
|
+
builder: (yargs) => yargs.option('branch', {
|
|
1379
1337
|
describe: 'Name of Avo branch to pull from',
|
|
1380
|
-
type: 'string'
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1338
|
+
type: 'string',
|
|
1339
|
+
}),
|
|
1340
|
+
handler: (argv) => {
|
|
1341
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1342
|
+
.then((json) => {
|
|
1343
|
+
Avo.cliInvoked({
|
|
1344
|
+
schemaId: json.schema.id,
|
|
1345
|
+
schemaName: json.schema.name,
|
|
1346
|
+
branchId: json.branch.id,
|
|
1347
|
+
branchName: json.branch.name,
|
|
1348
|
+
userId_: installIdOrUserId(),
|
|
1349
|
+
cliAction: Avo.CliAction.PULL,
|
|
1350
|
+
cliInvokedByCi: invokedByCi(),
|
|
1351
|
+
force: undefined,
|
|
1352
|
+
});
|
|
1353
|
+
requireAuth(argv, () => {
|
|
1354
|
+
if (argv.branch && json.branch.name !== argv.branch) {
|
|
1355
|
+
return checkout(argv.branch, json)
|
|
1356
|
+
.then((data) => getSource(argv, data))
|
|
1357
|
+
.then(([source, data]) => pull(source, data));
|
|
1358
|
+
}
|
|
1359
|
+
report.info(`Pulling from branch '${json.branch.name}'`);
|
|
1360
|
+
return getSource(argv, json).then(([source, data]) => pull(source, data));
|
|
1361
|
+
});
|
|
1407
1362
|
})
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1363
|
+
.catch((error) => {
|
|
1364
|
+
Avo.cliInvoked({
|
|
1365
|
+
schemaId: 'N/A',
|
|
1366
|
+
schemaName: 'N/A',
|
|
1367
|
+
branchId: 'N/A',
|
|
1368
|
+
branchName: 'N/A',
|
|
1369
|
+
userId_: installIdOrUserId(),
|
|
1370
|
+
cliAction: Avo.CliAction.PULL,
|
|
1371
|
+
cliInvokedByCi: invokedByCi(),
|
|
1372
|
+
force: undefined,
|
|
1373
|
+
});
|
|
1374
|
+
throw error;
|
|
1419
1375
|
});
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1376
|
+
},
|
|
1377
|
+
})
|
|
1378
|
+
.command({
|
|
1423
1379
|
command: 'checkout [branch]',
|
|
1424
1380
|
aliases: ['branch'],
|
|
1425
1381
|
desc: 'Switch branches',
|
|
1426
|
-
handler: argv => {
|
|
1427
|
-
|
|
1428
|
-
.
|
|
1429
|
-
Avo.cliInvoked({
|
|
1382
|
+
handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1383
|
+
.then((json) => {
|
|
1384
|
+
Avo.cliInvoked({
|
|
1430
1385
|
schemaId: json.schema.id,
|
|
1431
1386
|
schemaName: json.schema.name,
|
|
1432
1387
|
branchId: json.branch.id,
|
|
1433
1388
|
branchName: json.branch.name,
|
|
1434
1389
|
userId_: installIdOrUserId(),
|
|
1435
1390
|
cliAction: Avo.CliAction.CHECKOUT,
|
|
1436
|
-
cliInvokedByCi: invokedByCi()
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
.
|
|
1444
|
-
Avo.cliInvoked({
|
|
1391
|
+
cliInvokedByCi: invokedByCi(),
|
|
1392
|
+
force: undefined,
|
|
1393
|
+
});
|
|
1394
|
+
report.info(`Currently on branch '${json.branch.name}'`);
|
|
1395
|
+
requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
|
|
1396
|
+
})
|
|
1397
|
+
.catch((error) => {
|
|
1398
|
+
Avo.cliInvoked({
|
|
1445
1399
|
schemaId: 'N/A',
|
|
1446
1400
|
schemaName: 'N/A',
|
|
1447
1401
|
branchId: 'N/A',
|
|
1448
1402
|
branchName: 'N/A',
|
|
1449
1403
|
userId_: installIdOrUserId(),
|
|
1450
1404
|
cliAction: Avo.CliAction.CHECKOUT,
|
|
1451
|
-
cliInvokedByCi: invokedByCi()
|
|
1452
|
-
|
|
1453
|
-
throw error;
|
|
1405
|
+
cliInvokedByCi: invokedByCi(),
|
|
1406
|
+
force: undefined,
|
|
1454
1407
|
});
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1408
|
+
throw error;
|
|
1409
|
+
}),
|
|
1410
|
+
})
|
|
1411
|
+
.command({
|
|
1458
1412
|
command: 'source <command>',
|
|
1459
1413
|
desc: 'Manage sources for the current project',
|
|
1460
|
-
builder: yargs => {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
schemaId: 'N/A',
|
|
1501
|
-
schemaName: 'N/A',
|
|
1502
|
-
branchId: 'N/A',
|
|
1503
|
-
branchName: 'N/A',
|
|
1504
|
-
userId_: installIdOrUserId(),
|
|
1505
|
-
cliAction: Avo.CliAction.SOURCE,
|
|
1506
|
-
cliInvokedByCi: invokedByCi()
|
|
1414
|
+
builder: (yargs) => {
|
|
1415
|
+
yargs
|
|
1416
|
+
.command({
|
|
1417
|
+
command: '$0',
|
|
1418
|
+
desc: 'List sources in this project',
|
|
1419
|
+
handler: (argv) => {
|
|
1420
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1421
|
+
.then((json) => {
|
|
1422
|
+
Avo.cliInvoked({
|
|
1423
|
+
schemaId: json.schema.id,
|
|
1424
|
+
schemaName: json.schema.name,
|
|
1425
|
+
branchId: json.branch.id,
|
|
1426
|
+
branchName: json.branch.name,
|
|
1427
|
+
userId_: installIdOrUserId(),
|
|
1428
|
+
cliAction: Avo.CliAction.SOURCE,
|
|
1429
|
+
cliInvokedByCi: invokedByCi(),
|
|
1430
|
+
force: undefined,
|
|
1431
|
+
});
|
|
1432
|
+
if (!json.sources || !json.sources.length) {
|
|
1433
|
+
report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
report.info('Sources in this project:');
|
|
1437
|
+
report.tree('sources', json.sources.map((source) => ({
|
|
1438
|
+
name: source.name,
|
|
1439
|
+
children: [{ name: source.path }],
|
|
1440
|
+
})));
|
|
1441
|
+
})
|
|
1442
|
+
.catch((error) => {
|
|
1443
|
+
Avo.cliInvoked({
|
|
1444
|
+
schemaId: 'N/A',
|
|
1445
|
+
schemaName: 'N/A',
|
|
1446
|
+
branchId: 'N/A',
|
|
1447
|
+
branchName: 'N/A',
|
|
1448
|
+
userId_: installIdOrUserId(),
|
|
1449
|
+
cliAction: Avo.CliAction.SOURCE,
|
|
1450
|
+
cliInvokedByCi: invokedByCi(),
|
|
1451
|
+
force: undefined,
|
|
1452
|
+
});
|
|
1453
|
+
throw error;
|
|
1507
1454
|
});
|
|
1508
|
-
|
|
1509
|
-
});
|
|
1510
|
-
}
|
|
1455
|
+
},
|
|
1511
1456
|
})
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1457
|
+
.command({
|
|
1458
|
+
command: 'add [source]',
|
|
1459
|
+
desc: 'Add a source to this project',
|
|
1460
|
+
handler: (argv) => {
|
|
1461
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1462
|
+
.then((json) => {
|
|
1463
|
+
Avo.cliInvoked({
|
|
1464
|
+
schemaId: json.schema.id,
|
|
1465
|
+
schemaName: json.schema.name,
|
|
1466
|
+
branchId: json.branch.id,
|
|
1467
|
+
branchName: json.branch.name,
|
|
1468
|
+
userId_: installIdOrUserId(),
|
|
1469
|
+
cliAction: Avo.CliAction.SOURCE_ADD,
|
|
1470
|
+
cliInvokedByCi: invokedByCi(),
|
|
1471
|
+
force: undefined,
|
|
1472
|
+
});
|
|
1473
|
+
requireAuth(argv, () => {
|
|
1474
|
+
selectSource(argv.source, json).then(writeAvoJson);
|
|
1475
|
+
});
|
|
1476
|
+
})
|
|
1477
|
+
.catch((error) => {
|
|
1478
|
+
Avo.cliInvoked({
|
|
1479
|
+
schemaId: 'N/A',
|
|
1480
|
+
schemaName: 'N/A',
|
|
1481
|
+
branchId: 'N/A',
|
|
1482
|
+
branchName: 'N/A',
|
|
1483
|
+
userId_: installIdOrUserId(),
|
|
1484
|
+
cliAction: Avo.CliAction.SOURCE_ADD,
|
|
1485
|
+
cliInvokedByCi: invokedByCi(),
|
|
1486
|
+
force: undefined,
|
|
1487
|
+
});
|
|
1488
|
+
throw error;
|
|
1541
1489
|
});
|
|
1542
|
-
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1490
|
+
},
|
|
1545
1491
|
})
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
)
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
.then(answer => {
|
|
1611
|
-
if (answer.remove) {
|
|
1612
|
-
let sources = _.filter(
|
|
1613
|
-
json.sources || [],
|
|
1614
|
-
source => source.id !== targetSource.id
|
|
1615
|
-
);
|
|
1616
|
-
let newJson = Object.assign({}, json, {
|
|
1617
|
-
sources: sources
|
|
1618
|
-
});
|
|
1619
|
-
return writeAvoJson(newJson).then(() => {
|
|
1620
|
-
// XXX ask to remove file as well?
|
|
1621
|
-
report.info(
|
|
1622
|
-
`Removed source ${targetSource.name} from project`
|
|
1623
|
-
);
|
|
1492
|
+
.command({
|
|
1493
|
+
command: 'remove [source]',
|
|
1494
|
+
aliases: ['rm'],
|
|
1495
|
+
desc: 'Remove a source from this project',
|
|
1496
|
+
handler: (argv) => {
|
|
1497
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1498
|
+
.then((json) => {
|
|
1499
|
+
Avo.cliInvoked({
|
|
1500
|
+
schemaId: json.schema.id,
|
|
1501
|
+
schemaName: json.schema.name,
|
|
1502
|
+
branchId: json.branch.id,
|
|
1503
|
+
branchName: json.branch.name,
|
|
1504
|
+
userId_: installIdOrUserId(),
|
|
1505
|
+
cliAction: Avo.CliAction.SOURCE_REMOVE,
|
|
1506
|
+
cliInvokedByCi: invokedByCi(),
|
|
1507
|
+
force: undefined,
|
|
1508
|
+
});
|
|
1509
|
+
if (!json.sources || !json.sources.length) {
|
|
1510
|
+
report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const getSourceToRemove = (argv, json) => {
|
|
1514
|
+
if (argv.source) {
|
|
1515
|
+
return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
|
|
1516
|
+
}
|
|
1517
|
+
const choices = json.sources.map((source) => ({
|
|
1518
|
+
value: source,
|
|
1519
|
+
name: source.name,
|
|
1520
|
+
}));
|
|
1521
|
+
return inquirer
|
|
1522
|
+
.prompt({
|
|
1523
|
+
type: 'list',
|
|
1524
|
+
name: 'source',
|
|
1525
|
+
message: 'Select a source to remove',
|
|
1526
|
+
choices,
|
|
1527
|
+
pageSize: 15,
|
|
1528
|
+
})
|
|
1529
|
+
.then((answer) => answer.source);
|
|
1530
|
+
};
|
|
1531
|
+
getSourceToRemove(argv, json).then((targetSource) => {
|
|
1532
|
+
if (!targetSource) {
|
|
1533
|
+
report.error(`Source ${argv.source} not found in project.`);
|
|
1534
|
+
return Promise.resolve();
|
|
1535
|
+
}
|
|
1536
|
+
return inquirer
|
|
1537
|
+
.prompt([
|
|
1538
|
+
{
|
|
1539
|
+
type: 'confirm',
|
|
1540
|
+
name: 'remove',
|
|
1541
|
+
default: true,
|
|
1542
|
+
message: `Are you sure you want to remove source ${targetSource.name} from project`,
|
|
1543
|
+
},
|
|
1544
|
+
])
|
|
1545
|
+
.then((answer) => {
|
|
1546
|
+
if (answer.remove) {
|
|
1547
|
+
const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
|
|
1548
|
+
const newJson = { ...json, sources };
|
|
1549
|
+
return writeAvoJson(newJson).then(() => {
|
|
1550
|
+
// XXX ask to remove file as well?
|
|
1551
|
+
report.info(`Removed source ${targetSource.name} from project`);
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
report.info(`Did not remove source ${targetSource.name} from project`);
|
|
1555
|
+
return Promise.resolve();
|
|
1624
1556
|
});
|
|
1625
|
-
} else {
|
|
1626
|
-
report.info(
|
|
1627
|
-
`Did not remove source ${targetSource.name} from project`
|
|
1628
|
-
);
|
|
1629
|
-
}
|
|
1630
1557
|
});
|
|
1558
|
+
})
|
|
1559
|
+
.catch((error) => {
|
|
1560
|
+
Avo.cliInvoked({
|
|
1561
|
+
schemaId: 'N/A',
|
|
1562
|
+
schemaName: 'N/A',
|
|
1563
|
+
branchId: 'N/A',
|
|
1564
|
+
branchName: 'N/A',
|
|
1565
|
+
userId_: installIdOrUserId(),
|
|
1566
|
+
cliAction: Avo.CliAction.SOURCE_REMOVE,
|
|
1567
|
+
cliInvokedByCi: invokedByCi(),
|
|
1568
|
+
force: undefined,
|
|
1569
|
+
});
|
|
1570
|
+
throw error;
|
|
1631
1571
|
});
|
|
1632
|
-
|
|
1633
|
-
.catch(error => {
|
|
1634
|
-
Avo.cliInvoked({
|
|
1635
|
-
schemaId: 'N/A',
|
|
1636
|
-
schemaName: 'N/A',
|
|
1637
|
-
branchId: 'N/A',
|
|
1638
|
-
branchName: 'N/A',
|
|
1639
|
-
userId_: installIdOrUserId(),
|
|
1640
|
-
cliAction: Avo.CliAction.SOURCE_REMOVE,
|
|
1641
|
-
cliInvokedByCi: invokedByCi()
|
|
1642
|
-
});
|
|
1643
|
-
throw error;
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1572
|
+
},
|
|
1646
1573
|
});
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1574
|
+
},
|
|
1575
|
+
})
|
|
1576
|
+
.command({
|
|
1650
1577
|
command: 'status [source]',
|
|
1651
1578
|
desc: 'Show the status of the Avo implementation',
|
|
1652
|
-
handler: argv => {
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
.then(([source, json]) => {
|
|
1668
|
-
return status(source, json, argv);
|
|
1579
|
+
handler: (argv) => {
|
|
1580
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1581
|
+
.then((json) => {
|
|
1582
|
+
Avo.cliInvoked({
|
|
1583
|
+
schemaId: json.schema.id,
|
|
1584
|
+
schemaName: json.schema.name,
|
|
1585
|
+
branchId: json.branch.id,
|
|
1586
|
+
branchName: json.branch.name,
|
|
1587
|
+
userId_: installIdOrUserId(),
|
|
1588
|
+
cliAction: Avo.CliAction.STATUS,
|
|
1589
|
+
cliInvokedByCi: invokedByCi(),
|
|
1590
|
+
force: undefined,
|
|
1591
|
+
});
|
|
1592
|
+
report.info(`Currently on branch '${json.branch.name}'`);
|
|
1593
|
+
return getSource(argv, json);
|
|
1669
1594
|
})
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1595
|
+
.then(([source, json]) => status(source, json, argv))
|
|
1596
|
+
.catch((error) => {
|
|
1597
|
+
Avo.cliInvoked({
|
|
1598
|
+
schemaId: 'N/A',
|
|
1599
|
+
schemaName: 'N/A',
|
|
1600
|
+
branchId: 'N/A',
|
|
1601
|
+
branchName: 'N/A',
|
|
1602
|
+
userId_: installIdOrUserId(),
|
|
1603
|
+
cliAction: Avo.CliAction.STATUS,
|
|
1604
|
+
cliInvokedByCi: invokedByCi(),
|
|
1605
|
+
force: undefined,
|
|
1606
|
+
});
|
|
1607
|
+
throw error;
|
|
1681
1608
|
});
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
.command({
|
|
1609
|
+
},
|
|
1610
|
+
})
|
|
1611
|
+
.command({
|
|
1686
1612
|
command: 'merge main',
|
|
1687
1613
|
aliases: ['merge master'],
|
|
1688
1614
|
desc: 'Pull the Avo main branch into your current branch',
|
|
1689
|
-
handler: argv => {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
return requireAuth(argv, () => {
|
|
1704
|
-
return pullMaster(json).then(writeAvoJson);
|
|
1705
|
-
});
|
|
1615
|
+
handler: (argv) => {
|
|
1616
|
+
loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
|
|
1617
|
+
.then((json) => {
|
|
1618
|
+
Avo.cliInvoked({
|
|
1619
|
+
schemaId: json.schema.id,
|
|
1620
|
+
schemaName: json.schema.name,
|
|
1621
|
+
branchId: json.branch.id,
|
|
1622
|
+
branchName: json.branch.name,
|
|
1623
|
+
userId_: installIdOrUserId(),
|
|
1624
|
+
cliAction: Avo.CliAction.MERGE,
|
|
1625
|
+
cliInvokedByCi: invokedByCi(),
|
|
1626
|
+
force: json.force,
|
|
1627
|
+
});
|
|
1628
|
+
return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
|
|
1706
1629
|
})
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1630
|
+
.catch((error) => {
|
|
1631
|
+
Avo.cliInvoked({
|
|
1632
|
+
schemaId: 'N/A',
|
|
1633
|
+
schemaName: 'N/A',
|
|
1634
|
+
branchId: 'N/A',
|
|
1635
|
+
branchName: 'N/A',
|
|
1636
|
+
userId_: installIdOrUserId(),
|
|
1637
|
+
cliAction: Avo.CliAction.MERGE,
|
|
1638
|
+
cliInvokedByCi: invokedByCi(),
|
|
1639
|
+
force: undefined,
|
|
1640
|
+
});
|
|
1641
|
+
throw error;
|
|
1718
1642
|
});
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1643
|
+
},
|
|
1644
|
+
})
|
|
1645
|
+
.command({
|
|
1722
1646
|
command: 'conflict',
|
|
1723
1647
|
aliases: ['resolve', 'conflicts'],
|
|
1724
1648
|
desc: 'Resolve git conflicts in Avo files',
|
|
1725
|
-
handler: argv =>
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
}).then(json => {
|
|
1649
|
+
handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
|
|
1650
|
+
.then((avoFile) => {
|
|
1651
|
+
if (hasMergeConflicts(avoFile)) {
|
|
1652
|
+
return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
|
|
1653
|
+
argv,
|
|
1654
|
+
skipPullMaster: false,
|
|
1655
|
+
}).then((json) => {
|
|
1733
1656
|
Avo.cliInvoked({
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1657
|
+
schemaId: json.schema.id,
|
|
1658
|
+
schemaName: json.schema.name,
|
|
1659
|
+
branchId: json.branch.id,
|
|
1660
|
+
branchName: json.branch.name,
|
|
1661
|
+
userId_: installIdOrUserId(),
|
|
1662
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1663
|
+
cliInvokedByCi: invokedByCi(),
|
|
1664
|
+
force: undefined,
|
|
1741
1665
|
});
|
|
1742
1666
|
pull(null, json);
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
);
|
|
1749
|
-
const json = JSON.parse(file);
|
|
1750
|
-
Avo.cliInvoked({
|
|
1751
|
-
schemaId: json.schema.id,
|
|
1752
|
-
schemaName: json.schema.name,
|
|
1753
|
-
branchId: json.branch.id,
|
|
1754
|
-
branchName: json.branch.name,
|
|
1755
|
-
userId_: installIdOrUserId(),
|
|
1756
|
-
cliAction: Avo.CliAction.CONFLICT,
|
|
1757
|
-
cliInvokedByCi: invokedByCi()
|
|
1758
|
-
});
|
|
1759
|
-
return Promise.resolve(json);
|
|
1760
|
-
}
|
|
1761
|
-
})
|
|
1762
|
-
.catch(error => {
|
|
1763
|
-
Avo.cliInvoked({
|
|
1764
|
-
schemaId: 'N/A',
|
|
1765
|
-
schemaName: 'N/A',
|
|
1766
|
-
branchId: 'N/A',
|
|
1767
|
-
branchName: 'N/A',
|
|
1768
|
-
userId_: installIdOrUserId(),
|
|
1769
|
-
cliAction: Avo.CliAction.CONFLICT,
|
|
1770
|
-
cliInvokedByCi: invokedByCi()
|
|
1771
|
-
});
|
|
1772
|
-
throw error;
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
})
|
|
1776
|
-
.command({
|
|
1777
|
-
command: 'edit',
|
|
1778
|
-
desc: 'Open the Avo workspace in your browser',
|
|
1779
|
-
handler: argv => {
|
|
1780
|
-
loadAvoJsonOrInit({argv})
|
|
1781
|
-
.then(json => {
|
|
1782
|
-
Avo.cliInvoked({
|
|
1667
|
+
}));
|
|
1668
|
+
}
|
|
1669
|
+
report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
|
|
1670
|
+
const json = JSON.parse(avoFile);
|
|
1671
|
+
Avo.cliInvoked({
|
|
1783
1672
|
schemaId: json.schema.id,
|
|
1784
1673
|
schemaName: json.schema.name,
|
|
1785
1674
|
branchId: json.branch.id,
|
|
1786
1675
|
branchName: json.branch.name,
|
|
1787
1676
|
userId_: installIdOrUserId(),
|
|
1788
|
-
cliAction: Avo.CliAction.
|
|
1789
|
-
cliInvokedByCi: invokedByCi()
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
);
|
|
1797
|
-
opn(url, {wait: false});
|
|
1798
|
-
})
|
|
1799
|
-
.catch(error => {
|
|
1800
|
-
Avo.cliInvoked({
|
|
1677
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1678
|
+
cliInvokedByCi: invokedByCi(),
|
|
1679
|
+
force: undefined,
|
|
1680
|
+
});
|
|
1681
|
+
return Promise.resolve(json);
|
|
1682
|
+
})
|
|
1683
|
+
.catch((error) => {
|
|
1684
|
+
Avo.cliInvoked({
|
|
1801
1685
|
schemaId: 'N/A',
|
|
1802
1686
|
schemaName: 'N/A',
|
|
1803
1687
|
branchId: 'N/A',
|
|
1804
1688
|
branchName: 'N/A',
|
|
1805
1689
|
userId_: installIdOrUserId(),
|
|
1806
|
-
cliAction: Avo.CliAction.
|
|
1807
|
-
cliInvokedByCi: invokedByCi()
|
|
1808
|
-
|
|
1809
|
-
throw error;
|
|
1690
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1691
|
+
cliInvokedByCi: invokedByCi(),
|
|
1692
|
+
force: undefined,
|
|
1810
1693
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1694
|
+
throw error;
|
|
1695
|
+
}),
|
|
1696
|
+
})
|
|
1697
|
+
.command({
|
|
1698
|
+
command: 'edit',
|
|
1699
|
+
desc: 'Open the Avo workspace in your browser',
|
|
1700
|
+
handler: (argv) => {
|
|
1701
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1702
|
+
.then((json) => {
|
|
1703
|
+
Avo.cliInvoked({
|
|
1704
|
+
schemaId: json.schema.id,
|
|
1705
|
+
schemaName: json.schema.name,
|
|
1706
|
+
branchId: json.branch.id,
|
|
1707
|
+
branchName: json.branch.name,
|
|
1708
|
+
userId_: installIdOrUserId(),
|
|
1709
|
+
cliAction: Avo.CliAction.EDIT,
|
|
1710
|
+
cliInvokedByCi: invokedByCi(),
|
|
1711
|
+
force: undefined,
|
|
1712
|
+
});
|
|
1713
|
+
const { schema } = json;
|
|
1714
|
+
const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
|
|
1715
|
+
report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
|
|
1716
|
+
open(schemaUrl);
|
|
1717
|
+
})
|
|
1718
|
+
.catch((error) => {
|
|
1719
|
+
Avo.cliInvoked({
|
|
1720
|
+
schemaId: 'N/A',
|
|
1721
|
+
schemaName: 'N/A',
|
|
1722
|
+
branchId: 'N/A',
|
|
1723
|
+
branchName: 'N/A',
|
|
1724
|
+
userId_: installIdOrUserId(),
|
|
1725
|
+
cliAction: Avo.CliAction.EDIT,
|
|
1726
|
+
cliInvokedByCi: invokedByCi(),
|
|
1727
|
+
force: undefined,
|
|
1728
|
+
});
|
|
1729
|
+
throw error;
|
|
1730
|
+
});
|
|
1731
|
+
},
|
|
1732
|
+
})
|
|
1733
|
+
.command({
|
|
1814
1734
|
command: 'login',
|
|
1815
1735
|
desc: 'Log into the Avo platform',
|
|
1816
1736
|
handler: () => {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1737
|
+
const command = () => {
|
|
1738
|
+
const user = conf.get('user');
|
|
1739
|
+
if (user) {
|
|
1740
|
+
report.info(`Already logged in as ${email(user.email)}`);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
login()
|
|
1744
|
+
.then((result) => {
|
|
1745
|
+
conf.set('user', result.user);
|
|
1746
|
+
conf.set('tokens', result.tokens);
|
|
1747
|
+
Avo.signedIn({
|
|
1748
|
+
userId_: result.user.user_id,
|
|
1749
|
+
email: result.user.email,
|
|
1750
|
+
authenticationMethod: Avo.AuthenticationMethod.CLI,
|
|
1751
|
+
});
|
|
1752
|
+
report.success(`Logged in as ${email(result.user.email)}`);
|
|
1753
|
+
})
|
|
1754
|
+
.catch(() => {
|
|
1755
|
+
Avo.signInFailed({
|
|
1756
|
+
userId_: conf.get('avo_install_id'),
|
|
1757
|
+
emailInput: '',
|
|
1758
|
+
signInError: Avo.SignInError.UNKNOWN,
|
|
1759
|
+
});
|
|
1832
1760
|
});
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1761
|
+
};
|
|
1762
|
+
loadAvoJson()
|
|
1763
|
+
.then((json) => {
|
|
1764
|
+
Avo.cliInvoked({
|
|
1765
|
+
schemaId: json.schema.id,
|
|
1766
|
+
schemaName: json.schema.name,
|
|
1767
|
+
branchId: json.branch.id,
|
|
1768
|
+
branchName: json.branch.name,
|
|
1769
|
+
userId_: installIdOrUserId(),
|
|
1770
|
+
cliAction: Avo.CliAction.LOGIN,
|
|
1771
|
+
cliInvokedByCi: invokedByCi(),
|
|
1772
|
+
force: undefined,
|
|
1842
1773
|
});
|
|
1843
|
-
|
|
1844
|
-
};
|
|
1845
|
-
|
|
1846
|
-
loadAvoJson()
|
|
1847
|
-
.then(json => {
|
|
1848
|
-
Avo.cliInvoked({
|
|
1849
|
-
schemaId: json.schema.id,
|
|
1850
|
-
schemaName: json.schema.name,
|
|
1851
|
-
branchId: json.branch.id,
|
|
1852
|
-
branchName: json.branch.name,
|
|
1853
|
-
userId_: installIdOrUserId(),
|
|
1854
|
-
cliAction: Avo.CliAction.LOGIN,
|
|
1855
|
-
cliInvokedByCi: invokedByCi()
|
|
1856
|
-
});
|
|
1857
|
-
command();
|
|
1774
|
+
command();
|
|
1858
1775
|
})
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1776
|
+
.catch(() => {
|
|
1777
|
+
Avo.cliInvoked({
|
|
1778
|
+
schemaId: 'N/A',
|
|
1779
|
+
schemaName: 'N/A',
|
|
1780
|
+
branchId: 'N/A',
|
|
1781
|
+
branchName: 'N/A',
|
|
1782
|
+
userId_: installIdOrUserId(),
|
|
1783
|
+
cliAction: Avo.CliAction.LOGIN,
|
|
1784
|
+
cliInvokedByCi: invokedByCi(),
|
|
1785
|
+
force: undefined,
|
|
1786
|
+
});
|
|
1787
|
+
command();
|
|
1870
1788
|
});
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1789
|
+
},
|
|
1790
|
+
})
|
|
1791
|
+
.command({
|
|
1874
1792
|
command: 'logout',
|
|
1875
1793
|
desc: 'Log out from the Avo platform',
|
|
1876
1794
|
handler: () => {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
}
|
|
1886
|
-
if (token || user || tokens) {
|
|
1887
|
-
var msg = 'Logged out';
|
|
1888
|
-
if (token === currentToken) {
|
|
1889
|
-
if (user) {
|
|
1890
|
-
msg += ' from ' + bold(user.email);
|
|
1795
|
+
const command = () => {
|
|
1796
|
+
const user = conf.get('user');
|
|
1797
|
+
const tokens = conf.get('tokens');
|
|
1798
|
+
const currentToken = tokens.refreshToken;
|
|
1799
|
+
const token = currentToken;
|
|
1800
|
+
api.setRefreshToken(token);
|
|
1801
|
+
if (token) {
|
|
1802
|
+
logout(token);
|
|
1891
1803
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1804
|
+
if (token || user || tokens) {
|
|
1805
|
+
let msg = 'Logged out';
|
|
1806
|
+
if (token === currentToken) {
|
|
1807
|
+
if (user) {
|
|
1808
|
+
msg += ` from ${bold(user.email)}`;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
else {
|
|
1812
|
+
msg += ` token "${bold(token)}"`;
|
|
1813
|
+
}
|
|
1814
|
+
report.log(msg);
|
|
1815
|
+
}
|
|
1816
|
+
else {
|
|
1817
|
+
report.log("No need to logout, you're not logged in");
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
loadAvoJson()
|
|
1821
|
+
.then((json) => {
|
|
1822
|
+
Avo.cliInvoked({
|
|
1823
|
+
schemaId: json.schema.id,
|
|
1824
|
+
schemaName: json.schema.name,
|
|
1825
|
+
branchId: json.branch.id,
|
|
1826
|
+
branchName: json.branch.name,
|
|
1827
|
+
userId_: installIdOrUserId(),
|
|
1828
|
+
cliAction: Avo.CliAction.LOGOUT,
|
|
1829
|
+
cliInvokedByCi: invokedByCi(),
|
|
1830
|
+
force: undefined,
|
|
1831
|
+
});
|
|
1832
|
+
command();
|
|
1913
1833
|
})
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1834
|
+
.catch(() => {
|
|
1835
|
+
Avo.cliInvoked({
|
|
1836
|
+
schemaId: 'N/A',
|
|
1837
|
+
schemaName: 'N/A',
|
|
1838
|
+
branchId: 'N/A',
|
|
1839
|
+
branchName: 'N/A',
|
|
1840
|
+
userId_: installIdOrUserId(),
|
|
1841
|
+
cliAction: Avo.CliAction.LOGOUT,
|
|
1842
|
+
cliInvokedByCi: invokedByCi(),
|
|
1843
|
+
force: undefined,
|
|
1844
|
+
});
|
|
1845
|
+
command();
|
|
1925
1846
|
});
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1847
|
+
},
|
|
1848
|
+
})
|
|
1849
|
+
.command({
|
|
1929
1850
|
command: 'whoami',
|
|
1930
1851
|
desc: 'Shows the currently logged in username',
|
|
1931
|
-
handler: argv => {
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1852
|
+
handler: (argv) => {
|
|
1853
|
+
const command = () => {
|
|
1854
|
+
requireAuth(argv, () => {
|
|
1855
|
+
if (conf.has('user')) {
|
|
1856
|
+
const user = conf.get('user');
|
|
1857
|
+
report.info(`Logged in as ${email(user.email)}`);
|
|
1858
|
+
}
|
|
1859
|
+
else {
|
|
1860
|
+
report.warn('Not logged in');
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
};
|
|
1864
|
+
loadAvoJson()
|
|
1865
|
+
.then((json) => {
|
|
1866
|
+
Avo.cliInvoked({
|
|
1867
|
+
schemaId: json.schema.id,
|
|
1868
|
+
schemaName: json.schema.name,
|
|
1869
|
+
branchId: json.branch.id,
|
|
1870
|
+
branchName: json.branch.name,
|
|
1871
|
+
userId_: installIdOrUserId(),
|
|
1872
|
+
cliAction: Avo.CliAction.WHOAMI,
|
|
1873
|
+
cliInvokedByCi: invokedByCi(),
|
|
1874
|
+
force: undefined,
|
|
1875
|
+
});
|
|
1876
|
+
command();
|
|
1955
1877
|
})
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1878
|
+
.catch(() => {
|
|
1879
|
+
Avo.cliInvoked({
|
|
1880
|
+
schemaId: 'N/A',
|
|
1881
|
+
schemaName: 'N/A',
|
|
1882
|
+
branchId: 'N/A',
|
|
1883
|
+
branchName: 'N/A',
|
|
1884
|
+
userId_: installIdOrUserId(),
|
|
1885
|
+
cliAction: Avo.CliAction.WHOAMI,
|
|
1886
|
+
cliInvokedByCi: invokedByCi(),
|
|
1887
|
+
force: undefined,
|
|
1888
|
+
});
|
|
1889
|
+
command();
|
|
1967
1890
|
});
|
|
1891
|
+
},
|
|
1892
|
+
})
|
|
1893
|
+
.demandCommand(1, 'must provide a valid command')
|
|
1894
|
+
.recommendCommands()
|
|
1895
|
+
.help().argv;
|
|
1896
|
+
/// ///////////////// ////////
|
|
1897
|
+
// catch unhandled promises
|
|
1898
|
+
process.on('unhandledRejection', (err) => {
|
|
1899
|
+
cancelWait();
|
|
1900
|
+
if (!(err instanceof Error) && !(err instanceof AvoError)) {
|
|
1901
|
+
report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
|
|
1968
1902
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
.recommendCommands()
|
|
1973
|
-
.help().argv;
|
|
1974
|
-
|
|
1975
|
-
/////////////////////////////////////////////////////////////////////////
|
|
1976
|
-
// LOGGING
|
|
1977
|
-
|
|
1978
|
-
function cmd(command) {
|
|
1979
|
-
return `${gray('`')}${cyan(command)}${gray('`')}`;
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
function link(url) {
|
|
1983
|
-
return underline(url);
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
function file(url) {
|
|
1987
|
-
return underline(url);
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
function email(email) {
|
|
1991
|
-
return underline(email);
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
function cancelWait() {
|
|
1995
|
-
if (_cancel !== null) {
|
|
1996
|
-
_cancel();
|
|
1997
|
-
_cancel = null;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
function wait(message, timeOut) {
|
|
2001
|
-
cancelWait();
|
|
2002
|
-
timeOut = timeOut || 300;
|
|
2003
|
-
let running = false;
|
|
2004
|
-
let spinner;
|
|
2005
|
-
let stopped = false;
|
|
2006
|
-
|
|
2007
|
-
setTimeout(() => {
|
|
2008
|
-
if (stopped) return;
|
|
2009
|
-
|
|
2010
|
-
spinner = ora(gray(message));
|
|
2011
|
-
spinner.color = 'gray';
|
|
2012
|
-
spinner.start();
|
|
2013
|
-
|
|
2014
|
-
running = true;
|
|
2015
|
-
}, timeOut);
|
|
2016
|
-
|
|
2017
|
-
const cancel = () => {
|
|
2018
|
-
stopped = true;
|
|
2019
|
-
if (running) {
|
|
2020
|
-
spinner.stop();
|
|
2021
|
-
running = false;
|
|
2022
|
-
}
|
|
2023
|
-
process.removeListener('nowExit', cancel);
|
|
2024
|
-
};
|
|
2025
|
-
|
|
2026
|
-
process.on('nowExit', cancel);
|
|
2027
|
-
cancelWait = cancel;
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
/////////////////////////////////////////////////////////////////////////
|
|
2031
|
-
// AUTH
|
|
2032
|
-
|
|
2033
|
-
function _haveValidAccessToken(refreshToken) {
|
|
2034
|
-
if (_.isEmpty(lastAccessToken)) {
|
|
2035
|
-
var tokens = conf.get('tokens');
|
|
2036
|
-
if (refreshToken === _.get(tokens, 'refreshToken')) {
|
|
2037
|
-
lastAccessToken = tokens;
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
return (
|
|
2042
|
-
_.has(lastAccessToken, 'idToken') &&
|
|
2043
|
-
lastAccessToken.refreshToken === refreshToken &&
|
|
2044
|
-
_.has(lastAccessToken, 'expiresAt') &&
|
|
2045
|
-
lastAccessToken.expiresAt > Date.now() + FIFTEEN_MINUTES_IN_MS
|
|
2046
|
-
);
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
function getAccessToken(refreshToken) {
|
|
2050
|
-
if (_haveValidAccessToken(refreshToken)) {
|
|
2051
|
-
return Promise.resolve(lastAccessToken);
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
return _refreshAccessToken(refreshToken);
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
function _refreshAccessToken(refreshToken) {
|
|
2058
|
-
return api
|
|
2059
|
-
.request('POST', '/auth/refresh', {
|
|
2060
|
-
origin: api.apiOrigin,
|
|
2061
|
-
data: {
|
|
2062
|
-
token: refreshToken
|
|
2063
|
-
}
|
|
2064
|
-
})
|
|
2065
|
-
.then(
|
|
2066
|
-
function(res) {
|
|
2067
|
-
if (res.status === 401 || res.status === 400) {
|
|
2068
|
-
return {idToken: refreshToken};
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
if (!_.isString(res.body.idToken)) {
|
|
2072
|
-
throw INVALID_CREDENTIAL_ERROR;
|
|
2073
|
-
}
|
|
2074
|
-
lastAccessToken = _.assign(
|
|
2075
|
-
{
|
|
2076
|
-
expiresAt: Date.now() + res.body.expiresIn * 1000,
|
|
2077
|
-
refreshToken: refreshToken
|
|
2078
|
-
},
|
|
2079
|
-
res.body
|
|
2080
|
-
);
|
|
2081
|
-
|
|
2082
|
-
var currentRefreshToken = _.get(conf.get('tokens'), 'refreshToken');
|
|
2083
|
-
if (refreshToken === currentRefreshToken) {
|
|
2084
|
-
conf.set('tokens', lastAccessToken);
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
return lastAccessToken;
|
|
2088
|
-
},
|
|
2089
|
-
function(err) {
|
|
2090
|
-
throw INVALID_CREDENTIAL_ERROR;
|
|
2091
|
-
}
|
|
2092
|
-
);
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
function _getLoginUrl(callbackUrl) {
|
|
2096
|
-
return (
|
|
2097
|
-
api.authOrigin +
|
|
2098
|
-
'/auth/cli?' +
|
|
2099
|
-
_.map(
|
|
2100
|
-
{
|
|
2101
|
-
state: nonce,
|
|
2102
|
-
redirect_uri: callbackUrl
|
|
2103
|
-
},
|
|
2104
|
-
function(v, k) {
|
|
2105
|
-
return k + '=' + encodeURIComponent(v);
|
|
2106
|
-
}
|
|
2107
|
-
).join('&')
|
|
2108
|
-
);
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
function _loginWithLocalhost(port) {
|
|
2112
|
-
return new Promise(function(resolve, reject) {
|
|
2113
|
-
var callbackUrl = _getCallbackUrl(port);
|
|
2114
|
-
var authUrl = _getLoginUrl(callbackUrl);
|
|
2115
|
-
|
|
2116
|
-
var server = http.createServer(function(req, res) {
|
|
2117
|
-
var tokens;
|
|
2118
|
-
var query = _.get(url.parse(req.url, true), 'query', {});
|
|
2119
|
-
|
|
2120
|
-
if (query.state === nonce && _.isString(query.code)) {
|
|
2121
|
-
return _getTokensFromAuthorizationCode(query.code, callbackUrl)
|
|
2122
|
-
.then(function(result) {
|
|
2123
|
-
tokens = result;
|
|
2124
|
-
return _respondWithRedirect(
|
|
2125
|
-
req,
|
|
2126
|
-
res,
|
|
2127
|
-
api.authOrigin + '/auth/cli/success'
|
|
2128
|
-
);
|
|
2129
|
-
})
|
|
2130
|
-
.then(function() {
|
|
2131
|
-
cancelWait();
|
|
2132
|
-
server.shutdown();
|
|
2133
|
-
return resolve({
|
|
2134
|
-
user: jwt.decode(tokens.idToken),
|
|
2135
|
-
tokens: tokens
|
|
2136
|
-
});
|
|
2137
|
-
})
|
|
2138
|
-
.catch(function() {
|
|
2139
|
-
return _respondWithRedirect(
|
|
2140
|
-
req,
|
|
2141
|
-
res,
|
|
2142
|
-
api.authOrigin + '/auth/cli/error'
|
|
2143
|
-
);
|
|
2144
|
-
});
|
|
2145
|
-
}
|
|
2146
|
-
_respondWithRedirect(req, res, api.authOrigin + '/auth/cli/error');
|
|
2147
|
-
});
|
|
2148
|
-
|
|
2149
|
-
server = httpShutdown(server);
|
|
2150
|
-
|
|
2151
|
-
server.listen(port, function() {
|
|
2152
|
-
report.info(`Visit this URL on any device to login: ${link(authUrl)}`);
|
|
2153
|
-
wait(`Waiting for authentication...`);
|
|
2154
|
-
|
|
2155
|
-
opn(authUrl, {wait: false});
|
|
2156
|
-
});
|
|
2157
|
-
|
|
2158
|
-
server.on('error', function() {
|
|
2159
|
-
_loginWithoutLocalhost().then(resolve, reject);
|
|
2160
|
-
});
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
function _loginWithoutLocalhost() {
|
|
2165
|
-
var callbackUrl = _getCallbackUrl();
|
|
2166
|
-
var authUrl = _getLoginUrl(callbackUrl);
|
|
2167
|
-
|
|
2168
|
-
report.info(`Visit this URL on any device to login: ${url(authUrl)}`);
|
|
2169
|
-
|
|
2170
|
-
opn(authUrl, {wait: false});
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
function login() {
|
|
2174
|
-
return _getPort().then(_loginWithLocalhost, _loginWithoutLocalhost);
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
function _respondWithRedirect(req, res, url) {
|
|
2178
|
-
return new Promise(function(resolve, reject) {
|
|
2179
|
-
res.writeHead(302, {
|
|
2180
|
-
Location: url
|
|
2181
|
-
});
|
|
2182
|
-
res.end();
|
|
2183
|
-
req.socket.destroy();
|
|
2184
|
-
return resolve();
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
function _getTokensFromAuthorizationCode(code, callbackUrl) {
|
|
2189
|
-
return api
|
|
2190
|
-
.request('POST', '/auth/token', {
|
|
2191
|
-
origin: api.apiOrigin,
|
|
2192
|
-
data: {
|
|
2193
|
-
token: code,
|
|
2194
|
-
redirect_uri: callbackUrl
|
|
2195
|
-
}
|
|
2196
|
-
})
|
|
2197
|
-
.then(
|
|
2198
|
-
function(res) {
|
|
2199
|
-
if (!_.has(res, 'body.idToken') && !_.has(res, 'body.refreshToken')) {
|
|
2200
|
-
throw INVALID_CREDENTIAL_ERROR;
|
|
2201
|
-
}
|
|
2202
|
-
lastAccessToken = _.assign(
|
|
2203
|
-
{
|
|
2204
|
-
expiresAt: Date.now() + res.body.expiresIn * 1000
|
|
2205
|
-
},
|
|
2206
|
-
res.body
|
|
2207
|
-
);
|
|
2208
|
-
return lastAccessToken;
|
|
2209
|
-
},
|
|
2210
|
-
function(err) {
|
|
2211
|
-
throw INVALID_CREDENTIAL_ERROR;
|
|
2212
|
-
}
|
|
2213
|
-
);
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
function _getCallbackUrl(port) {
|
|
2217
|
-
if (_.isUndefined(port)) {
|
|
2218
|
-
return 'urn:ietf:wg:oauth:2.0:oob';
|
|
2219
|
-
}
|
|
2220
|
-
return 'http://localhost:' + port;
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
function logout(refreshToken) {
|
|
2224
|
-
if (lastAccessToken.refreshToken === refreshToken) {
|
|
2225
|
-
lastAccessToken = {};
|
|
2226
|
-
}
|
|
2227
|
-
var tokens = conf.get('tokens');
|
|
2228
|
-
var currentToken = _.get(tokens, 'refreshToken');
|
|
2229
|
-
if (refreshToken === currentToken) {
|
|
2230
|
-
conf.delete('user');
|
|
2231
|
-
conf.delete('tokens');
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
function responseToError(response, body) {
|
|
2236
|
-
if (typeof body === 'string' && response.statusCode === 404) {
|
|
2237
|
-
body = {
|
|
2238
|
-
error: {
|
|
2239
|
-
message: 'Not Found'
|
|
2240
|
-
}
|
|
2241
|
-
};
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
if (response.statusCode < 400) {
|
|
2245
|
-
return null;
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
if (typeof body !== 'object') {
|
|
2249
|
-
try {
|
|
2250
|
-
body = JSON.parse(body);
|
|
2251
|
-
} catch (e) {
|
|
2252
|
-
body = {};
|
|
1903
|
+
else {
|
|
1904
|
+
// @ts-ignore
|
|
1905
|
+
report.error(err.message);
|
|
2253
1906
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
if (!body.error) {
|
|
2257
|
-
var message = response.statusCode === 404 ? 'Not Found' : 'Unknown Error';
|
|
2258
|
-
body.error = {
|
|
2259
|
-
message: message
|
|
2260
|
-
};
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
var message = `HTTP Error: ${response.statusCode}, ${body.error.message ||
|
|
2264
|
-
body.error}`;
|
|
2265
|
-
|
|
2266
|
-
var exitCode;
|
|
2267
|
-
if (response.statusCode >= 500) {
|
|
2268
|
-
// 5xx errors are unexpected
|
|
2269
|
-
exitCode = 2;
|
|
2270
|
-
} else {
|
|
2271
|
-
// 4xx errors happen sometimes
|
|
2272
|
-
exitCode = 1;
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
_.unset(response, 'request.headers');
|
|
2276
|
-
return new AvoError(message, {
|
|
2277
|
-
context: {
|
|
2278
|
-
body: body,
|
|
2279
|
-
response: response
|
|
2280
|
-
},
|
|
2281
|
-
exit: exitCode
|
|
2282
|
-
});
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
function requireAuth(argv, cb) {
|
|
2286
|
-
let tokens = conf.get('tokens');
|
|
2287
|
-
let user = conf.get('user');
|
|
2288
|
-
|
|
2289
|
-
let tokenOpt = argv.token || process.env.AVO_TOKEN;
|
|
2290
|
-
|
|
2291
|
-
if (tokenOpt) {
|
|
2292
|
-
api.setRefreshToken(tokenOpt);
|
|
2293
|
-
return cb();
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
if (!user || !tokens) {
|
|
2297
|
-
report.error(`Command requires authentication. Run ${cmd('avo login')}`);
|
|
1907
|
+
// @ts-ignore
|
|
1908
|
+
// console.error(err.stack);
|
|
2298
1909
|
process.exit(1);
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
argv.user = user;
|
|
2303
|
-
argv.tokens = tokens;
|
|
2304
|
-
api.setRefreshToken(tokens.refreshToken);
|
|
2305
|
-
return cb();
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
|
-
//////////////////// ////////
|
|
2309
|
-
// catch unhandled promises
|
|
2310
|
-
|
|
2311
|
-
process.on('unhandledRejection', err => {
|
|
2312
|
-
cancelWait();
|
|
2313
|
-
|
|
2314
|
-
if (!(err instanceof Error) && !(err instanceof AvoError)) {
|
|
2315
|
-
err = new AvoError(`Promise rejected with value: ${util.inspect(err)}`);
|
|
2316
|
-
}
|
|
2317
|
-
report.error(err.message);
|
|
2318
|
-
// console.error(err.stack);
|
|
2319
|
-
|
|
2320
|
-
process.exit(1);
|
|
2321
1910
|
});
|