1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const fs = require('fs');
|
17 | const os = require('os');
|
18 | const path = require('path');
|
19 |
|
20 | const AESjs = require('aes-js');
|
21 | const async = require('async');
|
22 | const chalk = require('chalk');
|
23 | const validUrl = require('valid-url');
|
24 | const isemail = require('isemail');
|
25 | const uuidV4 = require('uuid-v4');
|
26 | const isempty = require('lodash.isempty');
|
27 | const lodashGet = require('lodash.get');
|
28 | const lodashCloneDeep = require('lodash.clonedeep');
|
29 | const lodashPick = require('lodash.pick');
|
30 | const lodashOmitBy = require('lodash.omitby');
|
31 | const lodashSortBy = require('lodash.sortby');
|
32 | const lodashPickBy = require('lodash.pickby');
|
33 | const logger = require('./logger.js');
|
34 | const moment = require('moment');
|
35 | const { ActiveItemTypes, DomainTypes, Errors, PromptMessages } = require('./Constants');
|
36 | const KinveyError = require('./KinveyError');
|
37 |
|
38 | const Utils = {};
|
39 | Utils.formatHost = function formatHost(host) {
|
40 | let validHost = validUrl.isHttpUri(host);
|
41 | if (validHost) {
|
42 | if (validHost.slice(-1) !== '/') {
|
43 | validHost = `${validHost}/`;
|
44 | }
|
45 | return validHost;
|
46 | }
|
47 |
|
48 | validHost = validUrl.isHttpsUri(host);
|
49 | if (validHost) {
|
50 | if (validHost.slice(-1) !== '/') {
|
51 | validHost = `${validHost}/`;
|
52 | }
|
53 | return validHost;
|
54 | }
|
55 |
|
56 | return `https://${host}-manage.kinvey.com/`;
|
57 | };
|
58 |
|
59 | Utils.formatList = function formatList(list, name = 'name') {
|
60 | const result = list.map(el => ({
|
61 | name: el[name],
|
62 | value: el
|
63 | }));
|
64 | result.sort((x, y) => {
|
65 | if (x[name].toLowerCase() < y[name].toLowerCase()) return -1;
|
66 | return 1;
|
67 | });
|
68 | return result;
|
69 | };
|
70 |
|
71 | Utils.formatHostList = function formatHostList(list) {
|
72 | const result = list.map(el => ({
|
73 | name: el,
|
74 | value: el
|
75 | }));
|
76 | result.unshift({
|
77 | name: 'all hosts',
|
78 | value: null
|
79 | });
|
80 | return result;
|
81 | };
|
82 |
|
83 | Utils.readFile = function readFile(file, cb) {
|
84 | logger.debug('Reading contents from file %s', chalk.cyan(file));
|
85 | return fs.readFile(file, cb);
|
86 | };
|
87 |
|
88 | Utils.readJSON = function readJSON(file, cb) {
|
89 | logger.debug('Reading JSON from file %s', chalk.cyan(file));
|
90 | return async.waterfall([
|
91 | next => Utils.readFile(file, next),
|
92 | (data, next) => {
|
93 | let json = null;
|
94 | try {
|
95 | logger.debug('Parsing JSON from file: %s', chalk.cyan(file));
|
96 | json = JSON.parse(data);
|
97 | } catch (error) {
|
98 | logger.warn('Invalid JSON in file %s', chalk.cyan(file));
|
99 | return next(error);
|
100 | }
|
101 | return next(null, json);
|
102 | }
|
103 | ], cb);
|
104 | };
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | Utils.readJSONSync = function readJSONSync(filePath) {
|
111 | const rawData = fs.readFileSync(filePath);
|
112 | const jsonData = JSON.parse(rawData);
|
113 | return jsonData;
|
114 | };
|
115 |
|
116 | Utils.writeFile = function writeFile(file, contents, cb) {
|
117 | logger.debug('Writing contents to file %s', chalk.cyan(file));
|
118 | return fs.writeFile(file, contents, cb);
|
119 | };
|
120 |
|
121 | Utils.writeJSON = function writeJSON({ file, data, createParentDir, space }, done) {
|
122 | async.series([
|
123 | (next) => {
|
124 | if (!createParentDir) {
|
125 | return setImmediate(next);
|
126 | }
|
127 |
|
128 | Utils.mkdirp(file, next);
|
129 | },
|
130 | (next) => {
|
131 | const contents = JSON.stringify(data, null, space);
|
132 | Utils.writeFile(file, contents, next);
|
133 | }
|
134 | ], done);
|
135 | };
|
136 |
|
137 | Utils.writeConfigFile = function writeConfigFile(filePath, data, done) {
|
138 | Utils.writeJSON({ file: filePath, data, createParentDir: true, space: 4 }, (err) => {
|
139 | done(err, data);
|
140 | });
|
141 | };
|
142 |
|
143 | Utils.isValidMFAToken = function isValidMFAToken(value) {
|
144 | return (/^\d{6}$/.test(value));
|
145 | };
|
146 |
|
147 | Utils.isValidEmail = function isValidEmail(value) {
|
148 | return !Utils.isNullOrUndefined(value) && isemail(value);
|
149 | };
|
150 |
|
151 | Utils.isNullOrUndefined = function isNullOrUndefined(value) {
|
152 | return value == null;
|
153 | };
|
154 |
|
155 | Utils.isEmpty = function isEmpty(value) {
|
156 | return isempty(value);
|
157 | };
|
158 |
|
159 | Utils.isValidTimestamp = function isValidTimestamp(ts) {
|
160 | return moment(ts, moment.ISO_8601, true).isValid();
|
161 | };
|
162 |
|
163 | Utils.isValidNonZeroInteger = function isValidNonZeroInteger(number) {
|
164 | if (number === 0 || number === '0') return false;
|
165 | return /^\d+$/.test(number);
|
166 | };
|
167 |
|
168 | Utils.validateEmail = function validateEmail(value) {
|
169 | if (Utils.isValidEmail(value)) {
|
170 | return true;
|
171 | }
|
172 |
|
173 | return PromptMessages.INVALID_EMAIL_ADDRESS;
|
174 | };
|
175 |
|
176 | Utils.isStringWhitespace = function isStringWhitespace(value) {
|
177 | const trimmedValue = value.trim();
|
178 | return trimmedValue.length < 1;
|
179 | };
|
180 |
|
181 | Utils.askForValue = function askForValue(value) {
|
182 | if (Utils.isNullOrUndefined(value)) {
|
183 | return true;
|
184 | }
|
185 |
|
186 | return false;
|
187 | };
|
188 |
|
189 | Utils.validateString = function validateString(value) {
|
190 | if (!Utils.isStringWhitespace(value)) {
|
191 | return true;
|
192 | }
|
193 |
|
194 | return PromptMessages.INVALID_STRING;
|
195 | };
|
196 |
|
197 | Utils.validateMFAToken = function validateMFAToken(value) {
|
198 | if (Utils.isValidMFAToken(value)) {
|
199 | return true;
|
200 | }
|
201 |
|
202 | return PromptMessages.INVALID_MFA_TOKEN;
|
203 | };
|
204 |
|
205 | Utils.Endpoints = {
|
206 | getIdPartFromId: id => (Utils.isNullOrUndefined(id) ? '' : `/${id}`),
|
207 | version: schemaVersion => `v${schemaVersion}`,
|
208 | session: () => 'session',
|
209 | apps: (schemaVersion, appId) => `${Utils.Endpoints.version(schemaVersion)}/apps${Utils.Endpoints.getIdPartFromId(appId)}`,
|
210 | envs: (schemaVersion, envId) => `${Utils.Endpoints.version(schemaVersion)}/environments${Utils.Endpoints.getIdPartFromId(envId)}`,
|
211 | push: (schemaVersion, envId) => `${Utils.Endpoints.envs(schemaVersion, envId)}/push`,
|
212 | envsByAppId: (schemaVersion, appId) => `${Utils.Endpoints.apps(schemaVersion, appId)}/environments`,
|
213 | collections: (schemaVersion, envId, collName) => `${Utils.Endpoints.envs(schemaVersion, envId)}/collections${Utils.Endpoints.getIdPartFromId(collName)}`,
|
214 | commonCode: (schemaVersion, envId, name) => `${Utils.Endpoints.envs(schemaVersion, envId)}/business-logic/common${Utils.Endpoints.getIdPartFromId(name)}`,
|
215 | endpoints: (schemaVersion, envId, name) => `${Utils.Endpoints.envs(schemaVersion, envId)}/business-logic/endpoints${Utils.Endpoints.getIdPartFromId(name)}`,
|
216 | hooks: (schemaVersion, envId, collName, hookName) => `${Utils.Endpoints.envs(schemaVersion, envId)}/business-logic/collections/${collName}${Utils.Endpoints.getIdPartFromId(hookName)}`,
|
217 | collectionHooks: (schemaVersion, envId, collectionName) => {
|
218 | const namePart = collectionName ? `/${collectionName}` : '';
|
219 | return `${Utils.Endpoints.envs(schemaVersion, envId)}/business-logic/collections${namePart}`;
|
220 | },
|
221 | orgs: (schemaVersion, orgId) => `${Utils.Endpoints.version(schemaVersion)}/organizations${Utils.Endpoints.getIdPartFromId(orgId)}`,
|
222 | appsByOrg: (schemaVersion, orgId) => `${Utils.Endpoints.orgs(schemaVersion, orgId)}/apps`,
|
223 | jobs: (schemaVersion, id) => `${Utils.Endpoints.version(schemaVersion)}/jobs${Utils.Endpoints.getIdPartFromId(id)}`,
|
224 | services: (schemaVersion, serviceId) => `${Utils.Endpoints.version(schemaVersion)}/services${Utils.Endpoints.getIdPartFromId(serviceId)}`,
|
225 | serviceEnvs: (schemaVersion, serviceId, svcEnvId) => `${Utils.Endpoints.services(schemaVersion, serviceId)}/environments${Utils.Endpoints.getIdPartFromId(svcEnvId)}`,
|
226 | serviceStatus: (schemaVersion, serviceId, svcEnvId) => `${Utils.Endpoints.serviceEnvs(schemaVersion, serviceId, svcEnvId)}/status`,
|
227 | serviceLogs: (schemaVersion, serviceId, svcEnvId) => `${Utils.Endpoints.serviceEnvs(schemaVersion, serviceId, svcEnvId)}/logs`,
|
228 | sites: (schemaVersion, siteId) => `${Utils.Endpoints.version(schemaVersion)}/sites${Utils.Endpoints.getIdPartFromId(siteId)}`,
|
229 | siteEnvs: (schemaVersion, siteId, siteEnvId) => `${Utils.Endpoints.sites(schemaVersion, siteId)}/environments${Utils.Endpoints.getIdPartFromId(siteEnvId)}`,
|
230 | siteDeploy: (schemaVersion, siteId, siteEnvId) => `${Utils.Endpoints.siteEnvs(schemaVersion, siteId, siteEnvId)}/files`,
|
231 | sitePublish: (schemaVersion, siteId) => `${Utils.Endpoints.sites(schemaVersion, siteId)}/publish`,
|
232 | siteUnpublish: (schemaVersion, siteId) => `${Utils.Endpoints.sites(schemaVersion, siteId)}/unpublish`,
|
233 | siteStatus: (schemaVersion, siteId) => `${Utils.Endpoints.sites(schemaVersion, siteId)}/status`
|
234 | };
|
235 |
|
236 | Utils.getCommandNameFromOptions = function getCommandNameFromOptions(options) {
|
237 | let name;
|
238 | if (options._ && options._.length) {
|
239 | name = options._.join(' ');
|
240 | }
|
241 |
|
242 | return name;
|
243 | };
|
244 |
|
245 | Utils.getErrorFromRequestError = function getErrorFromRequestError(err) {
|
246 | let errResult;
|
247 | const errCode = err.code;
|
248 | if (errCode === 'ENOTFOUND') {
|
249 | errResult = new KinveyError(Errors.InvalidConfigUrl);
|
250 | } else if (errCode === 'ETIMEDOUT') {
|
251 | errResult = new KinveyError(Errors.RequestTimedOut);
|
252 | } else if (errCode === 'ECONNRESET') {
|
253 | errResult = new KinveyError(Errors.ConnectionReset);
|
254 | } else if (errCode === 'ECONNREFUSED') {
|
255 | errResult = new KinveyError(errCode, `Connection refused at ${err.address}`);
|
256 | } else {
|
257 | errResult = err;
|
258 | }
|
259 |
|
260 | return errResult;
|
261 | };
|
262 |
|
263 | Utils.isMFATokenError = function isMFATokenError(err) {
|
264 | return err && err.name === 'InvalidTwoFactorAuth';
|
265 | };
|
266 |
|
267 | Utils.findAndSortInternalServices = function findAndSortInternalServices(services) {
|
268 | const result = services.filter(el => el.type === 'internal');
|
269 | result.sort((x, y) => {
|
270 | if (x.name.toLowerCase() < y.name.toLowerCase()) {
|
271 | return -1;
|
272 | }
|
273 |
|
274 | return 1;
|
275 | });
|
276 |
|
277 | return result;
|
278 | };
|
279 |
|
280 | Utils.isArtifact = function isArtifact(artifacts, base, filepath) {
|
281 | const relative = path.normalize(path.relative(base, filepath));
|
282 | for (let i = 0; i < artifacts.length; i += 1) {
|
283 | const pattern = artifacts[i];
|
284 | if (relative.indexOf(pattern) === 0 || (`${relative}/`) === pattern) return true;
|
285 | }
|
286 | return false;
|
287 | };
|
288 |
|
289 | Utils.getDeviceInformation = function getDeviceInformation(cliVersion) {
|
290 | const info = `kinvey-cli/${cliVersion} ${os.platform()} ${os.release()}`;
|
291 | return info;
|
292 | };
|
293 |
|
294 | Utils.mkdirp = function mkdirp(pathToFile, done) {
|
295 | try {
|
296 | const filepath = path.resolve(pathToFile);
|
297 | filepath.split(path.sep).slice().reduce((prev, curr, i) => {
|
298 | if (prev && !fs.existsSync(prev)) {
|
299 | fs.mkdirSync(prev);
|
300 | }
|
301 | return prev + path.sep + curr;
|
302 | });
|
303 | done();
|
304 | } catch (err) {
|
305 | done(err);
|
306 | }
|
307 | };
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | Utils.isUUID = function isUUID(value) {
|
316 | if (Utils.isNullOrUndefined(value)) {
|
317 | return false;
|
318 | }
|
319 |
|
320 | const dash = '-';
|
321 | const lengthWithDashes = 36;
|
322 | const lengthValue = value.length;
|
323 | const couldBeRegularUUUID = value.includes(dash) && lengthValue === lengthWithDashes;
|
324 | if (couldBeRegularUUUID) {
|
325 | return uuidV4.isUUID(value);
|
326 | }
|
327 |
|
328 | const lengthWithoutDashes = 32;
|
329 | const couldBeUUIDWithoutDashes = lengthValue === lengthWithoutDashes;
|
330 | if (!couldBeUUIDWithoutDashes) {
|
331 | return false;
|
332 | }
|
333 |
|
334 | const valueWithDashes = `${value.slice(0, 8)}${dash}${value.slice(8, 12)}${dash}${value.slice(12, 16)}${dash}${value.slice(16, 20)}${dash}${value.slice(20)}`;
|
335 | return uuidV4.isUUID(valueWithDashes);
|
336 | };
|
337 |
|
338 | Utils.isEnvID = function isEnvID(value) {
|
339 | if (Utils.isNullOrUndefined(value)) {
|
340 | return false;
|
341 | }
|
342 |
|
343 |
|
344 | const lengthEnvId = 13;
|
345 | const isId = value.length === lengthEnvId && value.startsWith('kid_');
|
346 | return isId;
|
347 | };
|
348 |
|
349 | Utils.validateActiveItemType = function validateActiveItemType(itemType) {
|
350 | if (!ActiveItemTypes.includes(itemType)) {
|
351 | throw new KinveyError(`Invalid item type: ${itemType}.`);
|
352 | }
|
353 | };
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | Utils.getItemError = function getItemError(entityType) {
|
361 | const msg = `No ${entityType} identifier is specified and active ${entityType} is not set.`;
|
362 | return new KinveyError(Errors.ItemNotSpecified.NAME, msg);
|
363 | };
|
364 |
|
365 | Utils.getConfigTypeError = function getConfigTypeError(type) {
|
366 | const msg = `Unrecognized config type: ${type}`;
|
367 | return new KinveyError('ValidationError', msg);
|
368 | };
|
369 |
|
370 | Utils.isEntityError = function isEntityError(err) {
|
371 | if (Utils.isNullOrUndefined(err)) {
|
372 | return false;
|
373 | }
|
374 |
|
375 | return err.name === Errors.NoEntityFound.NAME || err.name === Errors.TooManyEntitiesFound.NAME;
|
376 | };
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 | Utils.getCustomEntityError = function getCustomEntityError(entityType, identifier, errName) {
|
386 | if (errName === Errors.NoEntityFound.NAME) {
|
387 | return Utils.getCustomNotFoundError(entityType, identifier);
|
388 | }
|
389 |
|
390 | if (errName === Errors.TooManyEntitiesFound.NAME) {
|
391 | const lastPart = identifier ? ` with identifier '${identifier}'.` : '.';
|
392 | const msg = `Found too many ${entityType}s${lastPart}`;
|
393 | return new KinveyError(Errors.TooManyEntitiesFound.NAME, msg);
|
394 | }
|
395 |
|
396 | return new KinveyError('UnknownError', `Failed to construct proper error for error name: ${errName}`);
|
397 | };
|
398 |
|
399 | Utils.getCustomNotFoundError = function getCustomNotFoundError(entityType, identifier) {
|
400 | const lastPart = identifier ? ` with identifier '${identifier}'.` : '.';
|
401 | const msg = `Could not find ${entityType}${lastPart}`;
|
402 | return new KinveyError(Errors.NoEntityFound.NAME, msg);
|
403 | };
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 | Utils.getTransformedError = function getTransformedError(err, entityType, identifier) {
|
413 | if (!Utils.isEntityError(err)) {
|
414 | return err;
|
415 | }
|
416 |
|
417 | return Utils.getCustomEntityError(entityType, identifier, err.name);
|
418 | };
|
419 |
|
420 | Utils.getValueFromObject = function getValueFromObject(obj, path, defaultValue = 'Not set') {
|
421 | return lodashGet(obj, path, defaultValue);
|
422 | };
|
423 |
|
424 | Utils.mapFromSource = function mapFromSource(mapping, originalSource) {
|
425 | const mappingKeys = Object.keys(mapping);
|
426 | const sourceIsArr = Array.isArray(originalSource);
|
427 | let source;
|
428 | if (!sourceIsArr) {
|
429 | source = [originalSource];
|
430 | } else {
|
431 | source = originalSource;
|
432 | }
|
433 |
|
434 | const result = source.map((item) => {
|
435 | const targetItem = {};
|
436 |
|
437 | mappingKeys.forEach((k) => {
|
438 | let targetKeyValue;
|
439 | const valueOfMappingKey = mapping[k];
|
440 | if (typeof valueOfMappingKey === 'function') {
|
441 | targetKeyValue = valueOfMappingKey(item);
|
442 | } else {
|
443 | targetKeyValue = Utils.getValueFromObject(item, mapping[k]);
|
444 | }
|
445 |
|
446 | targetItem[k] = targetKeyValue;
|
447 | });
|
448 |
|
449 | return targetItem;
|
450 | });
|
451 |
|
452 | return sourceIsArr ? result : result[0];
|
453 | };
|
454 |
|
455 | Utils.getObjectByOmitting = function getObjectByOmitting(source, propsToOmit) {
|
456 | const result = {};
|
457 | const keys = Object.keys(source);
|
458 | keys.forEach((k) => {
|
459 | if (!propsToOmit.includes(k)) {
|
460 | result[k] = lodashCloneDeep(source[k]);
|
461 | }
|
462 | });
|
463 |
|
464 | return result;
|
465 | };
|
466 |
|
467 | Utils.getObjectByPicking = function getObjectByPicking(source, propsToPick) {
|
468 | return lodashPick(source, propsToPick);
|
469 | };
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 | Utils.getObjectFromDelimiterSeparatedKeyValuePairs = function getObjectFromDelimiterSeparatedKeyValuePairs(dsv, primaryDelimiter = ',', secondaryDelimiter = '=') {
|
480 | if (typeof dsv !== 'string') {
|
481 | throw new Error('Expected string.');
|
482 | }
|
483 |
|
484 | const listOfKeyValuePairs = dsv.split(primaryDelimiter);
|
485 | if (Utils.isEmpty(listOfKeyValuePairs)) {
|
486 | throw new Error('Empty list.');
|
487 | }
|
488 |
|
489 | const result = {};
|
490 | listOfKeyValuePairs.reduce((accumulator, pair) => {
|
491 | const delimiterIndex = pair.indexOf(secondaryDelimiter);
|
492 | if (delimiterIndex < 0) {
|
493 | throw new Error(`No delimiter ('${secondaryDelimiter}') in '${pair}'.`);
|
494 | }
|
495 |
|
496 | const key = pair.substring(0, delimiterIndex);
|
497 | const value = pair.substring(delimiterIndex + 1, pair.length);
|
498 | if (key.length < 1 || value.length < 1) {
|
499 | throw new Error(`Invalid key-value pair: ${pair}`);
|
500 | }
|
501 |
|
502 | accumulator[key] = value;
|
503 | return accumulator;
|
504 | }, result);
|
505 |
|
506 | return result;
|
507 | };
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 |
|
514 |
|
515 | Utils.sortList = function sortList(list, property = 'name') {
|
516 | return lodashSortBy(list, x => x[property].toLowerCase());
|
517 | };
|
518 |
|
519 | Utils.pickBy = function pickBy(source, predicate) {
|
520 | return lodashPickBy(source, predicate);
|
521 | };
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 | Utils.getUsingEntityMsg = function getUsingEntityMsg(entityType, identifier) {
|
530 | return `Using ${entityType}: ${identifier}`;
|
531 | };
|
532 |
|
533 |
|
534 |
|
535 |
|
536 |
|
537 | Utils.generateSecretKey = function generateSecretKey() {
|
538 | let ret = '';
|
539 | const length = 58;
|
540 | while (ret.length < length) {
|
541 | ret += Math.random().toString(16).substring(2);
|
542 | }
|
543 |
|
544 | const text = ret.substring(0, length);
|
545 |
|
546 | const key256 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
547 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
|
548 | 29, 30, 31];
|
549 | const textBytes = AESjs.utils.utf8.toBytes(text);
|
550 |
|
551 |
|
552 | const aesCtr = new AESjs.ModeOfOperation.ctr(key256, new AESjs.Counter(5));
|
553 | const encryptedBytes = aesCtr.encrypt(textBytes);
|
554 | const result = AESjs.utils.hex.fromBytes(encryptedBytes);
|
555 | return result;
|
556 | };
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | Utils.stripNullOrUndefined = function stripNullOrUndefined(obj) {
|
564 | return lodashOmitBy(obj, Utils.isNullOrUndefined);
|
565 | };
|
566 |
|
567 | module.exports = Utils;
|