avo 2.0.2 → 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/cli.js +1648 -2086
- package/package.json +40 -27
package/cli.js
CHANGED
|
@@ -1,2321 +1,1883 @@
|
|
|
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
28
|
import Inspector 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: function 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
|
+
},
|
|
310
|
+
setUserProperties: () => { }, // noop
|
|
274
311
|
};
|
|
275
|
-
|
|
312
|
+
const inspector = new Inspector.AvoInspector({
|
|
313
|
+
apiKey: '3UWtteG9HenZ825cYoYr',
|
|
314
|
+
env: Inspector.AvoInspectorEnv.Prod,
|
|
315
|
+
version: '1.0.0',
|
|
316
|
+
appName: 'Avo CLI',
|
|
317
|
+
});
|
|
318
|
+
// setup Avo analytics
|
|
319
|
+
Avo.initAvo({ env: 'prod', inspector }, { client: Avo.Client.CLI, version: pkg.version }, {}, customAnalyticsDestination);
|
|
276
320
|
function isLegacyAvoJson(json) {
|
|
277
|
-
|
|
278
|
-
|
|
321
|
+
// check if legacy avo.json or un-initialized project
|
|
322
|
+
return json.types ?? !json.schema;
|
|
279
323
|
}
|
|
280
|
-
|
|
281
324
|
function avoNeedsUpdate(json) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
json.avo && json.avo.version && semver.major(pkg.version) < json.avo.version
|
|
285
|
-
);
|
|
325
|
+
// if avo.json has version, and this binary has lower version number it needs updating
|
|
326
|
+
return (json.avo && json.avo.version && semver.major(pkg.version) < json.avo.version);
|
|
286
327
|
}
|
|
287
|
-
|
|
288
328
|
const MERGE_CONFLICT_ANCESTOR = '|||||||';
|
|
289
329
|
const MERGE_CONFLICT_END = '>>>>>>>';
|
|
290
330
|
const MERGE_CONFLICT_SEP = '=======';
|
|
291
331
|
const MERGE_CONFLICT_START = '<<<<<<<';
|
|
292
332
|
function hasMergeConflicts(str) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
str.includes(MERGE_CONFLICT_END)
|
|
297
|
-
);
|
|
333
|
+
return (str.includes(MERGE_CONFLICT_START) &&
|
|
334
|
+
str.includes(MERGE_CONFLICT_SEP) &&
|
|
335
|
+
str.includes(MERGE_CONFLICT_END));
|
|
298
336
|
}
|
|
299
|
-
|
|
300
337
|
function extractConflictingFiles(str) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
338
|
+
const files = [[], []];
|
|
339
|
+
const lines = str.split(/\r?\n/g);
|
|
340
|
+
let skip = false;
|
|
341
|
+
while (lines.length) {
|
|
342
|
+
const line = lines.shift();
|
|
343
|
+
if (line.startsWith(MERGE_CONFLICT_START)) {
|
|
344
|
+
while (lines.length) {
|
|
345
|
+
const conflictLine = lines.shift();
|
|
346
|
+
if (conflictLine === MERGE_CONFLICT_SEP) {
|
|
347
|
+
skip = false;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
|
|
351
|
+
skip = true;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
files[0].push(conflictLine);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
while (lines.length) {
|
|
358
|
+
const conflictLine = lines.shift();
|
|
359
|
+
if (conflictLine.startsWith(MERGE_CONFLICT_END)) {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
files[1].push(conflictLine);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
318
366
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const conflictLine = lines.shift();
|
|
323
|
-
if (conflictLine.startsWith(MERGE_CONFLICT_END)) {
|
|
324
|
-
break;
|
|
325
|
-
} else {
|
|
326
|
-
files[1].push(conflictLine);
|
|
367
|
+
else {
|
|
368
|
+
files[0].push(line);
|
|
369
|
+
files[1].push(line);
|
|
327
370
|
}
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
files[0].push(line);
|
|
331
|
-
files[1].push(line);
|
|
332
371
|
}
|
|
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;
|
|
372
|
+
return [files[0].join('\n'), files[1].join('\n')];
|
|
353
373
|
}
|
|
354
|
-
|
|
355
374
|
const BRANCH_UP_TO_DATE = 'branch-up-to-date';
|
|
356
375
|
const BRANCH_NOT_UP_TO_DATE = 'branch-not-up-to-date';
|
|
357
|
-
|
|
358
376
|
function getMasterStatus(json) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
377
|
+
if (json.branch.id === 'master') {
|
|
378
|
+
return Promise.resolve(BRANCH_UP_TO_DATE);
|
|
379
|
+
}
|
|
362
380
|
return api
|
|
363
|
-
|
|
381
|
+
.request('POST', '/c/v1/master', {
|
|
364
382
|
origin: api.apiOrigin,
|
|
365
383
|
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
|
-
}
|
|
384
|
+
json: {
|
|
385
|
+
schemaId: json.schema.id,
|
|
386
|
+
branchId: json.branch.id,
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
.then(({ pullRequired }) => pullRequired ? BRANCH_NOT_UP_TO_DATE : BRANCH_UP_TO_DATE);
|
|
377
390
|
}
|
|
378
|
-
|
|
379
391
|
function pullMaster(json) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
392
|
+
if (json.branch.name === 'main') {
|
|
393
|
+
report.info('Your current branch is main');
|
|
394
|
+
return Promise.resolve(json);
|
|
395
|
+
}
|
|
384
396
|
wait(json.force ? 'Force pulling main into branch' : 'Pulling main into branch');
|
|
385
397
|
return api
|
|
386
|
-
|
|
398
|
+
.request('POST', '/c/v1/master/pull', {
|
|
387
399
|
origin: api.apiOrigin,
|
|
388
400
|
auth: true,
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
401
|
+
json: {
|
|
402
|
+
schemaId: json.schema.id,
|
|
403
|
+
branchId: json.branch.id,
|
|
404
|
+
force: json.force,
|
|
405
|
+
},
|
|
406
|
+
})
|
|
407
|
+
.then(() => {
|
|
396
408
|
cancelWait();
|
|
397
409
|
report.success('Branch is up to date with main');
|
|
398
410
|
return json;
|
|
399
|
-
|
|
400
|
-
}
|
|
411
|
+
});
|
|
401
412
|
}
|
|
402
|
-
|
|
403
413
|
function promptPullMaster(json) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
414
|
+
wait('Check if branch is up to date with main');
|
|
415
|
+
return getMasterStatus(json)
|
|
416
|
+
.then((branchStatus) => {
|
|
417
|
+
cancelWait();
|
|
418
|
+
if (branchStatus === BRANCH_NOT_UP_TO_DATE) {
|
|
419
|
+
return inquirer
|
|
420
|
+
.prompt([
|
|
421
|
+
{
|
|
422
|
+
type: 'confirm',
|
|
423
|
+
name: 'pull',
|
|
424
|
+
default: true,
|
|
425
|
+
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?`,
|
|
426
|
+
},
|
|
427
|
+
])
|
|
428
|
+
.then((answer) => Promise.resolve([branchStatus, answer]));
|
|
429
|
+
}
|
|
430
|
+
// We're expecting branchStatus === BRANCH_UP_TO_DATE
|
|
409
431
|
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
432
|
})
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
+
.then(([branchStatus, answer]) => {
|
|
434
|
+
if (branchStatus === BRANCH_UP_TO_DATE) {
|
|
435
|
+
report.success('Branch is up to date with main');
|
|
436
|
+
return Promise.resolve(json);
|
|
437
|
+
}
|
|
438
|
+
if (answer.pull) {
|
|
439
|
+
return pullMaster(json);
|
|
440
|
+
}
|
|
441
|
+
report.info('Did not pull main into branch');
|
|
433
442
|
return Promise.resolve(json);
|
|
434
|
-
}
|
|
435
443
|
});
|
|
436
444
|
}
|
|
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
|
-
|
|
445
|
+
function installIdOrUserId() {
|
|
446
|
+
const installId = conf.get('avo_install_id');
|
|
447
|
+
const user = conf.get('user');
|
|
448
|
+
if (user && user.user_id) {
|
|
449
|
+
return user.user_id;
|
|
450
|
+
}
|
|
451
|
+
return installId;
|
|
452
|
+
}
|
|
453
|
+
function invokedByCi() {
|
|
454
|
+
return process.env.CI !== undefined;
|
|
455
|
+
}
|
|
456
|
+
function requireAuth(argv, cb) {
|
|
457
|
+
const tokens = conf.get('tokens');
|
|
458
|
+
const user = conf.get('user');
|
|
459
|
+
const tokenOpt = argv.token ?? process.env.AVO_TOKEN;
|
|
460
|
+
if (tokenOpt) {
|
|
461
|
+
api.setRefreshToken(tokenOpt);
|
|
462
|
+
return cb();
|
|
463
|
+
}
|
|
464
|
+
if (!user || !tokens) {
|
|
465
|
+
report.error(`Command requires authentication. Run ${cmd('avo login')}`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
argv.user = user; // eslint-disable-line no-param-reassign
|
|
469
|
+
argv.tokens = tokens; // eslint-disable-line no-param-reassign
|
|
470
|
+
api.setRefreshToken(tokens.refreshToken);
|
|
471
|
+
return cb();
|
|
472
|
+
}
|
|
473
|
+
function init() {
|
|
474
|
+
const makeAvoJson = (schema) => {
|
|
475
|
+
report.success(`Initialized for workspace ${cyan(schema.name)}`);
|
|
476
|
+
return {
|
|
477
|
+
avo: {
|
|
478
|
+
version: semver.major(pkg.version),
|
|
479
|
+
},
|
|
480
|
+
schema: {
|
|
481
|
+
id: schema.id,
|
|
482
|
+
name: schema.name,
|
|
483
|
+
},
|
|
484
|
+
branch: {
|
|
485
|
+
id: 'master',
|
|
486
|
+
name: 'main',
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
};
|
|
490
|
+
wait('Initializing');
|
|
491
|
+
return api
|
|
492
|
+
.request('GET', '/c/v1/workspaces', {
|
|
493
|
+
origin: api.apiOrigin,
|
|
494
|
+
auth: true,
|
|
495
|
+
})
|
|
496
|
+
.then(({ workspaces }) => {
|
|
497
|
+
cancelWait();
|
|
498
|
+
const schemas = [...workspaces].sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
499
|
+
if (schemas.length > 1) {
|
|
500
|
+
const choices = schemas.map((schema) => ({
|
|
501
|
+
value: schema,
|
|
502
|
+
name: schema.name,
|
|
503
|
+
}));
|
|
504
|
+
return inquirer
|
|
505
|
+
.prompt([
|
|
506
|
+
{
|
|
507
|
+
type: 'list',
|
|
508
|
+
name: 'schema',
|
|
509
|
+
message: 'Select a workspace to initialize',
|
|
510
|
+
choices,
|
|
511
|
+
},
|
|
512
|
+
])
|
|
513
|
+
.then((answer) => makeAvoJson(answer.schema));
|
|
514
|
+
}
|
|
515
|
+
if (schemas.length === 0) {
|
|
516
|
+
throw new AvoError(`No workspaces to initialize. Go to ${link('wwww.avo.app')} to create one`);
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
const schema = schemas[0];
|
|
520
|
+
return makeAvoJson(schema);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function validateAvoJson(json) {
|
|
525
|
+
if (avoNeedsUpdate(json)) {
|
|
526
|
+
throw new AvoError('Your avo CLI is outdated, please update');
|
|
527
|
+
}
|
|
528
|
+
if (isLegacyAvoJson(json)) {
|
|
529
|
+
return init();
|
|
530
|
+
}
|
|
531
|
+
// augment the latest major version into avo.json
|
|
532
|
+
return { ...json, avo: { ...json.avo, version: semver.major(pkg.version) } };
|
|
533
|
+
}
|
|
534
|
+
function fetchBranches(json) {
|
|
535
|
+
wait('Fetching open branches');
|
|
536
|
+
const payload = {
|
|
537
|
+
origin: api.apiOrigin,
|
|
538
|
+
auth: true,
|
|
539
|
+
json: {
|
|
540
|
+
schemaId: json.schema.id,
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
return api
|
|
544
|
+
.request('POST', '/c/v1/branches', payload)
|
|
545
|
+
.then((data) => {
|
|
546
|
+
cancelWait();
|
|
547
|
+
const branches = [...data.branches].sort((a, b) => {
|
|
548
|
+
if (a.name < b.name)
|
|
549
|
+
return -1;
|
|
550
|
+
if (a.name > b.name)
|
|
551
|
+
return 1;
|
|
552
|
+
return 0;
|
|
553
|
+
});
|
|
554
|
+
// The api still returns master for backwards comparability so we manually
|
|
555
|
+
// update the branch name to main
|
|
556
|
+
return branches.map((branch) => branch.name === 'master' ? { ...branch, name: 'main' } : branch);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function checkout(branchToCheckout, json) {
|
|
560
|
+
return fetchBranches(json).then((branches) => {
|
|
561
|
+
if (!branchToCheckout) {
|
|
562
|
+
const choices = branches.map((branch) => ({
|
|
563
|
+
value: branch,
|
|
564
|
+
name: branch.name,
|
|
565
|
+
}));
|
|
566
|
+
const currentBranch = branches.find(({ id }) => id === json.branch.id);
|
|
567
|
+
return inquirer
|
|
568
|
+
.prompt([
|
|
569
|
+
{
|
|
570
|
+
type: 'list',
|
|
571
|
+
name: 'branch',
|
|
572
|
+
message: 'Select a branch',
|
|
573
|
+
default: currentBranch ?? branches.find(({ id }) => id === 'master'),
|
|
574
|
+
choices,
|
|
575
|
+
pageSize: 15,
|
|
576
|
+
},
|
|
577
|
+
])
|
|
578
|
+
.then((answer) => {
|
|
579
|
+
if (answer.branch === currentBranch) {
|
|
580
|
+
report.info(`Already on '${currentBranch.name}'`);
|
|
581
|
+
return json;
|
|
582
|
+
}
|
|
583
|
+
const { branch } = answer;
|
|
584
|
+
report.success(`Switched to branch '${branch.name}'`);
|
|
585
|
+
return {
|
|
586
|
+
...json,
|
|
587
|
+
branch: {
|
|
588
|
+
id: branch.id,
|
|
589
|
+
name: branch.name,
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (branchToCheckout === 'master') {
|
|
595
|
+
report.info("The master branch has been renamed to main. Continuing checkout with main branch...'");
|
|
596
|
+
}
|
|
597
|
+
const adjustedBranchToCheckout = branchToCheckout === 'master' ? 'main' : branchToCheckout;
|
|
598
|
+
if (adjustedBranchToCheckout === json.branch.name) {
|
|
599
|
+
// XXX should check here if json.branch.id === branch.id from server
|
|
600
|
+
// if not, it indicates branch delete, same branch re-created and client is out of sync
|
|
601
|
+
report.info(`Already on '${adjustedBranchToCheckout}'`);
|
|
602
|
+
return json;
|
|
603
|
+
}
|
|
604
|
+
const branch = branches.find(({ name }) => name === adjustedBranchToCheckout);
|
|
605
|
+
if (!branch) {
|
|
606
|
+
report.error(`Branch '${adjustedBranchToCheckout}' does not exist. Run ${cmd('avo checkout')} to list available branches`);
|
|
607
|
+
}
|
|
608
|
+
report.success(`Switched to branch '${branch.name}'`);
|
|
609
|
+
return {
|
|
610
|
+
...json,
|
|
611
|
+
branch: {
|
|
612
|
+
id: branch.id,
|
|
613
|
+
name: branch.name,
|
|
614
|
+
},
|
|
615
|
+
};
|
|
464
616
|
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
branchId: head.branch.id,
|
|
479
|
-
branchName: head.branch.name
|
|
617
|
+
}
|
|
618
|
+
function resolveAvoJsonConflicts(avoFile, { argv, skipPullMaster }) {
|
|
619
|
+
report.info('Resolving Avo merge conflicts');
|
|
620
|
+
const files = extractConflictingFiles(avoFile);
|
|
621
|
+
const head = JSON.parse(files[0]);
|
|
622
|
+
const incoming = JSON.parse(files[1]);
|
|
623
|
+
Avo.cliConflictResolveAttempted({
|
|
624
|
+
userId_: installIdOrUserId(),
|
|
625
|
+
cliInvokedByCi: invokedByCi(),
|
|
626
|
+
schemaId: head.schema.id,
|
|
627
|
+
schemaName: head.schema.name,
|
|
628
|
+
branchId: head.branch.id,
|
|
629
|
+
branchName: head.branch.name,
|
|
480
630
|
});
|
|
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
|
-
|
|
631
|
+
if (head.avo.version !== incoming.avo.version ||
|
|
632
|
+
head.schema.id !== incoming.schema.id) {
|
|
633
|
+
Avo.cliConflictResolveFailed({
|
|
634
|
+
userId_: installIdOrUserId(),
|
|
635
|
+
cliInvokedByCi: invokedByCi(),
|
|
636
|
+
schemaId: head.schema.id,
|
|
637
|
+
schemaName: head.schema.name,
|
|
638
|
+
branchId: head.branch.id,
|
|
639
|
+
branchName: head.branch.name,
|
|
640
|
+
});
|
|
641
|
+
throw new Error("Could not automatically resolve merge conflicts in avo.json. Resolve merge conflicts in avo.json before running 'avo pull' again.");
|
|
642
|
+
}
|
|
643
|
+
if (JSON.stringify(head.sources.map((s) => s.id)) !==
|
|
644
|
+
JSON.stringify(incoming.sources.map((s) => s.id))) {
|
|
645
|
+
Avo.cliConflictResolveFailed({
|
|
646
|
+
userId_: installIdOrUserId(),
|
|
647
|
+
cliInvokedByCi: invokedByCi(),
|
|
648
|
+
schemaId: head.schema.id,
|
|
649
|
+
schemaName: head.schema.name,
|
|
650
|
+
branchId: head.branch.id,
|
|
651
|
+
branchName: head.branch.name,
|
|
652
|
+
});
|
|
653
|
+
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.");
|
|
654
|
+
}
|
|
655
|
+
const nextAvoJson = {
|
|
656
|
+
avo: head.avo,
|
|
657
|
+
schema: head.schema,
|
|
658
|
+
branch: head.branch,
|
|
659
|
+
sources: head.sources,
|
|
660
|
+
};
|
|
661
|
+
return requireAuth(argv, () => fetchBranches(nextAvoJson).then((branches) => {
|
|
662
|
+
const isHeadBranchOpen = branches.find((branch) => branch.id === nextAvoJson.branch.id);
|
|
663
|
+
const isIncomingBranchOpen = branches.find((branch) => branch.id === incoming.branch.id);
|
|
664
|
+
function switchBranchIfRequired(json) {
|
|
665
|
+
if (isHeadBranchOpen) {
|
|
666
|
+
return Promise.resolve(json);
|
|
667
|
+
}
|
|
668
|
+
report.info(`Your current branch '${json.branch.name}' has been closed or merged. Go to another branch:`);
|
|
669
|
+
return checkout(null, json);
|
|
510
670
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
incoming.branch.id == 'master'
|
|
518
|
-
) {
|
|
519
|
-
return Promise.resolve([true, json]);
|
|
520
|
-
} else {
|
|
671
|
+
return switchBranchIfRequired(nextAvoJson)
|
|
672
|
+
.then((json) => {
|
|
673
|
+
if (head.branch.id === incoming.branch.id ||
|
|
674
|
+
incoming.branch.id === 'master') {
|
|
675
|
+
return Promise.resolve([true, json]);
|
|
676
|
+
}
|
|
521
677
|
return Promise.resolve([false, json]);
|
|
522
|
-
}
|
|
523
678
|
})
|
|
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
|
-
}
|
|
679
|
+
.then(([isDone, json]) => {
|
|
680
|
+
if (!isDone && isIncomingBranchOpen && argv.force) {
|
|
681
|
+
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`)}`);
|
|
682
|
+
return Promise.resolve(json);
|
|
683
|
+
}
|
|
684
|
+
if (!isDone && isIncomingBranchOpen) {
|
|
685
|
+
Avo.cliConflictResolveFailed({
|
|
686
|
+
userId_: installIdOrUserId(),
|
|
687
|
+
cliInvokedByCi: invokedByCi(),
|
|
688
|
+
schemaId: head.schema.id,
|
|
689
|
+
schemaName: head.schema.name,
|
|
690
|
+
branchId: head.branch.id,
|
|
691
|
+
branchName: head.branch.name,
|
|
692
|
+
});
|
|
693
|
+
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.`);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
return Promise.resolve(json);
|
|
697
|
+
}
|
|
553
698
|
})
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
699
|
+
.then((json) => {
|
|
700
|
+
if (skipPullMaster) {
|
|
701
|
+
return Promise.resolve(json);
|
|
702
|
+
}
|
|
558
703
|
return promptPullMaster(json);
|
|
559
|
-
}
|
|
560
704
|
})
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
705
|
+
.then((json) => {
|
|
706
|
+
Avo.cliConflictResolveSucceeded({
|
|
707
|
+
userId_: installIdOrUserId(),
|
|
708
|
+
cliInvokedByCi: invokedByCi(),
|
|
709
|
+
schemaId: head.schema.id,
|
|
710
|
+
schemaName: head.schema.name,
|
|
711
|
+
branchId: head.branch.id,
|
|
712
|
+
branchName: head.branch.name,
|
|
713
|
+
});
|
|
714
|
+
report.success('Successfully resolved Avo merge conflicts');
|
|
715
|
+
return validateAvoJson(json);
|
|
572
716
|
});
|
|
573
|
-
});
|
|
574
|
-
});
|
|
717
|
+
}));
|
|
575
718
|
}
|
|
576
|
-
|
|
577
719
|
function loadAvoJson() {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
720
|
+
return loadJsonFile('avo.json')
|
|
721
|
+
.then(validateAvoJson)
|
|
722
|
+
.catch((err) => {
|
|
723
|
+
if (err.code === 'ENOENT') {
|
|
724
|
+
throw new AvoError(`File ${file('avo.json')} does not exist. Run ${cmd('avo init')}`);
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
throw err;
|
|
728
|
+
}
|
|
588
729
|
});
|
|
589
730
|
}
|
|
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);
|
|
731
|
+
function loadAvoJsonOrInit({ argv, skipPullMaster, skipInit }) {
|
|
732
|
+
return pify(fs.readFile)('avo.json', 'utf8')
|
|
733
|
+
.then((avoFile) => {
|
|
734
|
+
if (hasMergeConflicts(avoFile)) {
|
|
735
|
+
return resolveAvoJsonConflicts(avoFile, {
|
|
736
|
+
argv,
|
|
737
|
+
skipPullMaster,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
return Promise.resolve(JSON.parse(avoFile));
|
|
606
741
|
})
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
742
|
+
.then((json) => Promise.resolve({ ...json, force: argv.f === true }))
|
|
743
|
+
.then(validateAvoJson)
|
|
744
|
+
.catch((error) => {
|
|
745
|
+
if (error.code === 'ENOENT' && skipInit) {
|
|
746
|
+
return Promise.resolve();
|
|
747
|
+
}
|
|
748
|
+
if (error.code === 'ENOENT') {
|
|
749
|
+
report.info('Avo not initialized');
|
|
750
|
+
return requireAuth(argv, init);
|
|
751
|
+
}
|
|
615
752
|
throw error;
|
|
616
|
-
}
|
|
617
753
|
});
|
|
618
754
|
}
|
|
619
|
-
|
|
620
755
|
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
|
-
});
|
|
756
|
+
return writeJsonFile('avo.json', json, {
|
|
757
|
+
indent: 2,
|
|
758
|
+
}).then(() => json);
|
|
682
759
|
}
|
|
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
|
-
})
|
|
760
|
+
function codegen(json, { schema, sources: targets, warnings, errors }) {
|
|
761
|
+
const newJson = { ...JSON.parse(JSON.stringify(json)), schema };
|
|
762
|
+
newJson.sources = newJson.sources.map((source) => {
|
|
763
|
+
const target = targets.find(({ id }) => id === source.id);
|
|
764
|
+
if (target) {
|
|
765
|
+
return {
|
|
766
|
+
...source,
|
|
767
|
+
actionId: target.actionId,
|
|
768
|
+
name: target.name,
|
|
769
|
+
id: target.id,
|
|
770
|
+
path: source.path,
|
|
771
|
+
branchId: target.branchId,
|
|
772
|
+
updatedAt: target.updatedAt,
|
|
773
|
+
};
|
|
738
774
|
}
|
|
739
|
-
|
|
775
|
+
return source;
|
|
740
776
|
});
|
|
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
|
|
777
|
+
const sourceTasks = targets.map((target) => Promise.all(target.code.map((code) => writeFile(code.path, code.content))));
|
|
778
|
+
const avoJsonTask = writeAvoJson(newJson);
|
|
779
|
+
Promise.all([avoJsonTask].concat(sourceTasks)).then(() => {
|
|
780
|
+
if (errors !== undefined && errors !== null && errors !== '') {
|
|
781
|
+
report.warn(`${errors}\n`);
|
|
783
782
|
}
|
|
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`);
|
|
783
|
+
if (warnings !== undefined &&
|
|
784
|
+
warnings !== null &&
|
|
785
|
+
Array.isArray(warnings)) {
|
|
786
|
+
warnings.forEach((warning) => {
|
|
787
|
+
report.warn(warning);
|
|
788
|
+
});
|
|
812
789
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
790
|
+
report.success(`Analytics ${targets.length > 1 ? 'wrappers' : 'wrapper'} successfully updated`);
|
|
791
|
+
targets.forEach((target) => {
|
|
792
|
+
const source = newJson.sources.find(({ id }) => id === target.id);
|
|
793
|
+
report.tree('sources', [
|
|
794
|
+
{
|
|
795
|
+
name: source.name,
|
|
796
|
+
children: target.code.map((code) => ({ name: code.path })),
|
|
797
|
+
},
|
|
798
|
+
]);
|
|
820
799
|
});
|
|
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
800
|
});
|
|
850
801
|
}
|
|
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
802
|
function matchesSource(source, filter) {
|
|
950
|
-
|
|
803
|
+
return source.name.toLowerCase() === filter.toLowerCase();
|
|
951
804
|
}
|
|
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', {
|
|
805
|
+
function selectSource(sourceToAdd, json) {
|
|
806
|
+
wait('Fetching sources');
|
|
807
|
+
return api
|
|
808
|
+
.request('POST', '/c/v1/sources', {
|
|
971
809
|
origin: api.apiOrigin,
|
|
972
810
|
auth: true,
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
return {id: source.id, path: source.path};
|
|
978
|
-
}),
|
|
979
|
-
force: json.force || false
|
|
980
|
-
}
|
|
981
|
-
});
|
|
811
|
+
json: {
|
|
812
|
+
schemaId: json.schema.id,
|
|
813
|
+
branchId: json.branch.id,
|
|
814
|
+
},
|
|
982
815
|
})
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
816
|
+
.then((data) => {
|
|
817
|
+
cancelWait();
|
|
818
|
+
const existingSources = json.sources ?? [];
|
|
819
|
+
let sources = data.sources
|
|
820
|
+
.filter((source) => !existingSources.find(({ id }) => source.id === id))
|
|
821
|
+
.sort((a, b) => {
|
|
822
|
+
if (a.name < b.name)
|
|
823
|
+
return -1;
|
|
824
|
+
if (a.name > b.name)
|
|
825
|
+
return 1;
|
|
826
|
+
return 0;
|
|
827
|
+
});
|
|
828
|
+
const prompts = [
|
|
829
|
+
{
|
|
830
|
+
type: 'fuzzypath',
|
|
831
|
+
name: 'folder',
|
|
832
|
+
excludePath: (maybeExcludePath) => maybeExcludePath.startsWith('node_modules') ||
|
|
833
|
+
maybeExcludePath.startsWith('.git'),
|
|
834
|
+
itemType: 'directory',
|
|
835
|
+
rootPath: '.',
|
|
836
|
+
message: 'Select a folder to save the analytics wrapper in',
|
|
837
|
+
default: '.',
|
|
838
|
+
suggestOnly: false,
|
|
839
|
+
depthLimit: 10,
|
|
840
|
+
},
|
|
841
|
+
];
|
|
842
|
+
if (!sourceToAdd) {
|
|
843
|
+
const choices = sources.map((source) => ({
|
|
844
|
+
value: source,
|
|
845
|
+
name: source.name,
|
|
846
|
+
}));
|
|
847
|
+
prompts.unshift({
|
|
848
|
+
type: 'list',
|
|
849
|
+
name: 'source',
|
|
850
|
+
message: 'Select a source to set up',
|
|
851
|
+
// @ts-ignore
|
|
852
|
+
choices,
|
|
853
|
+
pageSize: 15,
|
|
854
|
+
});
|
|
855
|
+
prompts.push({
|
|
856
|
+
type: 'input',
|
|
857
|
+
name: 'filename',
|
|
858
|
+
message: 'Select a filename for the analytics wrapper',
|
|
859
|
+
// @ts-ignore
|
|
860
|
+
default(answers) {
|
|
861
|
+
return answers.source.filenameHint;
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
const source = sources.find((soruceToFind) => matchesSource(soruceToFind, sourceToAdd));
|
|
867
|
+
if (!source) {
|
|
868
|
+
throw new AvoError(`Source ${sourceToAdd} does not exist`);
|
|
869
|
+
}
|
|
870
|
+
prompts.push({
|
|
871
|
+
type: 'input',
|
|
872
|
+
name: 'filename',
|
|
873
|
+
message: 'Select a filename for the library',
|
|
874
|
+
// @ts-ignore
|
|
875
|
+
default() {
|
|
876
|
+
return source.filenameHint;
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return inquirer.prompt(prompts).then((answer) => {
|
|
881
|
+
const relativePath = path.relative(process.cwd(), path.join(path.resolve(answer.folder), answer.filename));
|
|
882
|
+
let source;
|
|
883
|
+
if (sourceToAdd) {
|
|
884
|
+
source = sources.find((sourceToFind) => matchesSource(sourceToFind, sourceToAdd));
|
|
885
|
+
source = { id: source.id, name: source.name, path: relativePath };
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
source = {
|
|
889
|
+
id: answer.source.id,
|
|
890
|
+
name: answer.source.name,
|
|
891
|
+
path: relativePath,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
sources = (json.sources ?? []).concat([source]);
|
|
895
|
+
const newJson = { ...json, sources };
|
|
896
|
+
report.info(`Added source ${source.name} to the project`);
|
|
897
|
+
report.info(`Run 'avo pull "${source.name}"' to pull the latest analytics wrapper for this source`);
|
|
898
|
+
return newJson;
|
|
1000
899
|
});
|
|
1001
|
-
}
|
|
1002
900
|
});
|
|
1003
901
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
902
|
+
function pull(sourceFilter, json) {
|
|
903
|
+
const sources = sourceFilter
|
|
904
|
+
? [json.sources.find((source) => matchesSource(source, sourceFilter))]
|
|
905
|
+
: json.sources;
|
|
906
|
+
const sourceNames = sources.map((source) => source.name);
|
|
907
|
+
wait(`Pulling ${sourceNames.join(', ')}`);
|
|
908
|
+
return getMasterStatus(json)
|
|
909
|
+
.then((masterStatus) => {
|
|
910
|
+
if (masterStatus === BRANCH_NOT_UP_TO_DATE) {
|
|
911
|
+
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'.`);
|
|
912
|
+
}
|
|
913
|
+
return Promise.resolve();
|
|
914
|
+
})
|
|
915
|
+
.then(() => api.request('POST', '/c/v1/pull', {
|
|
916
|
+
origin: api.apiOrigin,
|
|
917
|
+
auth: true,
|
|
918
|
+
json: {
|
|
919
|
+
schemaId: json.schema.id,
|
|
920
|
+
branchId: json.branch.id,
|
|
921
|
+
sources: sources.map((source) => ({
|
|
922
|
+
id: source.id,
|
|
923
|
+
path: source.path,
|
|
924
|
+
})),
|
|
925
|
+
force: json.force ?? false,
|
|
926
|
+
},
|
|
927
|
+
}))
|
|
928
|
+
.then((result) => {
|
|
929
|
+
cancelWait();
|
|
930
|
+
if (result.ok) {
|
|
931
|
+
codegen(json, result);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
report.error(`Branch ${result.branchName} was ${result.reason} ${dateFns.formatDistance(new Date(), new Date(result.closedAt))} ago. Pick another branch.`);
|
|
935
|
+
checkout(null, json).then((data) => pull(sourceFilter, data));
|
|
936
|
+
}
|
|
937
|
+
});
|
|
1017
938
|
}
|
|
1018
|
-
|
|
1019
939
|
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;
|
|
940
|
+
const isGlobal = regex.global;
|
|
941
|
+
const lines = data.split('\n');
|
|
942
|
+
const fileMatches = [];
|
|
943
|
+
let lastIndex = 0;
|
|
944
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
945
|
+
const lineContents = lines[index];
|
|
946
|
+
const line = lastIndex + index;
|
|
947
|
+
let match;
|
|
948
|
+
while (true) {
|
|
949
|
+
match = regex.exec(lineContents);
|
|
950
|
+
if (!match)
|
|
951
|
+
break;
|
|
952
|
+
const start = match.index;
|
|
953
|
+
const end = match.index + match[0].length;
|
|
954
|
+
fileMatches.push({
|
|
955
|
+
line,
|
|
956
|
+
start,
|
|
957
|
+
end,
|
|
958
|
+
lineContents,
|
|
959
|
+
});
|
|
960
|
+
if (!isGlobal)
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
1045
963
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
lastIndex += lines.length;
|
|
1049
|
-
|
|
1050
|
-
return fileMatches;
|
|
964
|
+
lastIndex += lines.length;
|
|
965
|
+
return fileMatches;
|
|
1051
966
|
}
|
|
1052
|
-
|
|
1053
967
|
function getEventMap(data) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
return eventMap;
|
|
1063
|
-
} else {
|
|
968
|
+
const searchFor = 'AVOEVENTMAP:';
|
|
969
|
+
const lines = data.split('\n').filter((line) => line.indexOf(searchFor) > -1);
|
|
970
|
+
if (lines.length === 1) {
|
|
971
|
+
let line = lines[0].substring(lines[0].indexOf(searchFor) + searchFor.length);
|
|
972
|
+
line = line.substring(line.indexOf('['), line.indexOf(']') + 1);
|
|
973
|
+
const eventMap = JSON.parse(line);
|
|
974
|
+
return eventMap;
|
|
975
|
+
}
|
|
1064
976
|
return null;
|
|
1065
|
-
}
|
|
1066
977
|
}
|
|
1067
|
-
|
|
1068
978
|
function getModuleMap(data) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return moduleMap;
|
|
1078
|
-
} else {
|
|
979
|
+
const searchFor = 'AVOMODULEMAP:';
|
|
980
|
+
const lines = data.split('\n').filter((line) => line.indexOf(searchFor) > -1);
|
|
981
|
+
if (lines.length === 1) {
|
|
982
|
+
let line = lines[0].substring(lines[0].indexOf(searchFor) + searchFor.length);
|
|
983
|
+
line = line.substring(line.indexOf('"'), line.lastIndexOf('"') + 1);
|
|
984
|
+
const moduleMap = JSON.parse(line);
|
|
985
|
+
return moduleMap;
|
|
986
|
+
}
|
|
1079
987
|
return null;
|
|
1080
|
-
}
|
|
1081
988
|
}
|
|
1082
|
-
|
|
1083
989
|
function getSource(argv, json) {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
990
|
+
if (!json.sources || !json.sources.length) {
|
|
991
|
+
report.info('No sources configured.');
|
|
992
|
+
return requireAuth(argv, () => {
|
|
993
|
+
if (argv.source) {
|
|
994
|
+
report.info(`Setting up source "${argv.source}"`);
|
|
995
|
+
}
|
|
996
|
+
return selectSource(argv.source, json).then((sourceJson) => [
|
|
997
|
+
argv.source,
|
|
998
|
+
sourceJson,
|
|
999
|
+
]);
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
if (argv.source &&
|
|
1003
|
+
!json.sources.find((source) => matchesSource(source, argv.source))) {
|
|
1004
|
+
report.error(`Source ${argv.source} not found`);
|
|
1005
|
+
return requireAuth(argv, () => selectSource(argv.source, json).then((sourceJson) => [
|
|
1006
|
+
argv.source,
|
|
1007
|
+
sourceJson,
|
|
1008
|
+
]));
|
|
1009
|
+
}
|
|
1101
1010
|
return Promise.resolve([argv.source, json]);
|
|
1102
|
-
}
|
|
1103
1011
|
}
|
|
1104
|
-
|
|
1105
1012
|
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()) {
|
|
1013
|
+
let sources = source
|
|
1014
|
+
? json.sources.filter((s) => matchesSource(s, source))
|
|
1015
|
+
: json.sources;
|
|
1016
|
+
sources = sources.filter(({ analysis }) => analysis !== false);
|
|
1017
|
+
const fileCache = walk({
|
|
1018
|
+
ignoreFiles: ['.gitignore'],
|
|
1019
|
+
follow: false,
|
|
1020
|
+
}).then((results) => Promise.all(results
|
|
1021
|
+
.filter((result) => !result.startsWith('.git'))
|
|
1022
|
+
.map((resultPath) => pify(fs.lstat)(resultPath).then((stats) => {
|
|
1023
|
+
if (stats.isSymbolicLink()) {
|
|
1120
1024
|
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);
|
|
1025
|
+
}
|
|
1026
|
+
return pify(fs.readFile)(resultPath, 'utf8').then((data) => [
|
|
1027
|
+
resultPath,
|
|
1028
|
+
data,
|
|
1029
|
+
]);
|
|
1030
|
+
}))).then((cachePairs) => Object.fromEntries(cachePairs)));
|
|
1031
|
+
fileCache
|
|
1032
|
+
.then((cache) => {
|
|
1033
|
+
sources = Promise.all(sources.map((source) => pify(fs.readFile)(source.path, 'utf8').then((data) => {
|
|
1034
|
+
const eventMap = getEventMap(data);
|
|
1137
1035
|
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}`);
|
|
1036
|
+
const moduleMap = getModuleMap(data);
|
|
1037
|
+
const sourcePath = path.parse(source.path);
|
|
1038
|
+
const moduleName = source.analysis?.module ??
|
|
1039
|
+
moduleMap ??
|
|
1040
|
+
sourcePath.name ??
|
|
1041
|
+
'Avo';
|
|
1042
|
+
const sourcePathExts = [];
|
|
1043
|
+
if (sourcePath.ext === '.js' || sourcePath.ext === '.ts') {
|
|
1044
|
+
sourcePathExts.push('js');
|
|
1045
|
+
sourcePathExts.push('jsx');
|
|
1046
|
+
sourcePathExts.push('ts');
|
|
1047
|
+
sourcePathExts.push('tsx');
|
|
1048
|
+
}
|
|
1049
|
+
else if (sourcePath.ext === '.java' ||
|
|
1050
|
+
sourcePath.ext === '.kt') {
|
|
1051
|
+
sourcePathExts.push('java');
|
|
1052
|
+
sourcePathExts.push('kt');
|
|
1053
|
+
}
|
|
1054
|
+
else if (sourcePath.ext === '.m' ||
|
|
1055
|
+
sourcePath.ext === '.swift') {
|
|
1056
|
+
sourcePathExts.push('m');
|
|
1057
|
+
sourcePathExts.push('swift');
|
|
1058
|
+
}
|
|
1059
|
+
else if (sourcePath.ext === '.re') {
|
|
1060
|
+
sourcePathExts.push('re');
|
|
1061
|
+
sourcePathExts.push('res');
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
sourcePathExts.push(sourcePath.ext.substring(1));
|
|
1065
|
+
}
|
|
1066
|
+
if (argv.verbose) {
|
|
1067
|
+
console.log('Looking in files with extensions:', sourcePathExts);
|
|
1068
|
+
}
|
|
1069
|
+
const globs = [
|
|
1070
|
+
new Minimatch(source.analysis?.glob ??
|
|
1071
|
+
`**/*.+(${sourcePathExts.join('|')})`, {}),
|
|
1072
|
+
new Minimatch(`!${source.path}`, {}),
|
|
1073
|
+
];
|
|
1074
|
+
const lookup = {};
|
|
1075
|
+
Object.entries(cache).forEach(([cachePath, value]) => {
|
|
1076
|
+
if (globs.every((mm) => mm.match(cachePath))) {
|
|
1077
|
+
lookup[cachePath] = value;
|
|
1185
1078
|
}
|
|
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
1079
|
});
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1080
|
+
return Promise.all(eventMap.map((eventName) => {
|
|
1081
|
+
const re = new RegExp(`(${moduleName}\\.${eventName}|\\[${moduleName} ${eventName})`);
|
|
1082
|
+
const results = Object.entries(lookup)
|
|
1083
|
+
.map(([path, data]) => {
|
|
1084
|
+
if (argv.verbose) {
|
|
1085
|
+
report.info(`Looking for events in ${path}`);
|
|
1086
|
+
}
|
|
1087
|
+
const results = findMatches(data, re);
|
|
1088
|
+
return results.length ? [[path, results]] : [];
|
|
1089
|
+
})
|
|
1090
|
+
.flat();
|
|
1091
|
+
return [eventName, Object.fromEntries(results)];
|
|
1092
|
+
})).then((results) => ({
|
|
1093
|
+
...source,
|
|
1094
|
+
results: Object.fromEntries(results),
|
|
1095
|
+
}));
|
|
1198
1096
|
}
|
|
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
|
-
|
|
1097
|
+
return source;
|
|
1098
|
+
})));
|
|
1099
|
+
return sources.then((sources) => {
|
|
1100
|
+
report.tree('sources', sources.map((source) => ({
|
|
1101
|
+
name: `${source.name} (${source.path})`,
|
|
1102
|
+
children: Object.entries(source.results).map(([eventName, results]) => ({
|
|
1103
|
+
name: eventName,
|
|
1104
|
+
children: Object.keys(results).length > 0
|
|
1105
|
+
? Object.entries(results).map(([matchFile, result]) => ({
|
|
1106
|
+
name: `used in ${matchFile}: ${result.length}${result.length === 1 ? ' time' : ' times'}`,
|
|
1107
|
+
}))
|
|
1108
|
+
: [
|
|
1109
|
+
{
|
|
1110
|
+
name: `${logSymbols.error} no usage found`,
|
|
1111
|
+
},
|
|
1112
|
+
],
|
|
1113
|
+
})),
|
|
1114
|
+
})));
|
|
1115
|
+
const totalEvents = sources
|
|
1116
|
+
.map(({ results }) => Object.keys(results).length)
|
|
1117
|
+
.reduce(sum, 0);
|
|
1118
|
+
const missingEvents = sources
|
|
1119
|
+
.map(({ results }) => Object.values(results).filter((missing) => Object.keys(missing).length === 0).length)
|
|
1120
|
+
.reduce(sum, 0);
|
|
1121
|
+
if (missingEvents === 0) {
|
|
1122
|
+
if (totalEvents === 0) {
|
|
1123
|
+
report.error('no events found in the avo file - please run avo pull');
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
report.info(`${totalEvents} events seen in code`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
report.info(`${totalEvents - missingEvents} of ${totalEvents} events seen in code`);
|
|
1131
|
+
}
|
|
1132
|
+
if (missingEvents > 0) {
|
|
1133
|
+
report.error(`${missingEvents} missing ${missingEvents > 1 ? 'events' : 'event'}`);
|
|
1134
|
+
report.tree('missingEvents', sources.map((source) => ({
|
|
1135
|
+
name: `${source.name} (${source.path})`,
|
|
1136
|
+
children: Object.entries(source.results)
|
|
1137
|
+
.map(([eventName, results]) => Object.keys(results).length === 0
|
|
1138
|
+
? [
|
|
1139
|
+
{
|
|
1140
|
+
name: `${red(eventName)}: no usage found`,
|
|
1141
|
+
},
|
|
1142
|
+
]
|
|
1143
|
+
: [])
|
|
1144
|
+
.flat(),
|
|
1145
|
+
})));
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
})
|
|
1150
|
+
.catch((error) => {
|
|
1151
|
+
if (error.code === 'ENOENT') {
|
|
1152
|
+
report.error("Avo file not found. Run 'avo pull' to pull latest Avo files.");
|
|
1255
1153
|
}
|
|
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);
|
|
1154
|
+
else {
|
|
1155
|
+
throw error;
|
|
1279
1156
|
}
|
|
1280
|
-
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
/// //////////////////////////////////////////////////////////////////////
|
|
1160
|
+
// AUTH
|
|
1161
|
+
function _getLoginUrl(callbackUrl) {
|
|
1162
|
+
return `${api.authOrigin}/auth/cli?state=${encodeURIComponent(nonce)}&redirect_uri=${encodeURIComponent(callbackUrl)}`;
|
|
1163
|
+
}
|
|
1164
|
+
function _getCallbackUrl(port) {
|
|
1165
|
+
if (port === undefined) {
|
|
1166
|
+
return 'urn:ietf:wg:oauth:2.0:oob';
|
|
1167
|
+
}
|
|
1168
|
+
return `http://localhost:${port}`;
|
|
1169
|
+
}
|
|
1170
|
+
function _getTokensFromAuthorizationCode(code, callbackUrl) {
|
|
1171
|
+
return api
|
|
1172
|
+
.request('POST', '/auth/token', {
|
|
1173
|
+
origin: api.apiOrigin,
|
|
1174
|
+
json: {
|
|
1175
|
+
token: code,
|
|
1176
|
+
redirect_uri: callbackUrl,
|
|
1177
|
+
},
|
|
1281
1178
|
})
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1179
|
+
.then((data) => {
|
|
1180
|
+
if (!data.idToken && !data.refreshToken) {
|
|
1181
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
1182
|
+
}
|
|
1183
|
+
lastAccessToken = {
|
|
1184
|
+
expiresAt: Date.now() + data.expiresIn * 1000,
|
|
1185
|
+
...data,
|
|
1186
|
+
};
|
|
1187
|
+
return lastAccessToken;
|
|
1188
|
+
}, () => {
|
|
1189
|
+
throw INVALID_CREDENTIAL_ERROR;
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
function _respondWithRedirect(req, res, Location) {
|
|
1193
|
+
return new Promise((resolve) => {
|
|
1194
|
+
res.writeHead(302, { Location });
|
|
1195
|
+
res.end();
|
|
1196
|
+
req.socket.destroy();
|
|
1197
|
+
resolve();
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
function _loginWithoutLocalhost() {
|
|
1201
|
+
const callbackUrl = _getCallbackUrl();
|
|
1202
|
+
const authUrl = _getLoginUrl(callbackUrl);
|
|
1203
|
+
report.info(`Visit this URL on any device to login: ${new URL(authUrl)}`);
|
|
1204
|
+
return open(authUrl);
|
|
1205
|
+
}
|
|
1206
|
+
function _loginWithLocalhost(port) {
|
|
1207
|
+
return new Promise((resolve, reject) => {
|
|
1208
|
+
const callbackUrl = _getCallbackUrl(port);
|
|
1209
|
+
const authUrl = _getLoginUrl(callbackUrl);
|
|
1210
|
+
let server = http.createServer((req, res) => {
|
|
1211
|
+
let tokens;
|
|
1212
|
+
const query = url.parse(req.url, true).query ?? {};
|
|
1213
|
+
if (query.state === nonce && isString(query.code)) {
|
|
1214
|
+
return _getTokensFromAuthorizationCode(query.code, callbackUrl)
|
|
1215
|
+
.then((result) => {
|
|
1216
|
+
tokens = result;
|
|
1217
|
+
return _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/success`);
|
|
1218
|
+
})
|
|
1219
|
+
.then(() => {
|
|
1220
|
+
cancelWait();
|
|
1221
|
+
server.close();
|
|
1222
|
+
return resolve({
|
|
1223
|
+
user: jwt.decode(tokens.idToken),
|
|
1224
|
+
tokens,
|
|
1225
|
+
});
|
|
1226
|
+
})
|
|
1227
|
+
.catch(() => _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/error`));
|
|
1228
|
+
}
|
|
1229
|
+
return _respondWithRedirect(req, res, `${api.authOrigin}/auth/cli/error`);
|
|
1230
|
+
});
|
|
1231
|
+
server = httpShutdown(server);
|
|
1232
|
+
server.listen(port, () => {
|
|
1233
|
+
report.info(`Visit this URL on any device to login: ${link(authUrl)}`);
|
|
1234
|
+
wait('Waiting for authentication...');
|
|
1235
|
+
open(authUrl);
|
|
1236
|
+
});
|
|
1237
|
+
server.on('error', () => {
|
|
1238
|
+
_loginWithoutLocalhost().then(resolve, reject);
|
|
1239
|
+
});
|
|
1290
1240
|
});
|
|
1291
1241
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1242
|
+
function login() {
|
|
1243
|
+
return _getPort().then(_loginWithLocalhost, _loginWithoutLocalhost);
|
|
1244
|
+
}
|
|
1245
|
+
function logout(refreshToken) {
|
|
1246
|
+
if (lastAccessToken.refreshToken === refreshToken) {
|
|
1247
|
+
lastAccessToken = {};
|
|
1248
|
+
}
|
|
1249
|
+
const tokens = conf.get('tokens');
|
|
1250
|
+
const currentToken = tokens.refreshToken;
|
|
1251
|
+
if (refreshToken === currentToken) {
|
|
1252
|
+
conf.delete('user');
|
|
1253
|
+
conf.delete('tokens');
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
yargs(hideBin(process.argv)) // eslint-disable-line no-unused-expressions
|
|
1257
|
+
.usage('$0 command')
|
|
1258
|
+
.scriptName('avo')
|
|
1259
|
+
.version(pkg.version)
|
|
1260
|
+
.option('v', {
|
|
1298
1261
|
alias: 'verbose',
|
|
1299
1262
|
default: false,
|
|
1300
1263
|
describe: 'make output more verbose',
|
|
1301
|
-
type: 'boolean'
|
|
1302
|
-
|
|
1303
|
-
|
|
1264
|
+
type: 'boolean',
|
|
1265
|
+
})
|
|
1266
|
+
.option('f', {
|
|
1304
1267
|
alias: 'force',
|
|
1305
1268
|
describe: 'Proceed with merge when incoming branch is open',
|
|
1306
1269
|
default: false,
|
|
1307
|
-
type: 'boolean'
|
|
1308
|
-
|
|
1309
|
-
|
|
1270
|
+
type: 'boolean',
|
|
1271
|
+
})
|
|
1272
|
+
.command({
|
|
1310
1273
|
command: 'track-install',
|
|
1311
1274
|
desc: false,
|
|
1312
1275
|
handler: () => {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1276
|
+
Avo.cliInstalled({
|
|
1277
|
+
userId_: installIdOrUserId(),
|
|
1278
|
+
cliInvokedByCi: invokedByCi(),
|
|
1279
|
+
});
|
|
1280
|
+
},
|
|
1281
|
+
})
|
|
1282
|
+
.command({
|
|
1320
1283
|
command: 'init',
|
|
1321
1284
|
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
|
-
)} (${file('avo.json')} exists)`
|
|
1339
|
-
);
|
|
1340
|
-
} else {
|
|
1285
|
+
handler: (argv) => {
|
|
1286
|
+
loadAvoJsonOrInit({ argv, skipPullMaster: false, skipInit: true })
|
|
1287
|
+
.then((json) => {
|
|
1288
|
+
if (json) {
|
|
1289
|
+
Avo.cliInvoked({
|
|
1290
|
+
schemaId: json.schema.id,
|
|
1291
|
+
schemaName: json.schema.name,
|
|
1292
|
+
branchId: json.branch.id,
|
|
1293
|
+
branchName: json.branch.name,
|
|
1294
|
+
userId_: installIdOrUserId(),
|
|
1295
|
+
cliAction: Avo.CliAction.INIT,
|
|
1296
|
+
cliInvokedByCi: invokedByCi(),
|
|
1297
|
+
});
|
|
1298
|
+
report.info(`Avo is already initialized for workspace ${cyan(json.schema.name)} (${file('avo.json')} exists)`);
|
|
1299
|
+
return Promise.resolve();
|
|
1300
|
+
}
|
|
1341
1301
|
Avo.cliInvoked({
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1302
|
+
schemaId: 'N/A',
|
|
1303
|
+
schemaName: 'N/A',
|
|
1304
|
+
branchId: 'N/A',
|
|
1305
|
+
branchName: 'N/A',
|
|
1306
|
+
userId_: installIdOrUserId(),
|
|
1307
|
+
cliAction: Avo.CliAction.INIT,
|
|
1308
|
+
cliInvokedByCi: invokedByCi(),
|
|
1349
1309
|
});
|
|
1350
|
-
return requireAuth(argv, () =>
|
|
1351
|
-
return init()
|
|
1310
|
+
return requireAuth(argv, () => init()
|
|
1352
1311
|
.then(writeAvoJson)
|
|
1353
1312
|
.then(() => {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
);
|
|
1357
|
-
});
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1313
|
+
report.info("Run 'avo pull' to pull analytics wrappers from Avo");
|
|
1314
|
+
}));
|
|
1360
1315
|
})
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1316
|
+
.catch(() => {
|
|
1317
|
+
Avo.cliInvoked({
|
|
1318
|
+
schemaId: 'N/A',
|
|
1319
|
+
schemaName: 'N/A',
|
|
1320
|
+
branchId: 'N/A',
|
|
1321
|
+
branchName: 'N/A',
|
|
1322
|
+
userId_: installIdOrUserId(),
|
|
1323
|
+
cliAction: Avo.CliAction.INIT,
|
|
1324
|
+
cliInvokedByCi: invokedByCi(),
|
|
1325
|
+
});
|
|
1371
1326
|
});
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1327
|
+
},
|
|
1328
|
+
})
|
|
1329
|
+
.command({
|
|
1375
1330
|
command: 'pull [source]',
|
|
1376
1331
|
desc: 'Pull analytics wrappers from Avo workspace',
|
|
1377
|
-
builder: yargs => {
|
|
1378
|
-
return yargs.option('branch', {
|
|
1332
|
+
builder: (yargs) => yargs.option('branch', {
|
|
1379
1333
|
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
|
-
return pull(source, json);
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1334
|
+
type: 'string',
|
|
1335
|
+
}),
|
|
1336
|
+
handler: (argv) => {
|
|
1337
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1338
|
+
.then((json) => {
|
|
1339
|
+
Avo.cliInvoked({
|
|
1340
|
+
schemaId: json.schema.id,
|
|
1341
|
+
schemaName: json.schema.name,
|
|
1342
|
+
branchId: json.branch.id,
|
|
1343
|
+
branchName: json.branch.name,
|
|
1344
|
+
userId_: installIdOrUserId(),
|
|
1345
|
+
cliAction: Avo.CliAction.PULL,
|
|
1346
|
+
cliInvokedByCi: invokedByCi(),
|
|
1347
|
+
});
|
|
1348
|
+
requireAuth(argv, () => {
|
|
1349
|
+
if (argv.branch && json.branch.name !== argv.branch) {
|
|
1350
|
+
return checkout(argv.branch, json)
|
|
1351
|
+
.then((data) => getSource(argv, data))
|
|
1352
|
+
.then(([source, data]) => pull(source, data));
|
|
1353
|
+
}
|
|
1354
|
+
report.info(`Pulling from branch '${json.branch.name}'`);
|
|
1355
|
+
return getSource(argv, json).then(([source, data]) => pull(source, data));
|
|
1356
|
+
});
|
|
1407
1357
|
})
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1358
|
+
.catch((error) => {
|
|
1359
|
+
Avo.cliInvoked({
|
|
1360
|
+
schemaId: 'N/A',
|
|
1361
|
+
schemaName: 'N/A',
|
|
1362
|
+
branchId: 'N/A',
|
|
1363
|
+
branchName: 'N/A',
|
|
1364
|
+
userId_: installIdOrUserId(),
|
|
1365
|
+
cliAction: Avo.CliAction.PULL,
|
|
1366
|
+
cliInvokedByCi: invokedByCi(),
|
|
1367
|
+
});
|
|
1368
|
+
throw error;
|
|
1419
1369
|
});
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1370
|
+
},
|
|
1371
|
+
})
|
|
1372
|
+
.command({
|
|
1423
1373
|
command: 'checkout [branch]',
|
|
1424
1374
|
aliases: ['branch'],
|
|
1425
1375
|
desc: 'Switch branches',
|
|
1426
|
-
handler: argv => {
|
|
1427
|
-
|
|
1428
|
-
.
|
|
1429
|
-
Avo.cliInvoked({
|
|
1376
|
+
handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1377
|
+
.then((json) => {
|
|
1378
|
+
Avo.cliInvoked({
|
|
1430
1379
|
schemaId: json.schema.id,
|
|
1431
1380
|
schemaName: json.schema.name,
|
|
1432
1381
|
branchId: json.branch.id,
|
|
1433
1382
|
branchName: json.branch.name,
|
|
1434
1383
|
userId_: installIdOrUserId(),
|
|
1435
1384
|
cliAction: Avo.CliAction.CHECKOUT,
|
|
1436
|
-
cliInvokedByCi: invokedByCi()
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
.catch(error => {
|
|
1444
|
-
Avo.cliInvoked({
|
|
1385
|
+
cliInvokedByCi: invokedByCi(),
|
|
1386
|
+
});
|
|
1387
|
+
report.info(`Currently on branch '${json.branch.name}'`);
|
|
1388
|
+
requireAuth(argv, () => checkout(argv.branch, json).then(writeAvoJson));
|
|
1389
|
+
})
|
|
1390
|
+
.catch((error) => {
|
|
1391
|
+
Avo.cliInvoked({
|
|
1445
1392
|
schemaId: 'N/A',
|
|
1446
1393
|
schemaName: 'N/A',
|
|
1447
1394
|
branchId: 'N/A',
|
|
1448
1395
|
branchName: 'N/A',
|
|
1449
1396
|
userId_: installIdOrUserId(),
|
|
1450
1397
|
cliAction: Avo.CliAction.CHECKOUT,
|
|
1451
|
-
cliInvokedByCi: invokedByCi()
|
|
1452
|
-
});
|
|
1453
|
-
throw error;
|
|
1398
|
+
cliInvokedByCi: invokedByCi(),
|
|
1454
1399
|
});
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1400
|
+
throw error;
|
|
1401
|
+
}),
|
|
1402
|
+
})
|
|
1403
|
+
.command({
|
|
1458
1404
|
command: 'source <command>',
|
|
1459
1405
|
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
|
-
.catch(error => {
|
|
1499
|
-
Avo.cliInvoked({
|
|
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()
|
|
1406
|
+
builder: (yargs) => {
|
|
1407
|
+
yargs
|
|
1408
|
+
.command({
|
|
1409
|
+
command: '$0',
|
|
1410
|
+
desc: 'List sources in this project',
|
|
1411
|
+
handler: (argv) => {
|
|
1412
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1413
|
+
.then((json) => {
|
|
1414
|
+
Avo.cliInvoked({
|
|
1415
|
+
schemaId: json.schema.id,
|
|
1416
|
+
schemaName: json.schema.name,
|
|
1417
|
+
branchId: json.branch.id,
|
|
1418
|
+
branchName: json.branch.name,
|
|
1419
|
+
userId_: installIdOrUserId(),
|
|
1420
|
+
cliAction: Avo.CliAction.SOURCE,
|
|
1421
|
+
cliInvokedByCi: invokedByCi(),
|
|
1422
|
+
});
|
|
1423
|
+
if (!json.sources || !json.sources.length) {
|
|
1424
|
+
report.info(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
report.info('Sources in this project:');
|
|
1428
|
+
report.tree('sources', json.sources.map((source) => ({
|
|
1429
|
+
name: source.name,
|
|
1430
|
+
children: [{ name: source.path }],
|
|
1431
|
+
})));
|
|
1432
|
+
})
|
|
1433
|
+
.catch((error) => {
|
|
1434
|
+
Avo.cliInvoked({
|
|
1435
|
+
schemaId: 'N/A',
|
|
1436
|
+
schemaName: 'N/A',
|
|
1437
|
+
branchId: 'N/A',
|
|
1438
|
+
branchName: 'N/A',
|
|
1439
|
+
userId_: installIdOrUserId(),
|
|
1440
|
+
cliAction: Avo.CliAction.SOURCE,
|
|
1441
|
+
cliInvokedByCi: invokedByCi(),
|
|
1442
|
+
});
|
|
1443
|
+
throw error;
|
|
1507
1444
|
});
|
|
1508
|
-
|
|
1509
|
-
});
|
|
1510
|
-
}
|
|
1445
|
+
},
|
|
1511
1446
|
})
|
|
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
|
-
|
|
1447
|
+
.command({
|
|
1448
|
+
command: 'add [source]',
|
|
1449
|
+
desc: 'Add a source to this project',
|
|
1450
|
+
handler: (argv) => {
|
|
1451
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1452
|
+
.then((json) => {
|
|
1453
|
+
Avo.cliInvoked({
|
|
1454
|
+
schemaId: json.schema.id,
|
|
1455
|
+
schemaName: json.schema.name,
|
|
1456
|
+
branchId: json.branch.id,
|
|
1457
|
+
branchName: json.branch.name,
|
|
1458
|
+
userId_: installIdOrUserId(),
|
|
1459
|
+
cliAction: Avo.CliAction.SOURCE_ADD,
|
|
1460
|
+
cliInvokedByCi: invokedByCi(),
|
|
1461
|
+
});
|
|
1462
|
+
requireAuth(argv, () => {
|
|
1463
|
+
selectSource(argv.source, json).then(writeAvoJson);
|
|
1464
|
+
});
|
|
1465
|
+
})
|
|
1466
|
+
.catch((error) => {
|
|
1467
|
+
Avo.cliInvoked({
|
|
1468
|
+
schemaId: 'N/A',
|
|
1469
|
+
schemaName: 'N/A',
|
|
1470
|
+
branchId: 'N/A',
|
|
1471
|
+
branchName: 'N/A',
|
|
1472
|
+
userId_: installIdOrUserId(),
|
|
1473
|
+
cliAction: Avo.CliAction.SOURCE_ADD,
|
|
1474
|
+
cliInvokedByCi: invokedByCi(),
|
|
1475
|
+
});
|
|
1476
|
+
throw error;
|
|
1541
1477
|
});
|
|
1542
|
-
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1478
|
+
},
|
|
1545
1479
|
})
|
|
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
|
-
);
|
|
1480
|
+
.command({
|
|
1481
|
+
command: 'remove [source]',
|
|
1482
|
+
aliases: ['rm'],
|
|
1483
|
+
desc: 'Remove a source from this project',
|
|
1484
|
+
handler: (argv) => {
|
|
1485
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1486
|
+
.then((json) => {
|
|
1487
|
+
Avo.cliInvoked({
|
|
1488
|
+
schemaId: json.schema.id,
|
|
1489
|
+
schemaName: json.schema.name,
|
|
1490
|
+
branchId: json.branch.id,
|
|
1491
|
+
branchName: json.branch.name,
|
|
1492
|
+
userId_: installIdOrUserId(),
|
|
1493
|
+
cliAction: Avo.CliAction.SOURCE_REMOVE,
|
|
1494
|
+
cliInvokedByCi: invokedByCi(),
|
|
1495
|
+
});
|
|
1496
|
+
if (!json.sources || !json.sources.length) {
|
|
1497
|
+
report.warn(`No sources defined in ${file('avo.json')}. Run ${cmd('avo source add')} to add sources`);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const getSourceToRemove = (argv, json) => {
|
|
1501
|
+
if (argv.source) {
|
|
1502
|
+
return Promise.resolve(json.sources.find((source) => matchesSource(source, argv.source)));
|
|
1503
|
+
}
|
|
1504
|
+
const choices = json.sources.map((source) => ({
|
|
1505
|
+
value: source,
|
|
1506
|
+
name: source.name,
|
|
1507
|
+
}));
|
|
1508
|
+
return inquirer
|
|
1509
|
+
.prompt({
|
|
1510
|
+
type: 'list',
|
|
1511
|
+
name: 'source',
|
|
1512
|
+
message: 'Select a source to remove',
|
|
1513
|
+
choices,
|
|
1514
|
+
pageSize: 15,
|
|
1515
|
+
})
|
|
1516
|
+
.then((answer) => answer.source);
|
|
1517
|
+
};
|
|
1518
|
+
getSourceToRemove(argv, json).then((targetSource) => {
|
|
1519
|
+
if (!targetSource) {
|
|
1520
|
+
report.error(`Source ${argv.source} not found in project.`);
|
|
1521
|
+
return Promise.resolve();
|
|
1522
|
+
}
|
|
1523
|
+
return inquirer
|
|
1524
|
+
.prompt([
|
|
1525
|
+
{
|
|
1526
|
+
type: 'confirm',
|
|
1527
|
+
name: 'remove',
|
|
1528
|
+
default: true,
|
|
1529
|
+
message: `Are you sure you want to remove source ${targetSource.name} from project`,
|
|
1530
|
+
},
|
|
1531
|
+
])
|
|
1532
|
+
.then((answer) => {
|
|
1533
|
+
if (answer.remove) {
|
|
1534
|
+
const sources = (json.sources ?? []).filter((source) => source.id !== targetSource.id);
|
|
1535
|
+
const newJson = { ...json, sources };
|
|
1536
|
+
return writeAvoJson(newJson).then(() => {
|
|
1537
|
+
// XXX ask to remove file as well?
|
|
1538
|
+
report.info(`Removed source ${targetSource.name} from project`);
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
report.info(`Did not remove source ${targetSource.name} from project`);
|
|
1542
|
+
return Promise.resolve();
|
|
1624
1543
|
});
|
|
1625
|
-
} else {
|
|
1626
|
-
report.info(
|
|
1627
|
-
`Did not remove source ${targetSource.name} from project`
|
|
1628
|
-
);
|
|
1629
|
-
}
|
|
1630
1544
|
});
|
|
1545
|
+
})
|
|
1546
|
+
.catch((error) => {
|
|
1547
|
+
Avo.cliInvoked({
|
|
1548
|
+
schemaId: 'N/A',
|
|
1549
|
+
schemaName: 'N/A',
|
|
1550
|
+
branchId: 'N/A',
|
|
1551
|
+
branchName: 'N/A',
|
|
1552
|
+
userId_: installIdOrUserId(),
|
|
1553
|
+
cliAction: Avo.CliAction.SOURCE_REMOVE,
|
|
1554
|
+
cliInvokedByCi: invokedByCi(),
|
|
1555
|
+
});
|
|
1556
|
+
throw error;
|
|
1631
1557
|
});
|
|
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
|
-
}
|
|
1558
|
+
},
|
|
1646
1559
|
});
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1560
|
+
},
|
|
1561
|
+
})
|
|
1562
|
+
.command({
|
|
1650
1563
|
command: 'status [source]',
|
|
1651
1564
|
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);
|
|
1565
|
+
handler: (argv) => {
|
|
1566
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1567
|
+
.then((json) => {
|
|
1568
|
+
Avo.cliInvoked({
|
|
1569
|
+
schemaId: json.schema.id,
|
|
1570
|
+
schemaName: json.schema.name,
|
|
1571
|
+
branchId: json.branch.id,
|
|
1572
|
+
branchName: json.branch.name,
|
|
1573
|
+
userId_: installIdOrUserId(),
|
|
1574
|
+
cliAction: Avo.CliAction.STATUS,
|
|
1575
|
+
cliInvokedByCi: invokedByCi(),
|
|
1576
|
+
});
|
|
1577
|
+
report.info(`Currently on branch '${json.branch.name}'`);
|
|
1578
|
+
return getSource(argv, json);
|
|
1669
1579
|
})
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1580
|
+
.then(([source, json]) => status(source, json, argv))
|
|
1581
|
+
.catch((error) => {
|
|
1582
|
+
Avo.cliInvoked({
|
|
1583
|
+
schemaId: 'N/A',
|
|
1584
|
+
schemaName: 'N/A',
|
|
1585
|
+
branchId: 'N/A',
|
|
1586
|
+
branchName: 'N/A',
|
|
1587
|
+
userId_: installIdOrUserId(),
|
|
1588
|
+
cliAction: Avo.CliAction.STATUS,
|
|
1589
|
+
cliInvokedByCi: invokedByCi(),
|
|
1590
|
+
});
|
|
1591
|
+
throw error;
|
|
1681
1592
|
});
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
.command({
|
|
1593
|
+
},
|
|
1594
|
+
})
|
|
1595
|
+
.command({
|
|
1686
1596
|
command: 'merge main',
|
|
1687
1597
|
aliases: ['merge master'],
|
|
1688
1598
|
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
|
-
});
|
|
1599
|
+
handler: (argv) => {
|
|
1600
|
+
loadAvoJsonOrInit({ argv, skipPullMaster: true, skipInit: false })
|
|
1601
|
+
.then((json) => {
|
|
1602
|
+
Avo.cliInvoked({
|
|
1603
|
+
schemaId: json.schema.id,
|
|
1604
|
+
schemaName: json.schema.name,
|
|
1605
|
+
branchId: json.branch.id,
|
|
1606
|
+
branchName: json.branch.name,
|
|
1607
|
+
userId_: installIdOrUserId(),
|
|
1608
|
+
cliAction: Avo.CliAction.MERGE,
|
|
1609
|
+
cliInvokedByCi: invokedByCi(),
|
|
1610
|
+
force: json.force,
|
|
1611
|
+
});
|
|
1612
|
+
return requireAuth(argv, () => pullMaster(json).then(writeAvoJson));
|
|
1706
1613
|
})
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1614
|
+
.catch((error) => {
|
|
1615
|
+
Avo.cliInvoked({
|
|
1616
|
+
schemaId: 'N/A',
|
|
1617
|
+
schemaName: 'N/A',
|
|
1618
|
+
branchId: 'N/A',
|
|
1619
|
+
branchName: 'N/A',
|
|
1620
|
+
userId_: installIdOrUserId(),
|
|
1621
|
+
cliAction: Avo.CliAction.MERGE,
|
|
1622
|
+
cliInvokedByCi: invokedByCi(),
|
|
1623
|
+
});
|
|
1624
|
+
throw error;
|
|
1718
1625
|
});
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1626
|
+
},
|
|
1627
|
+
})
|
|
1628
|
+
.command({
|
|
1722
1629
|
command: 'conflict',
|
|
1723
1630
|
aliases: ['resolve', 'conflicts'],
|
|
1724
1631
|
desc: 'Resolve git conflicts in Avo files',
|
|
1725
|
-
handler: argv =>
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
}).then(json => {
|
|
1632
|
+
handler: (argv) => pify(fs.readFile)('avo.json', 'utf8')
|
|
1633
|
+
.then((avoFile) => {
|
|
1634
|
+
if (hasMergeConflicts(avoFile)) {
|
|
1635
|
+
return requireAuth(argv, () => resolveAvoJsonConflicts(avoFile, {
|
|
1636
|
+
argv,
|
|
1637
|
+
skipPullMaster: false,
|
|
1638
|
+
}).then((json) => {
|
|
1733
1639
|
Avo.cliInvoked({
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1640
|
+
schemaId: json.schema.id,
|
|
1641
|
+
schemaName: json.schema.name,
|
|
1642
|
+
branchId: json.branch.id,
|
|
1643
|
+
branchName: json.branch.name,
|
|
1644
|
+
userId_: installIdOrUserId(),
|
|
1645
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1646
|
+
cliInvokedByCi: invokedByCi(),
|
|
1741
1647
|
});
|
|
1742
1648
|
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({
|
|
1649
|
+
}));
|
|
1650
|
+
}
|
|
1651
|
+
report.info("No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files.");
|
|
1652
|
+
const json = JSON.parse(avoFile);
|
|
1653
|
+
Avo.cliInvoked({
|
|
1783
1654
|
schemaId: json.schema.id,
|
|
1784
1655
|
schemaName: json.schema.name,
|
|
1785
1656
|
branchId: json.branch.id,
|
|
1786
1657
|
branchName: json.branch.name,
|
|
1787
1658
|
userId_: installIdOrUserId(),
|
|
1788
|
-
cliAction: Avo.CliAction.
|
|
1789
|
-
cliInvokedByCi: invokedByCi()
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
`Opening ${cyan(schema.name)} workspace in Avo: ${link(url)}`
|
|
1796
|
-
);
|
|
1797
|
-
opn(url, {wait: false});
|
|
1798
|
-
})
|
|
1799
|
-
.catch(error => {
|
|
1800
|
-
Avo.cliInvoked({
|
|
1659
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1660
|
+
cliInvokedByCi: invokedByCi(),
|
|
1661
|
+
});
|
|
1662
|
+
return Promise.resolve(json);
|
|
1663
|
+
})
|
|
1664
|
+
.catch((error) => {
|
|
1665
|
+
Avo.cliInvoked({
|
|
1801
1666
|
schemaId: 'N/A',
|
|
1802
1667
|
schemaName: 'N/A',
|
|
1803
1668
|
branchId: 'N/A',
|
|
1804
1669
|
branchName: 'N/A',
|
|
1805
1670
|
userId_: installIdOrUserId(),
|
|
1806
|
-
cliAction: Avo.CliAction.
|
|
1807
|
-
cliInvokedByCi: invokedByCi()
|
|
1808
|
-
});
|
|
1809
|
-
throw error;
|
|
1671
|
+
cliAction: Avo.CliAction.CONFLICT,
|
|
1672
|
+
cliInvokedByCi: invokedByCi(),
|
|
1810
1673
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1674
|
+
throw error;
|
|
1675
|
+
}),
|
|
1676
|
+
})
|
|
1677
|
+
.command({
|
|
1678
|
+
command: 'edit',
|
|
1679
|
+
desc: 'Open the Avo workspace in your browser',
|
|
1680
|
+
handler: (argv) => {
|
|
1681
|
+
loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
|
|
1682
|
+
.then((json) => {
|
|
1683
|
+
Avo.cliInvoked({
|
|
1684
|
+
schemaId: json.schema.id,
|
|
1685
|
+
schemaName: json.schema.name,
|
|
1686
|
+
branchId: json.branch.id,
|
|
1687
|
+
branchName: json.branch.name,
|
|
1688
|
+
userId_: installIdOrUserId(),
|
|
1689
|
+
cliAction: Avo.CliAction.EDIT,
|
|
1690
|
+
cliInvokedByCi: invokedByCi(),
|
|
1691
|
+
});
|
|
1692
|
+
const { schema } = json;
|
|
1693
|
+
const schemaUrl = `https://www.avo.app/schemas/${schema.id}`;
|
|
1694
|
+
report.info(`Opening ${cyan(schema.name)} workspace in Avo: ${link(schemaUrl)}`);
|
|
1695
|
+
open(schemaUrl);
|
|
1696
|
+
})
|
|
1697
|
+
.catch((error) => {
|
|
1698
|
+
Avo.cliInvoked({
|
|
1699
|
+
schemaId: 'N/A',
|
|
1700
|
+
schemaName: 'N/A',
|
|
1701
|
+
branchId: 'N/A',
|
|
1702
|
+
branchName: 'N/A',
|
|
1703
|
+
userId_: installIdOrUserId(),
|
|
1704
|
+
cliAction: Avo.CliAction.EDIT,
|
|
1705
|
+
cliInvokedByCi: invokedByCi(),
|
|
1706
|
+
});
|
|
1707
|
+
throw error;
|
|
1708
|
+
});
|
|
1709
|
+
},
|
|
1710
|
+
})
|
|
1711
|
+
.command({
|
|
1814
1712
|
command: 'login',
|
|
1815
1713
|
desc: 'Log into the Avo platform',
|
|
1816
1714
|
handler: () => {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1715
|
+
const command = () => {
|
|
1716
|
+
const user = conf.get('user');
|
|
1717
|
+
if (user) {
|
|
1718
|
+
report.info(`Already logged in as ${email(user.email)}`);
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
login()
|
|
1722
|
+
.then((result) => {
|
|
1723
|
+
conf.set('user', result.user);
|
|
1724
|
+
conf.set('tokens', result.tokens);
|
|
1725
|
+
Avo.signedIn({
|
|
1726
|
+
userId_: result.user.user_id,
|
|
1727
|
+
email: result.user.email,
|
|
1728
|
+
cliInvokedByCi: invokedByCi(),
|
|
1729
|
+
});
|
|
1730
|
+
report.success(`Logged in as ${email(result.user.email)}`);
|
|
1731
|
+
})
|
|
1732
|
+
.catch(() => {
|
|
1733
|
+
Avo.signInFailed({
|
|
1734
|
+
userId_: conf.get('avo_install_id'),
|
|
1735
|
+
emailInput: '',
|
|
1736
|
+
signInError: Avo.SignInError.UNKNOWN,
|
|
1737
|
+
cliInvokedByCi: invokedByCi(),
|
|
1738
|
+
});
|
|
1832
1739
|
});
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1740
|
+
};
|
|
1741
|
+
loadAvoJson()
|
|
1742
|
+
.then((json) => {
|
|
1743
|
+
Avo.cliInvoked({
|
|
1744
|
+
schemaId: json.schema.id,
|
|
1745
|
+
schemaName: json.schema.name,
|
|
1746
|
+
branchId: json.branch.id,
|
|
1747
|
+
branchName: json.branch.name,
|
|
1748
|
+
userId_: installIdOrUserId(),
|
|
1749
|
+
cliAction: Avo.CliAction.LOGIN,
|
|
1750
|
+
cliInvokedByCi: invokedByCi(),
|
|
1842
1751
|
});
|
|
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();
|
|
1752
|
+
command();
|
|
1858
1753
|
})
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1754
|
+
.catch(() => {
|
|
1755
|
+
Avo.cliInvoked({
|
|
1756
|
+
schemaId: 'N/A',
|
|
1757
|
+
schemaName: 'N/A',
|
|
1758
|
+
branchId: 'N/A',
|
|
1759
|
+
branchName: 'N/A',
|
|
1760
|
+
userId_: installIdOrUserId(),
|
|
1761
|
+
cliAction: Avo.CliAction.LOGIN,
|
|
1762
|
+
cliInvokedByCi: invokedByCi(),
|
|
1763
|
+
});
|
|
1764
|
+
command();
|
|
1870
1765
|
});
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1766
|
+
},
|
|
1767
|
+
})
|
|
1768
|
+
.command({
|
|
1874
1769
|
command: 'logout',
|
|
1875
1770
|
desc: 'Log out from the Avo platform',
|
|
1876
1771
|
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);
|
|
1772
|
+
const command = () => {
|
|
1773
|
+
const user = conf.get('user');
|
|
1774
|
+
const tokens = conf.get('tokens');
|
|
1775
|
+
const currentToken = tokens.refreshToken;
|
|
1776
|
+
const token = currentToken;
|
|
1777
|
+
api.setRefreshToken(token);
|
|
1778
|
+
if (token) {
|
|
1779
|
+
logout(token);
|
|
1891
1780
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1781
|
+
if (token || user || tokens) {
|
|
1782
|
+
let msg = 'Logged out';
|
|
1783
|
+
if (token === currentToken) {
|
|
1784
|
+
if (user) {
|
|
1785
|
+
msg += ` from ${bold(user.email)}`;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
msg += ` token "${bold(token)}"`;
|
|
1790
|
+
}
|
|
1791
|
+
report.log(msg);
|
|
1792
|
+
}
|
|
1793
|
+
else {
|
|
1794
|
+
report.log("No need to logout, you're not logged in");
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
loadAvoJson()
|
|
1798
|
+
.then((json) => {
|
|
1799
|
+
Avo.cliInvoked({
|
|
1800
|
+
schemaId: json.schema.id,
|
|
1801
|
+
schemaName: json.schema.name,
|
|
1802
|
+
branchId: json.branch.id,
|
|
1803
|
+
branchName: json.branch.name,
|
|
1804
|
+
userId_: installIdOrUserId(),
|
|
1805
|
+
cliAction: Avo.CliAction.LOGOUT,
|
|
1806
|
+
cliInvokedByCi: invokedByCi(),
|
|
1807
|
+
});
|
|
1808
|
+
command();
|
|
1913
1809
|
})
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1810
|
+
.catch(() => {
|
|
1811
|
+
Avo.cliInvoked({
|
|
1812
|
+
schemaId: 'N/A',
|
|
1813
|
+
schemaName: 'N/A',
|
|
1814
|
+
branchId: 'N/A',
|
|
1815
|
+
branchName: 'N/A',
|
|
1816
|
+
userId_: installIdOrUserId(),
|
|
1817
|
+
cliAction: Avo.CliAction.LOGOUT,
|
|
1818
|
+
cliInvokedByCi: invokedByCi(),
|
|
1819
|
+
});
|
|
1820
|
+
command();
|
|
1925
1821
|
});
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1822
|
+
},
|
|
1823
|
+
})
|
|
1824
|
+
.command({
|
|
1929
1825
|
command: 'whoami',
|
|
1930
1826
|
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
|
-
|
|
1827
|
+
handler: (argv) => {
|
|
1828
|
+
const command = () => {
|
|
1829
|
+
requireAuth(argv, () => {
|
|
1830
|
+
if (conf.has('user')) {
|
|
1831
|
+
const user = conf.get('user');
|
|
1832
|
+
report.info(`Logged in as ${email(user.email)}`);
|
|
1833
|
+
}
|
|
1834
|
+
else {
|
|
1835
|
+
report.warn('Not logged in');
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1838
|
+
};
|
|
1839
|
+
loadAvoJson()
|
|
1840
|
+
.then((json) => {
|
|
1841
|
+
Avo.cliInvoked({
|
|
1842
|
+
schemaId: json.schema.id,
|
|
1843
|
+
schemaName: json.schema.name,
|
|
1844
|
+
branchId: json.branch.id,
|
|
1845
|
+
branchName: json.branch.name,
|
|
1846
|
+
userId_: installIdOrUserId(),
|
|
1847
|
+
cliAction: Avo.CliAction.WHOAMI,
|
|
1848
|
+
cliInvokedByCi: invokedByCi(),
|
|
1849
|
+
});
|
|
1850
|
+
command();
|
|
1955
1851
|
})
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1852
|
+
.catch(() => {
|
|
1853
|
+
Avo.cliInvoked({
|
|
1854
|
+
schemaId: 'N/A',
|
|
1855
|
+
schemaName: 'N/A',
|
|
1856
|
+
branchId: 'N/A',
|
|
1857
|
+
branchName: 'N/A',
|
|
1858
|
+
userId_: installIdOrUserId(),
|
|
1859
|
+
cliAction: Avo.CliAction.WHOAMI,
|
|
1860
|
+
cliInvokedByCi: invokedByCi(),
|
|
1861
|
+
});
|
|
1862
|
+
command();
|
|
1967
1863
|
});
|
|
1864
|
+
},
|
|
1865
|
+
})
|
|
1866
|
+
.demandCommand(1, 'must provide a valid command')
|
|
1867
|
+
.recommendCommands()
|
|
1868
|
+
.help().argv;
|
|
1869
|
+
/// ///////////////// ////////
|
|
1870
|
+
// catch unhandled promises
|
|
1871
|
+
process.on('unhandledRejection', (err) => {
|
|
1872
|
+
cancelWait();
|
|
1873
|
+
if (!(err instanceof Error) && !(err instanceof AvoError)) {
|
|
1874
|
+
report.error(new AvoError(`Promise rejected with value: ${util.inspect(err)}`));
|
|
1968
1875
|
}
|
|
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 = {};
|
|
1876
|
+
else {
|
|
1877
|
+
// @ts-ignore
|
|
1878
|
+
report.error(err.message);
|
|
2253
1879
|
}
|
|
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')}`);
|
|
1880
|
+
// @ts-ignore
|
|
1881
|
+
// console.error(err.stack);
|
|
2298
1882
|
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
1883
|
});
|