avo 2.0.1 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -7
  2. package/cli.js +1648 -2085
  3. package/package.json +40 -27
package/cli.js CHANGED
@@ -1,2320 +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 opn from 'opn';
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 request from 'request';
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
- const Minimatch = minimatch.Minimatch;
39
- const {cyan, gray, red, bold, underline} = chalk;
40
-
41
- const customAnalyticsDestination = {
42
- make: function make(production) {
43
- this.production = production;
44
- },
45
-
46
- logEvent: function logEvent(userId, eventName, eventProperties) {
47
- api
48
- .request('POST', '/c/v1/track', {
49
- origin: api.apiOrigin,
50
- data: {
51
- userId: userId,
52
- eventName: eventName,
53
- eventProperties: eventProperties
54
- }
55
- })
56
- .catch(function() {
57
- // don't crash on tracking errors
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
- let inspector = new Inspector.AvoInspector({
63
- apiKey: "3UWtteG9HenZ825cYoYr", env: Inspector.AvoInspectorEnv.Prod, version: "1.0.0", appName: "Avo CLI"
64
- });
65
-
66
- // setup Avo analytics
67
- Avo.initAvo(
68
- {env: 'prod', inspector: inspector},
69
- {client: Avo.Client.CLI, version: pkg.version},
70
- {},
71
- customAnalyticsDestination
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
- conf.set('avo_install_id', uuidv4());
88
+ conf.set('avo_install_id', uuidv4());
83
89
  }
84
-
85
90
  const FIFTEEN_MINUTES_IN_MS = 15 * 60 * 1000;
86
-
87
- // to cancel spinners globally
88
- let _cancel = null;
89
-
90
- var nonce = _.random(1, 2 << 29).toString();
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
- var _getPort = portfinder.getPortPromise;
94
-
95
- function AvoError(message, options) {
96
- options = options || {};
97
-
98
- this.name = 'AvoError';
99
- this.message = message;
100
- this.children = options.children || [];
101
- this.status = options.status || 500;
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
- var INVALID_CREDENTIAL_ERROR = new AvoError(
110
- `Authentication Error: Your credentials are no longer valid. Please run ${cmd(
111
- 'avo logout; avo login'
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
- function _request(options, logOptions) {
126
- logOptions = logOptions || {};
127
-
128
- if (options.qs && !logOptions.skipQueryParams) {
129
- qsLog = JSON.stringify(options.qs);
130
- }
131
-
132
- return new Promise(function(resolve, reject) {
133
- var req = request(options, function(err, response, body) {
134
- if (err) {
135
- return reject(
136
- new AvoError('Server Error. ' + err.message, {
137
- original: err,
138
- exit: 2
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
- var _appendQueryData = function(path, data) {
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
- var reqOptions = {
220
- method: method
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
- if (method === 'GET') {
228
- resource = _appendQueryData(resource, options.data);
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
- reqOptions.url = options.origin + resource;
238
- reqOptions.files = options.files;
239
- reqOptions.resolveOnHTTPError = options.resolveOnHTTPError;
240
- reqOptions.json = options.json;
241
- reqOptions.gzip = options.gzip;
242
- reqOptions.qs = options.qs;
243
- reqOptions.headers = options.headers;
244
- reqOptions.timeout = options.timeout;
245
-
246
- var requestFunction = function() {
247
- return _request(reqOptions, options.logOptions);
248
- };
249
- if (options.auth === true) {
250
- requestFunction = function() {
251
- return api
252
- .addRequestHeaders(reqOptions)
253
- .then(function(reqOptionsWithToken) {
254
- return _request(reqOptionsWithToken, options.logOptions);
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
- return requestFunction().catch(function(err) {
260
- if (
261
- options.retryCodes &&
262
- _.includes(
263
- options.retryCodes,
264
- _.get(err, 'context.response.statusCode')
265
- )
266
- ) {
267
- return new Promise(function(resolve) {
268
- setTimeout(resolve, 1000);
269
- }).then(requestFunction);
270
- }
271
- return Promise.reject(err);
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
- // check if legacy avo.json or un-initialized project
278
- return json.types || !json.schema;
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
- // if avo.json has version, and this binary has lower version number it needs updating
283
- return (
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
- return (
294
- str.includes(MERGE_CONFLICT_START) &&
295
- str.includes(MERGE_CONFLICT_SEP) &&
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
- const files = [[], []];
302
- const lines = str.split(/\r?\n/g);
303
- let skip = false;
304
-
305
- while (lines.length) {
306
- const line = lines.shift();
307
- if (line.startsWith(MERGE_CONFLICT_START)) {
308
- while (lines.length) {
309
- const conflictLine = lines.shift();
310
- if (conflictLine === MERGE_CONFLICT_SEP) {
311
- skip = false;
312
- break;
313
- } else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
314
- skip = true;
315
- continue;
316
- } else {
317
- files[0].push(conflictLine);
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
- while (lines.length) {
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
- if (json.branch.id == 'master') {
360
- return Promise.resolve(BRANCH_UP_TO_DATE);
361
- } else {
377
+ if (json.branch.id === 'master') {
378
+ return Promise.resolve(BRANCH_UP_TO_DATE);
379
+ }
362
380
  return api
363
- .request('POST', '/c/v1/master', {
381
+ .request('POST', '/c/v1/master', {
364
382
  origin: api.apiOrigin,
365
383
  auth: true,
366
- data: {
367
- schemaId: json.schema.id,
368
- branchId: json.branch.id
369
- }
370
- })
371
- .then(res => {
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
- if (json.branch.name == 'main') {
381
- report.info('Your current branch is main');
382
- return Promise.resolve(json);
383
- } else {
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
- .request('POST', '/c/v1/master/pull', {
398
+ .request('POST', '/c/v1/master/pull', {
387
399
  origin: api.apiOrigin,
388
400
  auth: true,
389
- data: {
390
- schemaId: json.schema.id,
391
- branchId: json.branch.id,
392
- force: json.force
393
- }
394
- })
395
- .then(() => {
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
- wait('Check if branch is up to date with main');
405
- return getMasterStatus(json)
406
- .then(branchStatus => {
407
- cancelWait();
408
- if (branchStatus == BRANCH_UP_TO_DATE) {
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
- .then(([branchStatus, answer]) => {
426
- if (branchStatus == BRANCH_UP_TO_DATE) {
427
- report.success('Branch is up to date with main');
428
- return Promise.resolve(json);
429
- } else if (answer.pull) {
430
- return pullMaster(json);
431
- } else {
432
- report.info(`Did not pull main into branch`);
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
- function resolveAvoJsonConflicts(file, {argv, skipPullMaster}) {
439
- report.info('Resolving Avo merge conflicts');
440
- let files = extractConflictingFiles(file);
441
- const head = JSON.parse(files[0]);
442
- const incoming = JSON.parse(files[1]);
443
-
444
- Avo.cliConflictResolveAttempted({
445
- userId_: installIdOrUserId(),
446
- cliInvokedByCi: invokedByCi(),
447
- schemaId: head.schema.id,
448
- schemaName: head.schema.name,
449
- branchId: head.branch.id,
450
- branchName: head.branch.name
451
- });
452
-
453
- if (
454
- head.avo.version != incoming.avo.version ||
455
- head.schema.id != incoming.schema.id
456
- ) {
457
- Avo.cliConflictResolveFailed({
458
- userId_: installIdOrUserId(),
459
- cliInvokedByCi: invokedByCi(),
460
- schemaId: head.schema.id,
461
- schemaName: head.schema.name,
462
- branchId: head.branch.id,
463
- branchName: head.branch.name
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
- throw new Error(
466
- "Could not automatically resolve merge conflicts in avo.json. Resolve merge conflicts in avo.json before running 'avo pull' again."
467
- );
468
- }
469
-
470
- if (
471
- !_.isEqual(head.sources.map(s => s.id), incoming.sources.map(s => s.id))
472
- ) {
473
- Avo.cliConflictResolveFailed({
474
- userId_: installIdOrUserId(),
475
- cliInvokedByCi: invokedByCi(),
476
- schemaId: head.schema.id,
477
- schemaName: head.schema.name,
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
- throw new Error(
482
- "Could not automatically resolve merge conflicts in avo.json. Resolve merge conflicts in sources list in avo.json before running 'avo pull' again."
483
- );
484
- }
485
-
486
- const nextAvoJson = {
487
- avo: head.avo,
488
- schema: head.schema,
489
- branch: head.branch,
490
- sources: head.sources
491
- };
492
- return requireAuth(argv, () => {
493
- return fetchBranches(nextAvoJson).then(branches => {
494
- const isHeadBranchOpen = branches.find(branch => {
495
- return branch.id == nextAvoJson.branch.id;
496
- });
497
-
498
- const isIncomingBranchOpen = branches.find(branch => {
499
- return branch.id == incoming.branch.id;
500
- });
501
-
502
- function switchBranchIfRequired(json) {
503
- if (isHeadBranchOpen) {
504
- return Promise.resolve(json);
505
- } else {
506
- report.info(
507
- `Your current branch '${json.branch.name}' has been closed or merged. Go to another branch:`
508
- );
509
- return checkout(null, json);
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
- return switchBranchIfRequired(nextAvoJson)
514
- .then(json => {
515
- if (
516
- head.branch.id == incoming.branch.id ||
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
- .then(([isDone, json]) => {
525
- if (!isDone && isIncomingBranchOpen && argv.force) {
526
- report.warn(
527
- `Incoming branch, ${
528
- incoming.branch.name
529
- }, has not been merged to Avo main. To review and merge go to: ${link(
530
- `https://www.avo.app/schemas/${nextAvoJson.schema.id}/branches/${incoming.branch.id}/diff`
531
- )}`
532
- );
533
- return Promise.resolve(json);
534
- } else if (!isDone && isIncomingBranchOpen) {
535
- Avo.cliConflictResolveFailed({
536
- userId_: installIdOrUserId(),
537
- cliInvokedByCi: invokedByCi(),
538
- schemaId: head.schema.id,
539
- schemaName: head.schema.name,
540
- branchId: head.branch.id,
541
- branchName: head.branch.name
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
- .then(json => {
555
- if (skipPullMaster) {
556
- return Promise.resolve(json);
557
- } else {
699
+ .then((json) => {
700
+ if (skipPullMaster) {
701
+ return Promise.resolve(json);
702
+ }
558
703
  return promptPullMaster(json);
559
- }
560
704
  })
561
- .then(json => {
562
- Avo.cliConflictResolveSucceeded({
563
- userId_: installIdOrUserId(),
564
- cliInvokedByCi: invokedByCi(),
565
- schemaId: head.schema.id,
566
- schemaName: head.schema.name,
567
- branchId: head.branch.id,
568
- branchName: head.branch.name
569
- });
570
- report.success('Successfully resolved Avo merge conflicts');
571
- return validateAvoJson(json);
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
- return loadJsonFile('avo.json')
579
- .then(validateAvoJson)
580
- .catch(err => {
581
- if (err.code === 'ENOENT') {
582
- throw new AvoError(
583
- `File ${file('avo.json')} does not exist. Run ${cmd('avo init')}`
584
- );
585
- } else {
586
- throw err;
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
- function loadAvoJsonOrInit({argv, skipPullMaster, skipInit}) {
592
- return pify(fs.readFile)('avo.json', 'utf8')
593
- .then(file => {
594
- if (hasMergeConflicts(file)) {
595
- return resolveAvoJsonConflicts(file, {
596
- argv,
597
- skipPullMaster
598
- });
599
- } else {
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
- .then(validateAvoJson)
608
- .catch(error => {
609
- if (error.code === 'ENOENT' && skipInit) {
610
- return;
611
- } else if (error.code === 'ENOENT') {
612
- report.info('Avo not initialized');
613
- return requireAuth(argv, init);
614
- } else {
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
- return writeJsonFile('avo.json', json, {
622
- indent: 2
623
- }).then(() => json);
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
- function codegen(json, result) {
685
- let schema = result.schema;
686
- let targets = result.sources;
687
- let newJson = Object.assign({}, _.cloneDeep(json), {schema: schema});
688
- let warnings = result.warnings;
689
- let errors = result.errors;
690
-
691
- newJson.sources = newJson.sources.map(source => {
692
- let target = _.find(targets, target => target.id === source.id);
693
- if (target) {
694
- return Object.assign({}, source, {
695
- actionId: target.actionId,
696
- name: target.name,
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
- function selectSource(sourceToAdd, json) {
745
- wait('Fetching sources');
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
- if (!sourceToAdd) {
787
- let choices = sources.map(source => {
788
- return {value: source, name: source.name};
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
- prompts.push({
814
- type: 'input',
815
- name: 'filename',
816
- message: 'Select a filename for the library',
817
- default: function(answers) {
818
- return source.filenameHint;
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
- return source.name.toLowerCase() === filter.toLowerCase();
803
+ return source.name.toLowerCase() === filter.toLowerCase();
951
804
  }
952
-
953
- function pull(sourceFilter, json) {
954
- let sources = sourceFilter
955
- ? [_.find(json.sources, source => matchesSource(source, sourceFilter))]
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
- data: {
974
- schemaId: json.schema.id,
975
- branchId: json.branch.id,
976
- sources: _.map(sources, source => {
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
- .then(res => {
984
-
985
- cancelWait();
986
- let result = res.body;
987
- if (result.ok) {
988
- codegen(json, result);
989
- } else {
990
- report.error(
991
- `Branch ${result.branchName} was ${
992
- result.reason
993
- } ${dateFns.distanceInWords(
994
- new Date(result.closedAt),
995
- new Date()
996
- )} ago. Pick another branch.`
997
- );
998
- checkout(null, json).then(json => {
999
- return pull(sourceFilter, json);
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
- function installIdOrUserId() {
1006
- let installId = conf.get('avo_install_id');
1007
- let user = conf.get('user');
1008
- if (user && user.user_id) {
1009
- return user.user_id;
1010
- } else {
1011
- return installId;
1012
- }
1013
- }
1014
-
1015
- function invokedByCi() {
1016
- return process.env.CI !== undefined;
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
- let isGlobal = regex.global;
1021
- let lines = data.split('\n');
1022
- let fileMatches = [];
1023
- let lastIndex = 0;
1024
-
1025
- for (let index = 0; index < lines.length; index++) {
1026
- let lineContents = lines[index];
1027
- let line = lastIndex + index;
1028
- let match;
1029
-
1030
- while (true) {
1031
- match = regex.exec(lineContents);
1032
- if (!match) break;
1033
-
1034
- let start = match.index;
1035
- let end = match.index + match[0].length;
1036
-
1037
- fileMatches.push({
1038
- line,
1039
- start,
1040
- end,
1041
- lineContents
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
- let searchFor = 'AVOEVENTMAP:';
1055
- let lines = data.split('\n').filter(line => line.indexOf(searchFor) > -1);
1056
- if (lines.length === 1) {
1057
- let line = lines[0].substring(
1058
- lines[0].indexOf(searchFor) + searchFor.length
1059
- );
1060
- line = line.substring(line.indexOf('['), line.indexOf(']') + 1);
1061
- let eventMap = JSON.parse(line);
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
- let searchFor = 'AVOMODULEMAP:';
1070
- let lines = data.split('\n').filter(line => line.indexOf(searchFor) > -1);
1071
- if (lines.length === 1) {
1072
- let line = lines[0].substring(
1073
- lines[0].indexOf(searchFor) + searchFor.length
1074
- );
1075
- line = line.substring(line.indexOf('"'), line.lastIndexOf('"') + 1);
1076
- let moduleMap = JSON.parse(line);
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
- if (!json.sources || !json.sources.length) {
1085
- report.info(`No sources configured.`);
1086
- return requireAuth(argv, () => {
1087
- if (argv.source) {
1088
- report.info(`Setting up source "${argv.source}"`);
1089
- }
1090
- return selectSource(argv.source, json).then(json => [argv.source, json]);
1091
- });
1092
- } else if (
1093
- argv.source &&
1094
- !_.find(json.sources, source => matchesSource(source, argv.source))
1095
- ) {
1096
- report.error(`Source ${argv.source} not found`);
1097
- return requireAuth(argv, () =>
1098
- selectSource(argv.source, json).then(json => [argv.source, json])
1099
- );
1100
- } else {
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
- let sources = source
1107
- ? _.filter(json.sources, source => matchesSource(source, source))
1108
- : json.sources;
1109
-
1110
- sources = sources.filter(source => source.analysis !== false);
1111
- let fileCache = walk({
1112
- ignoreFiles: ['.gitignore'],
1113
- follow: false
1114
- }).then(results => {
1115
- results = results.filter(path => !path.startsWith('.git'));
1116
- return Promise.all(
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
- } else {
1122
- return pify(fs.readFile)(path, 'utf8').then(data => {
1123
- return [path, data];
1124
- });
1125
- }
1126
- });
1127
- })
1128
- ).then(cachePairs => _.fromPairs(cachePairs));
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
- let moduleMap = getModuleMap(data);
1139
- let sourcePath = path.parse(source.path);
1140
- let moduleName = _.get(
1141
- source,
1142
- 'analysis.module',
1143
- moduleMap || sourcePath.name || 'Avo'
1144
- );
1145
-
1146
- let sourcePathExts = [];
1147
-
1148
- if (sourcePath.ext === ".js" || sourcePath.ext === ".ts") {
1149
- sourcePathExts.push("js");
1150
- sourcePathExts.push("jsx");
1151
- sourcePathExts.push("ts");
1152
- sourcePathExts.push("tsx");
1153
- } else if (sourcePath.ext === ".java" || sourcePath.ext === ".kt") {
1154
- sourcePathExts.push("java");
1155
- sourcePathExts.push("kt");
1156
- } else if (sourcePath.ext === ".m" || sourcePath.ext === ".swift") {
1157
- sourcePathExts.push("m");
1158
- sourcePathExts.push("swift");
1159
- } else {
1160
- sourcePathExts.push(sourcePath.ext.substring(1));
1161
- }
1162
-
1163
- if (argv.verbose) {
1164
- console.log("Looking in files with extensions:" , sourcePathExts);
1165
- }
1166
-
1167
- let globs = [
1168
- new Minimatch(
1169
- _.get(source, 'analysis.glob', '**/*.+(' + sourcePathExts.join("|") + ")"),
1170
- {}
1171
- ),
1172
- new Minimatch('!' + source.path, {})
1173
- ];
1174
-
1175
- let lookup = _.pickBy(cache, (value, path) =>
1176
- _.every(globs, mm => mm.match(path))
1177
- );
1178
-
1179
- return Promise.all(
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
- } else {
1197
- return source;
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
- return sources.then(sources => {
1204
- report.tree(
1205
- 'sources',
1206
- sources.map(source => {
1207
- return {
1208
- name: source.name + ' (' + source.path + ')',
1209
- children:
1210
- _.map(source.results, (results, eventName) => {
1211
- return {
1212
- name: eventName,
1213
- children:
1214
- _.size(results) > 0
1215
- ? _.map(results, (result, matchFile) => {
1216
- return {
1217
- name:
1218
- 'used in ' +
1219
- matchFile +
1220
- ': ' +
1221
- result.length +
1222
- (result.length === 1 ? ' time' : ' times')
1223
- };
1224
- })
1225
- : [
1226
- {
1227
- name: `${logSymbols.error} no usage found`
1228
- }
1229
- ]
1230
- };
1231
- })
1232
- };
1233
- })
1234
- );
1235
-
1236
- let totalEvents = _.sumBy(sources, source => _.size(source.results));
1237
- let missingEvents = _.sumBy(sources, source =>
1238
- _.sum(
1239
- _.map(source.results, (results, eventName) =>
1240
- _.size(results) > 0 ? 0 : 1
1241
- )
1242
- )
1243
- );
1244
- if (missingEvents === 0) {
1245
- if (totalEvents === 0) {
1246
- report.error('no events found in the avo file - please run avo pull');
1247
- } else {
1248
- report.info(`${totalEvents} events seen in code`);
1249
- }
1250
- } else {
1251
- report.info(
1252
- `${totalEvents -
1253
- missingEvents} of ${totalEvents} events seen in code`
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
- if (missingEvents > 0) {
1257
- report.error(
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
- .catch(error => {
1283
- if (error.code == 'ENOENT') {
1284
- report.error(
1285
- "Avo file not found. Run 'avo pull' to pull latest Avo files."
1286
- );
1287
- } else {
1288
- throw error;
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
- yargs(hideBin(process.argv))
1294
- .usage('$0 command')
1295
- .scriptName('avo')
1296
- .option('v', {
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', {
1297
1261
  alias: 'verbose',
1298
1262
  default: false,
1299
1263
  describe: 'make output more verbose',
1300
- type: 'boolean'
1301
- })
1302
- .option('f', {
1264
+ type: 'boolean',
1265
+ })
1266
+ .option('f', {
1303
1267
  alias: 'force',
1304
1268
  describe: 'Proceed with merge when incoming branch is open',
1305
1269
  default: false,
1306
- type: 'boolean'
1307
- })
1308
- .command({
1270
+ type: 'boolean',
1271
+ })
1272
+ .command({
1309
1273
  command: 'track-install',
1310
1274
  desc: false,
1311
1275
  handler: () => {
1312
- Avo.cliInstalled({
1313
- userId_: installIdOrUserId(),
1314
- cliInvokedByCi: invokedByCi()
1315
- });
1316
- }
1317
- })
1318
- .command({
1276
+ Avo.cliInstalled({
1277
+ userId_: installIdOrUserId(),
1278
+ cliInvokedByCi: invokedByCi(),
1279
+ });
1280
+ },
1281
+ })
1282
+ .command({
1319
1283
  command: 'init',
1320
1284
  desc: 'Initialize an Avo workspace in the current folder',
1321
- handler: argv => {
1322
- loadAvoJsonOrInit({argv, skipInit: true})
1323
- .then(json => {
1324
- if (json) {
1325
- Avo.cliInvoked({
1326
- schemaId: json.schema.id,
1327
- schemaName: json.schema.name,
1328
- branchId: json.branch.id,
1329
- branchName: json.branch.name,
1330
- userId_: installIdOrUserId(),
1331
- cliAction: Avo.CliAction.INIT,
1332
- cliInvokedByCi: invokedByCi()
1333
- });
1334
- report.info(
1335
- `Avo is already initialized for workspace ${cyan(
1336
- json.schema.name
1337
- )} (${file('avo.json')} exists)`
1338
- );
1339
- } 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
+ }
1340
1301
  Avo.cliInvoked({
1341
- schemaId: 'N/A',
1342
- schemaName: 'N/A',
1343
- branchId: 'N/A',
1344
- branchName: 'N/A',
1345
- userId_: installIdOrUserId(),
1346
- cliAction: Avo.CliAction.INIT,
1347
- cliInvokedByCi: invokedByCi()
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(),
1348
1309
  });
1349
- return requireAuth(argv, () => {
1350
- return init()
1310
+ return requireAuth(argv, () => init()
1351
1311
  .then(writeAvoJson)
1352
1312
  .then(() => {
1353
- report.info(
1354
- "Run 'avo pull' to pull analytics wrappers from Avo"
1355
- );
1356
- });
1357
- });
1358
- }
1313
+ report.info("Run 'avo pull' to pull analytics wrappers from Avo");
1314
+ }));
1359
1315
  })
1360
- .catch(() => {
1361
- Avo.cliInvoked({
1362
- schemaId: 'N/A',
1363
- schemaName: 'N/A',
1364
- branchId: 'N/A',
1365
- branchName: 'N/A',
1366
- userId_: installIdOrUserId(),
1367
- cliAction: Avo.CliAction.INIT,
1368
- cliInvokedByCi: invokedByCi()
1369
- });
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
+ });
1370
1326
  });
1371
- }
1372
- })
1373
- .command({
1327
+ },
1328
+ })
1329
+ .command({
1374
1330
  command: 'pull [source]',
1375
1331
  desc: 'Pull analytics wrappers from Avo workspace',
1376
- builder: yargs => {
1377
- return yargs.option('branch', {
1332
+ builder: (yargs) => yargs.option('branch', {
1378
1333
  describe: 'Name of Avo branch to pull from',
1379
- type: 'string'
1380
- });
1381
- },
1382
- handler: argv => {
1383
- loadAvoJsonOrInit({argv})
1384
- .then(json => {
1385
- Avo.cliInvoked({
1386
- schemaId: json.schema.id,
1387
- schemaName: json.schema.name,
1388
- branchId: json.branch.id,
1389
- branchName: json.branch.name,
1390
- userId_: installIdOrUserId(),
1391
- cliAction: Avo.CliAction.PULL,
1392
- cliInvokedByCi: invokedByCi()
1393
- });
1394
- requireAuth(argv, () => {
1395
- if (argv.branch && json.branch.name !== argv.branch) {
1396
- return checkout(argv.branch, json)
1397
- .then(json => getSource(argv, json))
1398
- .then(([source, json]) => pull(source, json));
1399
- } else {
1400
- report.info(`Pulling from branch '${json.branch.name}'`);
1401
- return getSource(argv, json).then(([source, json]) => {
1402
- return pull(source, json);
1403
- });
1404
- }
1405
- });
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
+ });
1406
1357
  })
1407
- .catch(error => {
1408
- Avo.cliInvoked({
1409
- schemaId: 'N/A',
1410
- schemaName: 'N/A',
1411
- branchId: 'N/A',
1412
- branchName: 'N/A',
1413
- userId_: installIdOrUserId(),
1414
- cliAction: Avo.CliAction.PULL,
1415
- cliInvokedByCi: invokedByCi()
1416
- });
1417
- throw error;
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;
1418
1369
  });
1419
- }
1420
- })
1421
- .command({
1370
+ },
1371
+ })
1372
+ .command({
1422
1373
  command: 'checkout [branch]',
1423
1374
  aliases: ['branch'],
1424
1375
  desc: 'Switch branches',
1425
- handler: argv => {
1426
- return loadAvoJsonOrInit({argv})
1427
- .then(json => {
1428
- Avo.cliInvoked({
1376
+ handler: (argv) => loadAvoJsonOrInit({ argv, skipInit: false, skipPullMaster: false })
1377
+ .then((json) => {
1378
+ Avo.cliInvoked({
1429
1379
  schemaId: json.schema.id,
1430
1380
  schemaName: json.schema.name,
1431
1381
  branchId: json.branch.id,
1432
1382
  branchName: json.branch.name,
1433
1383
  userId_: installIdOrUserId(),
1434
1384
  cliAction: Avo.CliAction.CHECKOUT,
1435
- cliInvokedByCi: invokedByCi()
1436
- });
1437
- report.info(`Currently on branch '${json.branch.name}'`);
1438
- requireAuth(argv, () => {
1439
- return checkout(argv.branch, json).then(writeAvoJson);
1440
- });
1441
- })
1442
- .catch(error => {
1443
- 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({
1444
1392
  schemaId: 'N/A',
1445
1393
  schemaName: 'N/A',
1446
1394
  branchId: 'N/A',
1447
1395
  branchName: 'N/A',
1448
1396
  userId_: installIdOrUserId(),
1449
1397
  cliAction: Avo.CliAction.CHECKOUT,
1450
- cliInvokedByCi: invokedByCi()
1451
- });
1452
- throw error;
1398
+ cliInvokedByCi: invokedByCi(),
1453
1399
  });
1454
- }
1455
- })
1456
- .command({
1400
+ throw error;
1401
+ }),
1402
+ })
1403
+ .command({
1457
1404
  command: 'source <command>',
1458
1405
  desc: 'Manage sources for the current project',
1459
- builder: yargs => {
1460
- yargs
1461
- .command({
1462
- command: '$0',
1463
- desc: 'List sources in this project',
1464
- handler: argv => {
1465
- loadAvoJsonOrInit({argv})
1466
- .then(json => {
1467
- Avo.cliInvoked({
1468
- schemaId: json.schema.id,
1469
- schemaName: json.schema.name,
1470
- branchId: json.branch.id,
1471
- branchName: json.branch.name,
1472
- userId_: installIdOrUserId(),
1473
- cliAction: Avo.CliAction.SOURCE,
1474
- cliInvokedByCi: invokedByCi()
1475
- });
1476
-
1477
- if (!json.sources || !json.sources.length) {
1478
- report.info(
1479
- `No sources defined in ${file('avo.json')}. Run ${cmd(
1480
- 'avo source add'
1481
- )} to add sources`
1482
- );
1483
- return;
1484
- }
1485
-
1486
- report.info(`Sources in this project:`);
1487
- report.tree(
1488
- 'sources',
1489
- json.sources.map(source => {
1490
- return {
1491
- name: source.name,
1492
- children: [{name: source.path}]
1493
- };
1494
- })
1495
- );
1496
- })
1497
- .catch(error => {
1498
- Avo.cliInvoked({
1499
- schemaId: 'N/A',
1500
- schemaName: 'N/A',
1501
- branchId: 'N/A',
1502
- branchName: 'N/A',
1503
- userId_: installIdOrUserId(),
1504
- cliAction: Avo.CliAction.SOURCE,
1505
- 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;
1506
1444
  });
1507
- throw error;
1508
- });
1509
- }
1445
+ },
1510
1446
  })
1511
- .command({
1512
- command: 'add [source]',
1513
- desc: 'Add a source to this project',
1514
- handler: argv => {
1515
- loadAvoJsonOrInit({argv})
1516
- .then(json => {
1517
- Avo.cliInvoked({
1518
- schemaId: json.schema.id,
1519
- schemaName: json.schema.name,
1520
- branchId: json.branch.id,
1521
- branchName: json.branch.name,
1522
- userId_: installIdOrUserId(),
1523
- cliAction: Avo.CliAction.SOURCE_ADD,
1524
- cliInvokedByCi: invokedByCi()
1525
- });
1526
-
1527
- requireAuth(argv, () => {
1528
- selectSource(argv.source, json).then(writeAvoJson);
1529
- });
1530
- })
1531
- .catch(error => {
1532
- Avo.cliInvoked({
1533
- schemaId: 'N/A',
1534
- schemaName: 'N/A',
1535
- branchId: 'N/A',
1536
- branchName: 'N/A',
1537
- userId_: installIdOrUserId(),
1538
- cliAction: Avo.CliAction.SOURCE_ADD,
1539
- cliInvokedByCi: invokedByCi()
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;
1540
1477
  });
1541
- throw error;
1542
- });
1543
- }
1478
+ },
1544
1479
  })
1545
- .command({
1546
- command: 'remove [source]',
1547
- aliases: ['rm'],
1548
- desc: 'Remove a source from this project',
1549
- handler: argv => {
1550
- loadAvoJsonOrInit({argv})
1551
- .then(json => {
1552
- Avo.cliInvoked({
1553
- schemaId: json.schema.id,
1554
- schemaName: json.schema.name,
1555
- branchId: json.branch.id,
1556
- branchName: json.branch.name,
1557
- userId_: installIdOrUserId(),
1558
- cliAction: Avo.CliAction.SOURCE_REMOVE,
1559
- cliInvokedByCi: invokedByCi()
1560
- });
1561
-
1562
- if (!json.sources || !json.sources.length) {
1563
- report.warn(
1564
- `No sources defined in ${file('avo.json')}. Run ${cmd(
1565
- 'avo source add'
1566
- )} to add sources`
1567
- );
1568
- return;
1569
- }
1570
-
1571
- const getSource = () => {
1572
- if (argv.source) {
1573
- return Promise.resolve(
1574
- _.find(json.sources, source =>
1575
- matchesSource(source, argv.source)
1576
- )
1577
- );
1578
- } else {
1579
- let choices = json.sources.map(source => ({
1580
- value: source,
1581
- name: source.name
1582
- }));
1583
- return inquirer
1584
- .prompt({
1585
- type: 'list',
1586
- name: 'source',
1587
- message: 'Select a source to remove',
1588
- choices: choices,
1589
- pageSize: 15
1590
- })
1591
- .then(answer => answer.source);
1592
- }
1593
- };
1594
- getSource(argv, json).then(targetSource => {
1595
- if (!targetSource) {
1596
- report.error(`Source ${argv.source} not found in project.`);
1597
- return;
1598
- }
1599
-
1600
- return inquirer
1601
- .prompt([
1602
- {
1603
- type: 'confirm',
1604
- name: 'remove',
1605
- default: true,
1606
- message: `Are you sure you want to remove source ${targetSource.name} from project`
1607
- }
1608
- ])
1609
- .then(answer => {
1610
- if (answer.remove) {
1611
- let sources = _.filter(
1612
- json.sources || [],
1613
- source => source.id !== targetSource.id
1614
- );
1615
- let newJson = Object.assign({}, json, {
1616
- sources: sources
1617
- });
1618
- return writeAvoJson(newJson).then(() => {
1619
- // XXX ask to remove file as well?
1620
- report.info(
1621
- `Removed source ${targetSource.name} from project`
1622
- );
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();
1623
1543
  });
1624
- } else {
1625
- report.info(
1626
- `Did not remove source ${targetSource.name} from project`
1627
- );
1628
- }
1629
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;
1630
1557
  });
1631
- })
1632
- .catch(error => {
1633
- Avo.cliInvoked({
1634
- schemaId: 'N/A',
1635
- schemaName: 'N/A',
1636
- branchId: 'N/A',
1637
- branchName: 'N/A',
1638
- userId_: installIdOrUserId(),
1639
- cliAction: Avo.CliAction.SOURCE_REMOVE,
1640
- cliInvokedByCi: invokedByCi()
1641
- });
1642
- throw error;
1643
- });
1644
- }
1558
+ },
1645
1559
  });
1646
- }
1647
- })
1648
- .command({
1560
+ },
1561
+ })
1562
+ .command({
1649
1563
  command: 'status [source]',
1650
1564
  desc: 'Show the status of the Avo implementation',
1651
- handler: argv => {
1652
- loadAvoJsonOrInit({argv})
1653
- .then(json => {
1654
- Avo.cliInvoked({
1655
- schemaId: json.schema.id,
1656
- schemaName: json.schema.name,
1657
- branchId: json.branch.id,
1658
- branchName: json.branch.name,
1659
- userId_: installIdOrUserId(),
1660
- cliAction: Avo.CliAction.STATUS,
1661
- cliInvokedByCi: invokedByCi()
1662
- });
1663
- report.info(`Currently on branch '${json.branch.name}'`);
1664
- return getSource(argv, json);
1665
- })
1666
- .then(([source, json]) => {
1667
- 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);
1668
1579
  })
1669
- .catch(error => {
1670
- Avo.cliInvoked({
1671
- schemaId: 'N/A',
1672
- schemaName: 'N/A',
1673
- branchId: 'N/A',
1674
- branchName: 'N/A',
1675
- userId_: installIdOrUserId(),
1676
- cliAction: Avo.CliAction.STATUS,
1677
- cliInvokedByCi: invokedByCi()
1678
- });
1679
- throw error;
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;
1680
1592
  });
1681
- }
1682
- })
1683
-
1684
- .command({
1593
+ },
1594
+ })
1595
+ .command({
1685
1596
  command: 'merge main',
1686
1597
  aliases: ['merge master'],
1687
1598
  desc: 'Pull the Avo main branch into your current branch',
1688
- handler: argv => {
1689
- loadAvoJsonOrInit({argv, skipPullMaster: true})
1690
- .then(json => {
1691
- Avo.cliInvoked({
1692
- schemaId: json.schema.id,
1693
- schemaName: json.schema.name,
1694
- branchId: json.branch.id,
1695
- branchName: json.branch.name,
1696
- userId_: installIdOrUserId(),
1697
- cliAction: Avo.CliAction.MERGE,
1698
- cliInvokedByCi: invokedByCi(),
1699
- force: json.force
1700
- });
1701
-
1702
- return requireAuth(argv, () => {
1703
- return pullMaster(json).then(writeAvoJson);
1704
- });
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));
1705
1613
  })
1706
- .catch(error => {
1707
- Avo.cliInvoked({
1708
- schemaId: 'N/A',
1709
- schemaName: 'N/A',
1710
- branchId: 'N/A',
1711
- branchName: 'N/A',
1712
- userId_: installIdOrUserId(),
1713
- cliAction: Avo.CliAction.MERGE,
1714
- cliInvokedByCi: invokedByCi()
1715
- });
1716
- throw error;
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;
1717
1625
  });
1718
- }
1719
- })
1720
- .command({
1626
+ },
1627
+ })
1628
+ .command({
1721
1629
  command: 'conflict',
1722
1630
  aliases: ['resolve', 'conflicts'],
1723
1631
  desc: 'Resolve git conflicts in Avo files',
1724
- handler: argv => {
1725
- return pify(fs.readFile)('avo.json', 'utf8')
1726
- .then(file => {
1727
- if (hasMergeConflicts(file)) {
1728
- return requireAuth(argv, () => {
1729
- return resolveAvoJsonConflicts(file, {
1730
- argv
1731
- }).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) => {
1732
1639
  Avo.cliInvoked({
1733
- schemaId: json.schema.id,
1734
- schemaName: json.schema.name,
1735
- branchId: json.branch.id,
1736
- branchName: json.branch.name,
1737
- userId_: installIdOrUserId(),
1738
- cliAction: Avo.CliAction.CONFLICT,
1739
- cliInvokedByCi: invokedByCi()
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(),
1740
1647
  });
1741
1648
  pull(null, json);
1742
- });
1743
- });
1744
- } else {
1745
- report.info(
1746
- "No git conflicts found in avo.json. Run 'avo pull' to resolve git conflicts in other Avo files."
1747
- );
1748
- const json = JSON.parse(file);
1749
- Avo.cliInvoked({
1750
- schemaId: json.schema.id,
1751
- schemaName: json.schema.name,
1752
- branchId: json.branch.id,
1753
- branchName: json.branch.name,
1754
- userId_: installIdOrUserId(),
1755
- cliAction: Avo.CliAction.CONFLICT,
1756
- cliInvokedByCi: invokedByCi()
1757
- });
1758
- return Promise.resolve(json);
1759
- }
1760
- })
1761
- .catch(error => {
1762
- Avo.cliInvoked({
1763
- schemaId: 'N/A',
1764
- schemaName: 'N/A',
1765
- branchId: 'N/A',
1766
- branchName: 'N/A',
1767
- userId_: installIdOrUserId(),
1768
- cliAction: Avo.CliAction.CONFLICT,
1769
- cliInvokedByCi: invokedByCi()
1770
- });
1771
- throw error;
1772
- });
1773
- }
1774
- })
1775
- .command({
1776
- command: 'edit',
1777
- desc: 'Open the Avo workspace in your browser',
1778
- handler: argv => {
1779
- loadAvoJsonOrInit({argv})
1780
- .then(json => {
1781
- 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({
1782
1654
  schemaId: json.schema.id,
1783
1655
  schemaName: json.schema.name,
1784
1656
  branchId: json.branch.id,
1785
1657
  branchName: json.branch.name,
1786
1658
  userId_: installIdOrUserId(),
1787
- cliAction: Avo.CliAction.EDIT,
1788
- cliInvokedByCi: invokedByCi()
1789
- });
1790
-
1791
- const schema = json.schema;
1792
- const url = `https://www.avo.app/schemas/${schema.id}`;
1793
- report.info(
1794
- `Opening ${cyan(schema.name)} workspace in Avo: ${link(url)}`
1795
- );
1796
- opn(url, {wait: false});
1797
- })
1798
- .catch(error => {
1799
- Avo.cliInvoked({
1659
+ cliAction: Avo.CliAction.CONFLICT,
1660
+ cliInvokedByCi: invokedByCi(),
1661
+ });
1662
+ return Promise.resolve(json);
1663
+ })
1664
+ .catch((error) => {
1665
+ Avo.cliInvoked({
1800
1666
  schemaId: 'N/A',
1801
1667
  schemaName: 'N/A',
1802
1668
  branchId: 'N/A',
1803
1669
  branchName: 'N/A',
1804
1670
  userId_: installIdOrUserId(),
1805
- cliAction: Avo.CliAction.EDIT,
1806
- cliInvokedByCi: invokedByCi()
1807
- });
1808
- throw error;
1671
+ cliAction: Avo.CliAction.CONFLICT,
1672
+ cliInvokedByCi: invokedByCi(),
1809
1673
  });
1810
- }
1811
- })
1812
- .command({
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({
1813
1712
  command: 'login',
1814
1713
  desc: 'Log into the Avo platform',
1815
1714
  handler: () => {
1816
- let command = () => {
1817
- let user = conf.get('user');
1818
- if (user) {
1819
- report.info(`Already logged in as ${email(user.email)}`);
1820
- return;
1821
- }
1822
- login()
1823
- .then(function(result) {
1824
- conf.set('user', result.user);
1825
- conf.set('tokens', result.tokens);
1826
-
1827
- Avo.signedIn({
1828
- userId_: result.user.user_id,
1829
- email: result.user.email,
1830
- cliInvokedByCi: invokedByCi()
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
+ });
1831
1739
  });
1832
-
1833
- report.success(`Logged in as ${email(result.user.email)}`);
1834
- })
1835
- .catch(() => {
1836
- Avo.signInFailed({
1837
- userId_: conf.get('avo_install_id'),
1838
- emailInput: '', // XXX this is not passed back here
1839
- signInError: Avo.SignInError.UNKNOWN,
1840
- cliInvokedByCi: invokedByCi()
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(),
1841
1751
  });
1842
- });
1843
- };
1844
-
1845
- loadAvoJson()
1846
- .then(json => {
1847
- Avo.cliInvoked({
1848
- schemaId: json.schema.id,
1849
- schemaName: json.schema.name,
1850
- branchId: json.branch.id,
1851
- branchName: json.branch.name,
1852
- userId_: installIdOrUserId(),
1853
- cliAction: Avo.CliAction.LOGIN,
1854
- cliInvokedByCi: invokedByCi()
1855
- });
1856
- command();
1752
+ command();
1857
1753
  })
1858
- .catch(() => {
1859
- Avo.cliInvoked({
1860
- schemaId: 'N/A',
1861
- schemaName: 'N/A',
1862
- branchId: 'N/A',
1863
- branchName: 'N/A',
1864
- userId_: installIdOrUserId(),
1865
- cliAction: Avo.CliAction.LOGIN,
1866
- cliInvokedByCi: invokedByCi()
1867
- });
1868
- command();
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();
1869
1765
  });
1870
- }
1871
- })
1872
- .command({
1766
+ },
1767
+ })
1768
+ .command({
1873
1769
  command: 'logout',
1874
1770
  desc: 'Log out from the Avo platform',
1875
1771
  handler: () => {
1876
- let command = () => {
1877
- let user = conf.get('user');
1878
- let tokens = conf.get('tokens');
1879
- let currentToken = _.get(tokens, 'refreshToken');
1880
- let token = currentToken;
1881
- api.setRefreshToken(token);
1882
- if (token) {
1883
- logout(token);
1884
- }
1885
- if (token || user || tokens) {
1886
- var msg = 'Logged out';
1887
- if (token === currentToken) {
1888
- if (user) {
1889
- 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);
1890
1780
  }
1891
- } else {
1892
- msg += ' token "' + bold(token) + '"';
1893
- }
1894
- report.log(msg);
1895
- } else {
1896
- report.log(`No need to logout, you're not logged in`);
1897
- }
1898
- };
1899
-
1900
- loadAvoJson()
1901
- .then(json => {
1902
- Avo.cliInvoked({
1903
- schemaId: json.schema.id,
1904
- schemaName: json.schema.name,
1905
- branchId: json.branch.id,
1906
- branchName: json.branch.name,
1907
- userId_: installIdOrUserId(),
1908
- cliAction: Avo.CliAction.LOGOUT,
1909
- cliInvokedByCi: invokedByCi()
1910
- });
1911
- command();
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();
1912
1809
  })
1913
- .catch(() => {
1914
- Avo.cliInvoked({
1915
- schemaId: 'N/A',
1916
- schemaName: 'N/A',
1917
- branchId: 'N/A',
1918
- branchName: 'N/A',
1919
- userId_: installIdOrUserId(),
1920
- cliAction: Avo.CliAction.LOGOUT,
1921
- cliInvokedByCi: invokedByCi()
1922
- });
1923
- command();
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();
1924
1821
  });
1925
- }
1926
- })
1927
- .command({
1822
+ },
1823
+ })
1824
+ .command({
1928
1825
  command: 'whoami',
1929
1826
  desc: 'Shows the currently logged in username',
1930
- handler: argv => {
1931
- let command = () => {
1932
- requireAuth(argv, () => {
1933
- if (conf.has('user')) {
1934
- let user = conf.get('user');
1935
- report.info(`Logged in as ${email(user.email)}`);
1936
- } else {
1937
- report.warn(`Not logged in`);
1938
- }
1939
- });
1940
- };
1941
-
1942
- loadAvoJson()
1943
- .then(json => {
1944
- Avo.cliInvoked({
1945
- schemaId: json.schema.id,
1946
- schemaName: json.schema.name,
1947
- branchId: json.branch.id,
1948
- branchName: json.branch.name,
1949
- userId_: installIdOrUserId(),
1950
- cliAction: Avo.CliAction.WHOAMI,
1951
- cliInvokedByCi: invokedByCi()
1952
- });
1953
- command();
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();
1954
1851
  })
1955
- .catch(() => {
1956
- Avo.cliInvoked({
1957
- schemaId: 'N/A',
1958
- schemaName: 'N/A',
1959
- branchId: 'N/A',
1960
- branchName: 'N/A',
1961
- userId_: installIdOrUserId(),
1962
- cliAction: Avo.CliAction.WHOAMI,
1963
- cliInvokedByCi: invokedByCi()
1964
- });
1965
- command();
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();
1966
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)}`));
1967
1875
  }
1968
- })
1969
-
1970
- .demandCommand(1, 'must provide a valid command')
1971
- .recommendCommands()
1972
- .help().argv;
1973
-
1974
- /////////////////////////////////////////////////////////////////////////
1975
- // LOGGING
1976
-
1977
- function cmd(command) {
1978
- return `${gray('`')}${cyan(command)}${gray('`')}`;
1979
- }
1980
-
1981
- function link(url) {
1982
- return underline(url);
1983
- }
1984
-
1985
- function file(url) {
1986
- return underline(url);
1987
- }
1988
-
1989
- function email(email) {
1990
- return underline(email);
1991
- }
1992
-
1993
- function cancelWait() {
1994
- if (_cancel !== null) {
1995
- _cancel();
1996
- _cancel = null;
1997
- }
1998
- }
1999
- function wait(message, timeOut) {
2000
- cancelWait();
2001
- timeOut = timeOut || 300;
2002
- let running = false;
2003
- let spinner;
2004
- let stopped = false;
2005
-
2006
- setTimeout(() => {
2007
- if (stopped) return;
2008
-
2009
- spinner = ora(gray(message));
2010
- spinner.color = 'gray';
2011
- spinner.start();
2012
-
2013
- running = true;
2014
- }, timeOut);
2015
-
2016
- const cancel = () => {
2017
- stopped = true;
2018
- if (running) {
2019
- spinner.stop();
2020
- running = false;
2021
- }
2022
- process.removeListener('nowExit', cancel);
2023
- };
2024
-
2025
- process.on('nowExit', cancel);
2026
- cancelWait = cancel;
2027
- }
2028
-
2029
- /////////////////////////////////////////////////////////////////////////
2030
- // AUTH
2031
-
2032
- function _haveValidAccessToken(refreshToken) {
2033
- if (_.isEmpty(lastAccessToken)) {
2034
- var tokens = conf.get('tokens');
2035
- if (refreshToken === _.get(tokens, 'refreshToken')) {
2036
- lastAccessToken = tokens;
2037
- }
2038
- }
2039
-
2040
- return (
2041
- _.has(lastAccessToken, 'idToken') &&
2042
- lastAccessToken.refreshToken === refreshToken &&
2043
- _.has(lastAccessToken, 'expiresAt') &&
2044
- lastAccessToken.expiresAt > Date.now() + FIFTEEN_MINUTES_IN_MS
2045
- );
2046
- }
2047
-
2048
- function getAccessToken(refreshToken) {
2049
- if (_haveValidAccessToken(refreshToken)) {
2050
- return Promise.resolve(lastAccessToken);
2051
- }
2052
-
2053
- return _refreshAccessToken(refreshToken);
2054
- }
2055
-
2056
- function _refreshAccessToken(refreshToken) {
2057
- return api
2058
- .request('POST', '/auth/refresh', {
2059
- origin: api.apiOrigin,
2060
- data: {
2061
- token: refreshToken
2062
- }
2063
- })
2064
- .then(
2065
- function(res) {
2066
- if (res.status === 401 || res.status === 400) {
2067
- return {idToken: refreshToken};
2068
- }
2069
-
2070
- if (!_.isString(res.body.idToken)) {
2071
- throw INVALID_CREDENTIAL_ERROR;
2072
- }
2073
- lastAccessToken = _.assign(
2074
- {
2075
- expiresAt: Date.now() + res.body.expiresIn * 1000,
2076
- refreshToken: refreshToken
2077
- },
2078
- res.body
2079
- );
2080
-
2081
- var currentRefreshToken = _.get(conf.get('tokens'), 'refreshToken');
2082
- if (refreshToken === currentRefreshToken) {
2083
- conf.set('tokens', lastAccessToken);
2084
- }
2085
-
2086
- return lastAccessToken;
2087
- },
2088
- function(err) {
2089
- throw INVALID_CREDENTIAL_ERROR;
2090
- }
2091
- );
2092
- }
2093
-
2094
- function _getLoginUrl(callbackUrl) {
2095
- return (
2096
- api.authOrigin +
2097
- '/auth/cli?' +
2098
- _.map(
2099
- {
2100
- state: nonce,
2101
- redirect_uri: callbackUrl
2102
- },
2103
- function(v, k) {
2104
- return k + '=' + encodeURIComponent(v);
2105
- }
2106
- ).join('&')
2107
- );
2108
- }
2109
-
2110
- function _loginWithLocalhost(port) {
2111
- return new Promise(function(resolve, reject) {
2112
- var callbackUrl = _getCallbackUrl(port);
2113
- var authUrl = _getLoginUrl(callbackUrl);
2114
-
2115
- var server = http.createServer(function(req, res) {
2116
- var tokens;
2117
- var query = _.get(url.parse(req.url, true), 'query', {});
2118
-
2119
- if (query.state === nonce && _.isString(query.code)) {
2120
- return _getTokensFromAuthorizationCode(query.code, callbackUrl)
2121
- .then(function(result) {
2122
- tokens = result;
2123
- return _respondWithRedirect(
2124
- req,
2125
- res,
2126
- api.authOrigin + '/auth/cli/success'
2127
- );
2128
- })
2129
- .then(function() {
2130
- cancelWait();
2131
- server.shutdown();
2132
- return resolve({
2133
- user: jwt.decode(tokens.idToken),
2134
- tokens: tokens
2135
- });
2136
- })
2137
- .catch(function() {
2138
- return _respondWithRedirect(
2139
- req,
2140
- res,
2141
- api.authOrigin + '/auth/cli/error'
2142
- );
2143
- });
2144
- }
2145
- _respondWithRedirect(req, res, api.authOrigin + '/auth/cli/error');
2146
- });
2147
-
2148
- server = httpShutdown(server);
2149
-
2150
- server.listen(port, function() {
2151
- report.info(`Visit this URL on any device to login: ${link(authUrl)}`);
2152
- wait(`Waiting for authentication...`);
2153
-
2154
- opn(authUrl, {wait: false});
2155
- });
2156
-
2157
- server.on('error', function() {
2158
- _loginWithoutLocalhost().then(resolve, reject);
2159
- });
2160
- });
2161
- }
2162
-
2163
- function _loginWithoutLocalhost() {
2164
- var callbackUrl = _getCallbackUrl();
2165
- var authUrl = _getLoginUrl(callbackUrl);
2166
-
2167
- report.info(`Visit this URL on any device to login: ${url(authUrl)}`);
2168
-
2169
- opn(authUrl, {wait: false});
2170
- }
2171
-
2172
- function login() {
2173
- return _getPort().then(_loginWithLocalhost, _loginWithoutLocalhost);
2174
- }
2175
-
2176
- function _respondWithRedirect(req, res, url) {
2177
- return new Promise(function(resolve, reject) {
2178
- res.writeHead(302, {
2179
- Location: url
2180
- });
2181
- res.end();
2182
- req.socket.destroy();
2183
- return resolve();
2184
- });
2185
- }
2186
-
2187
- function _getTokensFromAuthorizationCode(code, callbackUrl) {
2188
- return api
2189
- .request('POST', '/auth/token', {
2190
- origin: api.apiOrigin,
2191
- data: {
2192
- token: code,
2193
- redirect_uri: callbackUrl
2194
- }
2195
- })
2196
- .then(
2197
- function(res) {
2198
- if (!_.has(res, 'body.idToken') && !_.has(res, 'body.refreshToken')) {
2199
- throw INVALID_CREDENTIAL_ERROR;
2200
- }
2201
- lastAccessToken = _.assign(
2202
- {
2203
- expiresAt: Date.now() + res.body.expiresIn * 1000
2204
- },
2205
- res.body
2206
- );
2207
- return lastAccessToken;
2208
- },
2209
- function(err) {
2210
- throw INVALID_CREDENTIAL_ERROR;
2211
- }
2212
- );
2213
- }
2214
-
2215
- function _getCallbackUrl(port) {
2216
- if (_.isUndefined(port)) {
2217
- return 'urn:ietf:wg:oauth:2.0:oob';
2218
- }
2219
- return 'http://localhost:' + port;
2220
- }
2221
-
2222
- function logout(refreshToken) {
2223
- if (lastAccessToken.refreshToken === refreshToken) {
2224
- lastAccessToken = {};
2225
- }
2226
- var tokens = conf.get('tokens');
2227
- var currentToken = _.get(tokens, 'refreshToken');
2228
- if (refreshToken === currentToken) {
2229
- conf.delete('user');
2230
- conf.delete('tokens');
2231
- }
2232
- }
2233
-
2234
- function responseToError(response, body) {
2235
- if (typeof body === 'string' && response.statusCode === 404) {
2236
- body = {
2237
- error: {
2238
- message: 'Not Found'
2239
- }
2240
- };
2241
- }
2242
-
2243
- if (response.statusCode < 400) {
2244
- return null;
2245
- }
2246
-
2247
- if (typeof body !== 'object') {
2248
- try {
2249
- body = JSON.parse(body);
2250
- } catch (e) {
2251
- body = {};
1876
+ else {
1877
+ // @ts-ignore
1878
+ report.error(err.message);
2252
1879
  }
2253
- }
2254
-
2255
- if (!body.error) {
2256
- var message = response.statusCode === 404 ? 'Not Found' : 'Unknown Error';
2257
- body.error = {
2258
- message: message
2259
- };
2260
- }
2261
-
2262
- var message = `HTTP Error: ${response.statusCode}, ${body.error.message ||
2263
- body.error}`;
2264
-
2265
- var exitCode;
2266
- if (response.statusCode >= 500) {
2267
- // 5xx errors are unexpected
2268
- exitCode = 2;
2269
- } else {
2270
- // 4xx errors happen sometimes
2271
- exitCode = 1;
2272
- }
2273
-
2274
- _.unset(response, 'request.headers');
2275
- return new AvoError(message, {
2276
- context: {
2277
- body: body,
2278
- response: response
2279
- },
2280
- exit: exitCode
2281
- });
2282
- }
2283
-
2284
- function requireAuth(argv, cb) {
2285
- let tokens = conf.get('tokens');
2286
- let user = conf.get('user');
2287
-
2288
- let tokenOpt = argv.token || process.env.AVO_TOKEN;
2289
-
2290
- if (tokenOpt) {
2291
- api.setRefreshToken(tokenOpt);
2292
- return cb();
2293
- }
2294
-
2295
- if (!user || !tokens) {
2296
- report.error(`Command requires authentication. Run ${cmd('avo login')}`);
1880
+ // @ts-ignore
1881
+ // console.error(err.stack);
2297
1882
  process.exit(1);
2298
- return;
2299
- }
2300
-
2301
- argv.user = user;
2302
- argv.tokens = tokens;
2303
- api.setRefreshToken(tokens.refreshToken);
2304
- return cb();
2305
- }
2306
-
2307
- //////////////////// ////////
2308
- // catch unhandled promises
2309
-
2310
- process.on('unhandledRejection', err => {
2311
- cancelWait();
2312
-
2313
- if (!(err instanceof Error) && !(err instanceof AvoError)) {
2314
- err = new AvoError(`Promise rejected with value: ${util.inspect(err)}`);
2315
- }
2316
- report.error(err.message);
2317
- // console.error(err.stack);
2318
-
2319
- process.exit(1);
2320
1883
  });