UNPKG

81.5 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chalk'), require('os'), require('fs'), require('child_process'), require('perf_hooks'), require('request-promise'), require('path'), require('moment')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'chalk', 'os', 'fs', 'child_process', 'perf_hooks', 'request-promise', 'path', 'moment'], factory) :
4 (global = global || self, factory(global.RallyTools = {}, global.chalk$1, global.os, global.fs, global.child_process, global.perf_hooks, global.rp, global.path, global.moment));
5}(this, (function (exports, chalk$1, os, fs, child_process, perf_hooks, rp, path, moment) { 'use strict';
6
7 chalk$1 = chalk$1 && Object.prototype.hasOwnProperty.call(chalk$1, 'default') ? chalk$1['default'] : chalk$1;
8 var fs__default = 'default' in fs ? fs['default'] : fs;
9 rp = rp && Object.prototype.hasOwnProperty.call(rp, 'default') ? rp['default'] : rp;
10 var path__default = 'default' in path ? path['default'] : path;
11 moment = moment && Object.prototype.hasOwnProperty.call(moment, 'default') ? moment['default'] : moment;
12
13 exports.configFile = null;
14
15 if (os.homedir) {
16 exports.configFile = os.homedir() + "/.rallyconfig";
17 }
18
19
20 function loadConfig(file) {
21 if (file) exports.configFile = file;
22 if (!exports.configFile) return;
23 exports.configObject = {
24 hasConfig: true
25 };
26
27 try {
28 let json = fs.readFileSync(exports.configFile);
29 exports.configObject = JSON.parse(json);
30 exports.configObject.hasConfig = true;
31 } catch (e) {
32 if (e.code == "ENOENT") {
33 exports.configObject.hasConfig = false; //ok, they should probably make a config
34 } else {
35 throw e;
36 }
37 }
38 }
39 function loadConfigFromArgs(args) {
40 let tempConfig = {
41 hasConfig: true,
42 ...args.config
43 };
44 exports.configObject = tempConfig;
45 }
46 function setConfig(obj) {
47 exports.configObject = obj;
48 }
49
50 //these are the help entries for each command
51 //function retuns obj.a.b.c
52
53 function deepAccess(obj, path) {
54 let o = obj;
55
56 for (let key of path) {
57 if (!o) return [];
58 o = o[key];
59 }
60
61 return o;
62 } //This takes a class as the first argument, then adds a getter/setter pair that
63 //corresponds to an object in this.data
64
65
66 function defineAssoc(classname, shortname, path) {
67 path = path.split(".");
68 let lastKey = path.pop();
69 Object.defineProperty(classname.prototype, shortname, {
70 get() {
71 return deepAccess(this, path)[lastKey];
72 },
73
74 set(val) {
75 deepAccess(this, path)[lastKey] = val;
76 }
77
78 });
79 }
80
81 function spawn(options, ...args) {
82 if (typeof options !== "object") {
83 args.unshift(options);
84 options = {};
85 } //todo options
86
87
88 return new Promise((resolve, reject) => {
89 let start = perf_hooks.performance.now();
90 let stdout = "";
91 let stderr = "";
92 let cp = child_process.spawn(...args);
93 let write = global.write;
94
95 if (options.noecho) {
96 write = () => {};
97 }
98
99 if (cp.stdout) cp.stdout.on("data", chunk => {
100 stdout += chunk;
101 write(chunk);
102 });
103 if (cp.stderr) cp.stderr.on("data", chunk => {
104 stderr += chunk;
105 write(chunk);
106 });
107 cp.on("error", reject);
108 cp.on("close", code => {
109 let end = perf_hooks.performance.now();
110 let time = end - start;
111 let timestr = time > 1000 ? (time / 100 | 0) / 10 + "s" : (time | 0) + "ms";
112 resolve({
113 stdout,
114 stderr,
115 exitCode: code,
116 time,
117 timestr
118 });
119 });
120 });
121 }
122
123 global.chalk = chalk$1;
124
125 global.log = (...text) => console.log(...text);
126
127 global.write = (...text) => process.stdout.write(...text);
128
129 global.elog = (...text) => console.error(...text);
130
131 global.ewrite = (...text) => process.stderr.write(...text);
132
133 global.errorLog = (...text) => log(...text.map(chalk$1.red));
134
135 class lib {
136 //This function takes 2 required arguemnts:
137 // env: the enviornment you wish to use
138 // and either:
139 // 'path', the short path to the resource. ex '/presets/'
140 // 'path_full', the full path to the resource like 'https://discovery-dev.sdvi.com/presets'
141 //
142 // If the method is anything but GET, either payload or body should be set.
143 // payload should be a javascript object to be turned into json as the request body
144 // body should be a string that is passed as the body. for example: the python code of a preset.
145 //
146 // qs are the querystring parameters, in a key: value object.
147 // {filter: "name=test name"} becomes something like 'filter=name=test+name'
148 //
149 // headers are the headers of the request. "Content-Type" is already set if
150 // payload is given as a parameter
151 //
152 // fullResponse should be true if you want to receive the request object,
153 // not just the returned data.
154 static async makeAPIRequest({
155 env,
156 path,
157 path_full,
158 fullPath,
159 payload,
160 body,
161 method = "GET",
162 qs,
163 headers = {},
164 fullResponse = false,
165 timeout = exports.configObject.timeout || 20000
166 }) {
167 var _configObject$api;
168
169 //backwards compatability from ruby script
170 if (fullPath) path_full = fullPath; //Keys are defined in enviornment variables
171
172 let config = exports.configObject === null || exports.configObject === void 0 ? void 0 : (_configObject$api = exports.configObject.api) === null || _configObject$api === void 0 ? void 0 : _configObject$api[env];
173
174 if (!config) {
175 throw new UnconfiguredEnvError(env);
176 }
177
178 if (method !== "GET" && !exports.configObject.dangerModify) {
179 if (env === "UAT" && exports.configObject.restrictUAT || env === "PROD") {
180 throw new ProtectedEnvError(env);
181 }
182 }
183
184 let rally_api_key = config.key;
185 let rally_api = config.url;
186
187 if (path && path.startsWith("/v1.0/")) {
188 rally_api = rally_api.replace("/api/v2", "/api");
189 }
190
191 path = path_full || rally_api + path;
192
193 if (payload) {
194 body = JSON.stringify(payload, null, 4);
195 }
196
197 if (payload) {
198 headers["Content-Type"] = "application/vnd.api+json";
199 }
200
201 let fullHeaders = {
202 //SDVI ignores this header sometimes.
203 Accept: "application/vnd.api+json",
204 "X-SDVI-Client-Application": "Discovery-rtlib-" + (exports.configObject.appName || "commandline"),
205 ...headers
206 };
207
208 if (exports.configObject.vvverbose) {
209 log(`${method} @ ${path}`);
210 log(JSON.stringify(fullHeaders, null, 4));
211
212 if (body) {
213 log(body);
214 } else {
215 log("(No body");
216 }
217 }
218
219 let requestOptions = {
220 method,
221 body,
222 qs,
223 uri: path,
224 timeout,
225 auth: {
226 bearer: rally_api_key
227 },
228 headers: fullHeaders,
229 simple: false,
230 resolveWithFullResponse: true
231 };
232 let response;
233
234 try {
235 response = await rp(requestOptions);
236
237 if (exports.configObject.vverbose || exports.configObject.vvverbose) {
238 log(chalk$1`${method} @ ${response.request.uri.href}`);
239 }
240 } catch (e) {
241 if ((e === null || e === void 0 ? void 0 : e.cause.code) === "ESOCKETTIMEDOUT") {
242 throw new APIError(response || {}, requestOptions, body);
243 } else {
244 throw e;
245 }
246 } //Throw an error for any 5xx or 4xx
247
248
249 if (!fullResponse && ![200, 201, 202, 203, 204].includes(response.statusCode)) {
250 throw new APIError(response, requestOptions, body);
251 }
252
253 let contentType = response.headers["content-type"];
254 let isJSONResponse = contentType === "application/vnd.api+json" || contentType === "application/json";
255
256 if (exports.configObject.vvverbose) {
257 log(response.body);
258 }
259
260 if (fullResponse) {
261 return response;
262 } else if (isJSONResponse) {
263 var _response, _response$body;
264
265 if ([200, 201, 202, 203, 204].includes(response.statusCode) && !((_response = response) === null || _response === void 0 ? void 0 : (_response$body = _response.body) === null || _response$body === void 0 ? void 0 : _response$body.trim())) return {};
266
267 try {
268 return JSON.parse(response.body);
269 } catch (e) {
270 log(response.body);
271 throw new AbortError("Body is not valid json: ");
272 }
273 } else {
274 return response.body;
275 }
276 } //Index a json endpoint that returns a {links} field.
277 //This function returns the merged data objects as an array
278 //
279 //Additonal options (besides makeAPIRequest options):
280 // - Observe: function to be called for each set of data from the api
281
282
283 static async indexPath(env, path) {
284 let all = [];
285 let opts = typeof env === "string" ? {
286 env,
287 path
288 } : env;
289 let json = await this.makeAPIRequest(opts);
290 let [numPages, pageSize] = this.numPages(json.links.last); //log(`num pages: ${numPages} * ${pageSize}`);
291
292 all = [...json.data];
293
294 while (json.links.next) {
295 json = await this.makeAPIRequest({ ...opts,
296 path_full: json.links.next
297 });
298 if (opts.observe) await opts.observe(json.data);
299 all = [...all, ...json.data];
300 }
301
302 return all;
303 } //Returns number of pages and pagination size
304
305
306 static numPages(str) {
307 return /page=(\d+)p(\d+)/.exec(str).slice(1);
308 }
309
310 static arrayChunk(array, chunkSize) {
311 let newArr = [];
312
313 for (let i = 0; i < array.length; i += chunkSize) {
314 newArr.push(array.slice(i, i + chunkSize));
315 }
316
317 return newArr;
318 }
319
320 static async doPromises(promises, result = [], cb) {
321 for (let promise of promises) {
322 let res = await promise;
323 result.push(res);
324
325 if (cb) {
326 cb(res.data);
327 }
328 }
329
330 return result;
331 }
332
333 static clearProgress(size = 30) {
334 if (!exports.configObject.globalProgress) return;
335 process.stderr.write(`\r${" ".repeat(size + 15)}\r`);
336 }
337
338 static async drawProgress(i, max, size = process.stdout.columns - 15 || 15) {
339 if (!exports.configObject.globalProgress) return;
340 if (size > 45) size = 45;
341 let pct = Number(i) / Number(max); //clamp between 0 and 1
342
343 pct = pct < 0 ? 0 : pct > 1 ? 1 : pct;
344 let numFilled = Math.floor(pct * size);
345 let numEmpty = size - numFilled;
346 this.clearProgress(size);
347 process.stderr.write(`[${"*".repeat(numFilled)}${" ".repeat(numEmpty)}] ${i} / ${max}`);
348 }
349
350 static async keepalive(func, inputData, {
351 chunksize = 20,
352 observe = async _ => _,
353 progress = exports.configObject.globalProgress
354 } = {}) {
355 let total = inputData ? inputData.length : func.length;
356 let i = 0;
357
358 let createPromise = () => {
359 let ret;
360 if (i >= total) return [];
361
362 if (inputData) {
363 ret = [i, func(inputData[i])];
364 } else {
365 ret = [i, func[i]()];
366 }
367
368 i++;
369 return ret;
370 };
371
372 let values = [];
373 let finished = 0;
374 if (progress) process.stderr.write("\n");
375 let threads = [...this.range(chunksize)].map(async whichThread => {
376 while (true) {
377 let [i, currentPromise] = createPromise();
378 if (i == undefined) break;
379 values[i] = await observe((await currentPromise));
380 if (progress) this.drawProgress(++finished, total);
381 }
382 });
383 await Promise.all(threads);
384 if (progress) process.stderr.write("\n");
385 return values;
386 }
387
388 static *range(start, end) {
389 if (end === undefined) {
390 end = start;
391 start = 0;
392 }
393
394 while (start < end) yield start++;
395 } //Index a json endpoint that returns a {links} field.
396 //
397 //This function is faster than indexPath because it can guess the pages it
398 //needs to retreive so that it can request all assets at once.
399 //
400 //This function assumes that the content from the inital request is the
401 //first page, so starting on another page may cause issues. Consider
402 //indexPath for that.
403 //
404 //Additional opts, besides default indexPath opts:
405 // - chunksize[10]: How often to break apart concurrent requests
406
407
408 static async indexPathFast(env, path) {
409 let opts = typeof env === "string" ? {
410 env,
411 path
412 } : env; //Create a copy of the options in case we need to have a special first request
413
414 let start = opts.start || 1;
415 let initOpts = { ...opts
416 };
417
418 if (opts.pageSize) {
419 initOpts.qs = { ...opts.qs
420 };
421 initOpts.qs.page = `${start}p${opts.pageSize}`;
422 }
423
424 let json = await this.makeAPIRequest(initOpts);
425 if (opts.observe && opts.start !== 1) json = await opts.observe(json);
426 let baselink = json.links.first;
427
428 const linkToPage = page => baselink.replace(`page=1p`, `page=${page}p`);
429
430 let [numPages, pageSize] = this.numPages(json.links.last); //Construct an array of all the requests that are done simultanously.
431 //Assume that the content from the inital request is the first page.
432
433 let allResults = await this.keepalive(this.makeAPIRequest, [...this.range(start + 1, Number(numPages) + 1 || opts.limit + 1)].map(i => ({ ...opts,
434 path_full: linkToPage(i)
435 })), {
436 chunksize: opts.chunksize,
437 observe: opts.observe
438 });
439
440 if (start == 1) {
441 allResults.unshift(json);
442 }
443
444 this.clearProgress();
445 let all = [];
446
447 for (let result of allResults) {
448 for (let item of result.data) {
449 all.push(item);
450 }
451 }
452
453 return all;
454 }
455
456 static isLocalEnv(env) {
457 return !env || env === "LOCAL" || env === "LOC";
458 }
459
460 static envName(env) {
461 if (this.isLocalEnv(env)) return "LOCAL";
462 return env;
463 }
464
465 }
466 class AbortError extends Error {
467 constructor(message) {
468 super(message);
469 Error.captureStackTrace(this, this.constructor);
470 this.name = "AbortError";
471 }
472
473 }
474 class APIError extends Error {
475 constructor(response, opts, body) {
476 super(chalk$1`
477{reset Request returned} {yellow ${response === null || response === void 0 ? void 0 : response.statusCode}}{
478{green ${JSON.stringify(opts, null, 4)}}
479{green ${body}}
480{reset ${response.body}}
481===============================
482{red ${response.body ? "Request timed out" : "Bad response from API"}}
483===============================
484 `);
485 this.response = response;
486 this.opts = opts;
487 this.body = body;
488 Error.captureStackTrace(this, this.constructor);
489 this.name = "ApiError";
490 }
491
492 }
493 class UnconfiguredEnvError extends AbortError {
494 constructor(env) {
495 super("Unconfigured enviornment: " + env);
496 this.name = "Unconfigured Env Error";
497 }
498
499 }
500 class ProtectedEnvError extends AbortError {
501 constructor(env) {
502 super("Protected enviornment: " + env);
503 this.name = "Protected Env Error";
504 }
505
506 }
507 class FileTooLargeError extends Error {
508 constructor(file) {
509 super(`File ${file.parentAsset ? file.parentAsset.name : "(unknown)"}/${file.name} size is: ${file.sizeGB}g (> ~.2G)`);
510 this.name = "File too large error";
511 }
512
513 }
514 class Collection {
515 constructor(arr) {
516 this.arr = arr;
517 }
518
519 [Symbol.iterator]() {
520 return this.arr[Symbol.iterator]();
521 }
522
523 findById(id) {
524 return this.arr.find(x => x.id == id);
525 }
526
527 findByName(name) {
528 return this.arr.find(x => x.name == name);
529 }
530
531 findByNameContains(name) {
532 return this.arr.find(x => x.name.includes(name));
533 }
534
535 log() {
536 for (let d of this) {
537 if (d) {
538 log(d.chalkPrint(true));
539 } else {
540 log(chalk$1`{red (None)}`);
541 }
542 }
543 }
544
545 get length() {
546 return this.arr.length;
547 }
548
549 }
550 class RallyBase {
551 static handleCaching() {
552 if (!this.cache) this.cache = [];
553 }
554
555 static isLoaded(env) {
556 if (!this.hasLoadedAll) return;
557 return this.hasLoadedAll[env];
558 }
559
560 static async getById(env, id, qs) {
561 this.handleCaching();
562
563 for (let item of this.cache) {
564 if (item.id == id && item.remote === env || `${env}-${id}` === item.metastring) return item;
565 }
566
567 let data = await lib.makeAPIRequest({
568 env,
569 path: `/${this.endpoint}/${id}`,
570 qs
571 });
572
573 if (data.data) {
574 let o = new this({
575 data: data.data,
576 remote: env,
577 included: data.included
578 });
579 this.cache.push(o);
580 return o;
581 }
582 }
583
584 static async getByName(env, name, qs) {
585 this.handleCaching();
586
587 for (let item of this.cache) {
588 if (item.name === name && item.remote === env) return item;
589 }
590
591 let data = await lib.makeAPIRequest({
592 env,
593 path: `/${this.endpoint}`,
594 qs: { ...qs,
595 filter: `name=${name}` + (qs ? qs.filter : "")
596 }
597 }); //TODO included might not wokr correctly here
598
599 if (data.data[0]) {
600 let o = new this({
601 data: data.data[0],
602 remote: env,
603 included: data.included
604 });
605 this.cache.push(o);
606 return o;
607 }
608 }
609
610 static async getAllPreCollect(d) {
611 return d;
612 }
613
614 static async getAll(env) {
615 this.handleCaching();
616 let datas = await lib.indexPathFast({
617 env,
618 path: `/${this.endpoint}`,
619 pageSize: "50",
620 qs: {
621 sort: "id"
622 }
623 });
624 datas = await this.getAllPreCollect(datas);
625 let all = new Collection(datas.map(data => new this({
626 data,
627 remote: env
628 })));
629 this.cache = [...this.cache, ...all.arr];
630 return all;
631 }
632
633 static async removeCache(env) {
634 this.handleCaching();
635 this.cache = this.cache.filter(x => x.remote !== env);
636 } //Specific turns name into id based on env
637 //Generic turns ids into names
638
639
640 async resolveApply(type, dataObj, direction) {
641 let obj;
642
643 if (direction == "generic") {
644 obj = await type.getById(this.remote, dataObj.id);
645
646 if (obj) {
647 dataObj.name = obj.name;
648 }
649 } else if (direction == "specific") {
650 obj = await type.getByName(this.remote, dataObj.name);
651
652 if (obj) {
653 dataObj.id = obj.id;
654 }
655 }
656
657 return obj;
658 } //Type is the baseclass you are looking for (should extend RallyBase)
659 //name is the name of the field
660 //isArray is true if it has multiple cardinailty, false if it is single
661 //direction gets passed directly to resolveApply
662
663
664 async resolveField(type, name, isArray = false, direction = "generic") {
665 // ignore empty fields
666 let field = this.relationships[name];
667 if (!(field === null || field === void 0 ? void 0 : field.data)) return;
668
669 if (isArray) {
670 return await Promise.all(field.data.map(o => this.resolveApply(type, o, direction)));
671 } else {
672 return await this.resolveApply(type, field.data, direction);
673 }
674 }
675
676 cleanup() {
677 for (let [key, val] of Object.entries(this.relationships)) {
678 //Remove ids from data
679 if (val.data) {
680 if (val.data.id) {
681 delete val.data.id;
682 } else if (val.data[0]) {
683 for (let x of val.data) delete x.id;
684 }
685 }
686
687 delete val.links;
688 } // organization is unused (?)
689
690
691 delete this.relationships.organization; // id is specific to envs
692 // but save source inside meta string in case we need it
693
694 this.metastring = this.remote + "-" + this.data.id;
695 delete this.data.id; // links too
696
697 delete this.data.links;
698 }
699
700 }
701 function sleep(time = 1000) {
702 return new Promise(resolve => setTimeout(resolve, time));
703 }
704
705 const inquirer = importLazy("inquirer");
706 const readdir = importLazy("recursive-readdir");
707 async function loadLocals(path$1, Class) {
708 let basePath = exports.configObject.repodir;
709 let objs = (await readdir(basePath)).filter(name => name.includes(path$1)).filter(name => !path.basename(name).startsWith(".")).map(name => new Class({
710 path: name
711 }));
712 return objs;
713 }
714
715 class Provider extends RallyBase {
716 constructor({
717 data,
718 remote
719 }) {
720 super();
721 this.data = data;
722 this.meta = {};
723 this.remote = remote;
724 } //cached
725
726
727 async getEditorConfig() {
728 if (this.editorConfig) return this.editorConfig;
729 this.editorConfig = await lib.makeAPIRequest({
730 env: this.remote,
731 path_full: this.data.links.editorConfig
732 });
733 this.editorConfig.fileExt = await this.getFileExtension();
734 return this.editorConfig;
735 }
736
737 static async getAllPreCollect(providers) {
738 return providers.sort((a, b) => {
739 return a.attributes.category.localeCompare(b.attributes.category) || a.attributes.name.localeCompare(b.attributes.name);
740 });
741 }
742
743 async getFileExtension() {
744 let config = await this.getEditorConfig();
745 let map = {
746 python: "py",
747 text: "txt",
748
749 getmap(key) {
750 if (this.name === "Aurora") return "zip";
751 if (this[key]) return this[key];
752 return key;
753 }
754
755 };
756 return map.getmap(config.lang);
757 }
758
759 chalkPrint(pad = true) {
760 let id = String(this.id);
761 if (pad) id = id.padStart(4);
762 return chalk`{green ${id}}: {blue ${this.category}} - {green ${this.name}}`;
763 }
764
765 }
766
767 defineAssoc(Provider, "id", "data.id");
768 defineAssoc(Provider, "name", "data.attributes.name");
769 defineAssoc(Provider, "category", "data.attributes.category");
770 defineAssoc(Provider, "remote", "meta.remote");
771 defineAssoc(Provider, "editorConfig", "meta.editorConfig");
772 Provider.endpoint = "providerTypes";
773
774 class File extends RallyBase {
775 constructor({
776 data,
777 remote,
778 included,
779 parent
780 }) {
781 super();
782 this.data = data;
783 this.meta = {};
784 this.remote = remote;
785 this.parentAsset = parent;
786 }
787
788 chalkPrint(pad = false) {
789 let id = String("F-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
790 if (pad) id = id.padStart(15);
791 return chalk`{green ${id}}: {blue ${this.data.attributes ? this.name : "(lite file)"}} {red ${this.sizeHR}}`;
792 }
793
794 canBeDownloaded() {
795 return this.sizeGB <= .2;
796 }
797
798 async getContent(force = false) {
799 if (!this.canBeDownloaded() && !force) {
800 throw new FileTooLargeError(this);
801 }
802
803 return lib.makeAPIRequest({
804 env: this.remote,
805 fullPath: this.contentLink
806 });
807 }
808
809 async delete(remove = true) {
810 return lib.makeAPIRequest({
811 env: this.remote,
812 fullPath: this.selfLink,
813 method: "DELETE"
814 });
815 }
816
817 get size() {
818 return Object.values(this.data.attributes.instances)[0].size;
819 }
820
821 get sizeGB() {
822 return Math.round(this.size / 1024 / 1024 / 1024 * 10) / 10;
823 }
824
825 get sizeHR() {
826 let units = ["B", "K", "M", "G", "T"];
827 let unitIdx = 0;
828 let size = this.size;
829
830 while (size > 1000) {
831 size /= 1024;
832 unitIdx++;
833 }
834
835 if (size > 100) {
836 size = Math.round(size);
837 } else {
838 size = Math.round(size * 10) / 10;
839 }
840
841 return size + units[unitIdx];
842 }
843
844 get instancesList() {
845 let instances = [];
846
847 for (let [key, val] of Object.entries(this.instances)) {
848 let n = {
849 id: key
850 };
851 Object.assign(n, val);
852 instances.push(n);
853 }
854
855 return instances;
856 }
857
858 static rslURL(instance) {
859 return `rsl://${instance.storageLocationName}/${instance.name}`;
860 }
861
862 }
863
864 defineAssoc(File, "id", "data.id");
865 defineAssoc(File, "name", "data.attributes.label");
866 defineAssoc(File, "contentLink", "data.links.content");
867 defineAssoc(File, "selfLink", "data.links.self");
868 defineAssoc(File, "label", "data.attributes.label");
869 defineAssoc(File, "md5", "data.attributes.md5");
870 defineAssoc(File, "sha512", "data.attributes.sha512");
871 defineAssoc(File, "tags", "data.attributes.tagList");
872 defineAssoc(File, "instances", "data.attributes.instances");
873 File.endpoint = null;
874
875 class Asset extends RallyBase {
876 constructor({
877 data,
878 remote,
879 included,
880 lite
881 }) {
882 super();
883 this.data = data;
884 this.meta = {};
885 this.remote = remote;
886
887 if (included) {
888 this.meta.metadata = Asset.normalizeMetadata(included);
889 }
890
891 this.lite = !!lite;
892 }
893
894 static normalizeMetadata(payload) {
895 let newMetadata = {};
896
897 for (let md of payload) {
898 if (md.type !== "metadata") continue;
899 newMetadata[md.attributes.usage] = md.attributes.metadata;
900 }
901
902 return newMetadata;
903 }
904
905 async getMetadata(forceRefresh = false) {
906 if (this.meta.metadata && !forceRefresh) return this.meta.metadata;
907 let req = await lib.makeAPIRequest({
908 env: this.remote,
909 path: `/movies/${this.id}/metadata`
910 });
911 return this.meta.metadata = Asset.normalizeMetadata(req.data);
912 }
913
914 async patchMetadata(metadata) {
915 if (metadata.Workflow) {
916 //FIXME
917 //Currently, WORKFLOW_METADATA cannot be patched via api: we need to
918 //start a ephemeral eval to upload it
919 let md = JSON.stringify(JSON.stringify(metadata.Workflow));
920 let fakePreset = {
921 code: `WORKFLOW_METADATA.update(json.loads(${md}))`
922 };
923 await this.startEphemeralEvaluateIdeal(fakePreset);
924 log("WFMD Patched using ephemeralEval");
925 }
926
927 if (metadata.Metadata) {
928 let req = await lib.makeAPIRequest({
929 env: this.remote,
930 path: `/movies/${this.id}/metadata/Metadata`,
931 method: "PATCH",
932 payload: {
933 "data": {
934 "type": "metadata",
935 "attributes": {
936 "metadata": metadata.Metadata
937 }
938 }
939 }
940 });
941 log("MD Patched");
942 }
943 }
944
945 static lite(id, remote) {
946 return new this({
947 data: {
948 id
949 },
950 remote,
951 lite: true
952 });
953 }
954
955 chalkPrint(pad = false) {
956 let id = String("A-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
957 if (pad) id = id.padStart(15);
958 return chalk`{green ${id}}: {blue ${this.data.attributes ? this.name : "(lite asset)"}}`;
959 }
960
961 static async createNew(name, env) {
962 let req = await lib.makeAPIRequest({
963 env,
964 path: "/assets",
965 method: "POST",
966 payload: {
967 data: {
968 attributes: {
969 name
970 },
971 type: "assets"
972 }
973 }
974 });
975 return new this({
976 data: req.data,
977 remote: env
978 });
979 }
980
981 async delete() {
982 let req = await lib.makeAPIRequest({
983 env: this.remote,
984 path: "/assets/" + this.id,
985 method: "DELETE"
986 });
987 }
988
989 async getFiles() {
990 let req = await lib.indexPathFast({
991 env: this.remote,
992 path: `/assets/${this.id}/files`,
993 method: "GET"
994 }); //return req;
995
996 return new Collection(req.map(x => new File({
997 data: x,
998 remote: this.remote,
999 parent: this
1000 })));
1001 }
1002
1003 async addFile(label, fileuris) {
1004 if (!Array.isArray(fileuris)) fileuris = [fileuris];
1005 let instances = {};
1006
1007 for (let i = 0; i < fileuris.length; i++) {
1008 instances[String(i + 1)] = {
1009 uri: fileuris[i]
1010 };
1011 }
1012
1013 let req = await lib.makeAPIRequest({
1014 env: this.remote,
1015 path: "/files",
1016 method: "POST",
1017 payload: {
1018 "data": {
1019 "attributes": {
1020 label,
1021 instances
1022 },
1023 "relationships": {
1024 "asset": {
1025 "data": {
1026 id: this.id,
1027 "type": "assets"
1028 }
1029 }
1030 },
1031 "type": "files"
1032 }
1033 }
1034 });
1035 return req;
1036 }
1037
1038 async startWorkflow(jobName, {
1039 initData,
1040 priority
1041 } = {}) {
1042 let attributes = {};
1043
1044 if (initData) {
1045 //Convert init data to string
1046 initData = typeof initData === "string" ? initData : JSON.stringify(initData);
1047 attributes.initData = initData;
1048 }
1049
1050 if (priority) {
1051 attributes.priority = priority;
1052 }
1053
1054 let req = await lib.makeAPIRequest({
1055 env: this.remote,
1056 path: "/workflows",
1057 method: "POST",
1058 payload: {
1059 "data": {
1060 "type": "workflows",
1061 attributes,
1062 "relationships": {
1063 "movie": {
1064 "data": {
1065 id: this.id,
1066 "type": "movies"
1067 }
1068 },
1069 "rule": {
1070 "data": {
1071 "attributes": {
1072 "name": jobName
1073 },
1074 "type": "rules"
1075 }
1076 }
1077 }
1078 }
1079 }
1080 });
1081 return req;
1082 }
1083
1084 static async startAnonWorkflow(env, jobName, {
1085 initData,
1086 priority
1087 } = {}) {
1088 let attributes = {};
1089
1090 if (initData) {
1091 //Convert init data to string
1092 initData = typeof initData === "string" ? initData : JSON.stringify(initData);
1093 attributes.initData = initData;
1094 }
1095
1096 if (priority) {
1097 attributes.priority = priority;
1098 }
1099
1100 let req = await lib.makeAPIRequest({
1101 env,
1102 path: "/workflows",
1103 method: "POST",
1104 payload: {
1105 "data": {
1106 "type": "workflows",
1107 attributes,
1108 "relationships": {
1109 "rule": {
1110 "data": {
1111 "attributes": {
1112 "name": jobName
1113 },
1114 "type": "rules"
1115 }
1116 }
1117 }
1118 }
1119 }
1120 });
1121 return req;
1122 }
1123
1124 async startEphemeralEvaluateIdeal(preset, dynamicPresetData, isBinary = false) {
1125 let res;
1126 const env = this.remote;
1127 let provider = await Provider.getByName(this.remote, "SdviEvaluate");
1128 write(chalk`Starting ephemeral evaluate on ${this.chalkPrint(false)}...`); // Fire and forget.
1129
1130 let evalInfo = await lib.makeAPIRequest({
1131 env: this.remote,
1132 path: "/jobs",
1133 method: "POST",
1134 payload: {
1135 data: {
1136 attributes: {
1137 category: provider.category,
1138 providerTypeName: provider.name,
1139 rallyConfiguration: {},
1140 //we need to strip invalid utf8 characters from the
1141 //buffer before we encode it or the sdvi backend dies
1142 providerData: Buffer.from(preset.code, isBinary && "binary" || "utf8").toString("base64"),
1143 dynamicPresetData
1144 },
1145 type: "jobs",
1146 relationships: {
1147 movie: {
1148 data: {
1149 id: this.id,
1150 type: "movies"
1151 }
1152 }
1153 }
1154 }
1155 }
1156 });
1157 write(" Waiting for finish...");
1158
1159 for (;;) {
1160 res = await lib.makeAPIRequest({
1161 env,
1162 path_full: evalInfo.data.links.self
1163 });
1164 write(".");
1165
1166 if (res.data.attributes.state == "Complete") {
1167 write(chalk`{green Done}...\n`);
1168 break;
1169 }
1170
1171 await sleep(300);
1172 }
1173
1174 return;
1175 }
1176
1177 async startEvaluate(presetid, dynamicPresetData) {
1178 // Fire and forget.
1179 let data = await lib.makeAPIRequest({
1180 env: this.remote,
1181 path: "/jobs",
1182 method: "POST",
1183 payload: {
1184 data: {
1185 type: "jobs",
1186 attributes: {
1187 dynamicPresetData
1188 },
1189 relationships: {
1190 movie: {
1191 data: {
1192 id: this.id,
1193 type: "movies"
1194 }
1195 },
1196 preset: {
1197 data: {
1198 id: presetid,
1199 type: "presets"
1200 }
1201 }
1202 }
1203 }
1204 }
1205 });
1206 return data;
1207 }
1208
1209 async rename(newName) {
1210 let req = await lib.makeAPIRequest({
1211 env: this.remote,
1212 path: `/assets/${this.id}`,
1213 method: "PATCH",
1214 payload: {
1215 data: {
1216 attributes: {
1217 name: newName
1218 },
1219 type: "assets"
1220 }
1221 }
1222 });
1223 this.name = newName;
1224 return req;
1225 }
1226
1227 async migrate(targetEnv) {
1228 exports.configObject.globalProgress = false;
1229 log(`Creating paired file in ${targetEnv}`); //Fetch metadata in parallel, we await it later
1230
1231 let _mdPromise = this.getMetadata();
1232
1233 let targetAsset = await Asset.getByName(targetEnv, this.name);
1234
1235 if (targetAsset) {
1236 log(`Asset already exists ${targetAsset.chalkPrint()}`); //if(configObject.script) process.exit(10);
1237 } else {
1238 targetAsset = await Asset.createNew(this.name, targetEnv);
1239 log(`Asset created ${targetAsset.chalkPrint()}`);
1240 } //wait for metadata to be ready before patching
1241
1242
1243 await _mdPromise;
1244 log("Adding asset metadata");
1245 await targetAsset.patchMetadata(this.md);
1246 let fileCreations = [];
1247
1248 for (let file of await this.getFiles()) {
1249 //Check for any valid copy-able instances
1250 for (let inst of file.instancesList) {
1251 //We need to skip internal files
1252 if (inst.storageLocationName === "Rally Platform Bucket") continue;
1253 log(`Adding file: ${file.chalkPrint()}`);
1254 fileCreations.push(targetAsset.addFileInstance(file, inst));
1255 }
1256 }
1257
1258 await Promise.all(fileCreations);
1259 }
1260
1261 async addFileInstance(file, inst, tagList = []) {
1262 let newInst = {
1263 uri: File.rslURL(inst),
1264 name: inst.name,
1265 size: inst.size,
1266 lastModified: inst.lastModified,
1267 storageLocationName: inst.storageLocationName
1268 };
1269 let request = lib.makeAPIRequest({
1270 env: this.remote,
1271 path: `/files`,
1272 method: "POST",
1273 payload: {
1274 data: {
1275 type: "files",
1276 attributes: {
1277 label: file.label,
1278 tagList,
1279 instances: {
1280 "1": newInst
1281 }
1282 },
1283 relationships: {
1284 asset: {
1285 data: {
1286 id: this.id,
1287 type: "assets"
1288 }
1289 }
1290 }
1291 }
1292 }
1293 });
1294
1295 try {
1296 let fileData = await request;
1297 let newFile = new File({
1298 data: fileData.data,
1299 remote: this.remote,
1300 parent: this
1301 });
1302 if (exports.configObject.script) console.log(inst.uri, newFile.instancesList[0].uri);
1303 } catch (e) {
1304 log(chalk`{red Failed file: ${file.chalkPrint()}}`);
1305 }
1306 }
1307
1308 }
1309
1310 defineAssoc(Asset, "id", "data.id");
1311 defineAssoc(Asset, "name", "data.attributes.name");
1312 defineAssoc(Asset, "remote", "meta.remote");
1313 defineAssoc(Asset, "md", "meta.metadata");
1314 defineAssoc(Asset, "lite", "meta.lite");
1315 Asset.endpoint = "movies";
1316
1317 let home;
1318
1319 if (os.homedir) {
1320 home = os.homedir();
1321 }
1322
1323 const colon = /:/g;
1324 const siloLike = /(silo\-\w+?)s?\/([^\/]+)\.([\w1234567890]+)$/g;
1325 function pathTransform(path) {
1326 if (path.includes(":")) {
1327 //Ignore the first colon in window-like filesystems
1328 path = path.slice(0, 3) + path.slice(3).replace(colon, "--");
1329 }
1330
1331 if (exports.configObject.invertedPath) {
1332 path = path.replace(siloLike, "$2-$1.$3");
1333 }
1334
1335 if (path.includes("\\342\\200\\220")) {
1336 path = path.replace("\\342\\200\\220", "‐");
1337 }
1338
1339 return path;
1340 }
1341 function readFileSync(path, options) {
1342 return fs__default.readFileSync(pathTransform(path), options);
1343 } //Create writefilesync, with ability to create directory if it doesnt exist
1344
1345 function writeFileSync(path$1, data, options, dircreated = false) {
1346 path$1 = pathTransform(path$1);
1347
1348 try {
1349 return fs__default.writeFileSync(path$1, data, options);
1350 } catch (e) {
1351 if (dircreated) throw e;
1352 let directory = path.dirname(path$1);
1353
1354 try {
1355 fs__default.statSync(directory);
1356 throw e;
1357 } catch (nodir) {
1358 fs__default.mkdirSync(directory);
1359 return writeFileSync(path$1, data, options, true);
1360 }
1361 }
1362 }
1363
1364 let exists = {};
1365
1366 class Preset extends RallyBase {
1367 constructor({
1368 path: path$1,
1369 remote,
1370 data,
1371 subProject
1372 } = {}) {
1373 // Get full path if possible
1374 if (path$1) {
1375 path$1 = path.resolve(path$1);
1376
1377 if (path.dirname(path$1).includes("silo-metadata")) {
1378 throw new AbortError("Constructing preset from metadata file");
1379 }
1380 }
1381
1382 super(); // Cache by path
1383
1384 if (path$1) {
1385 if (exists[pathTransform(path$1)]) return exists[pathTransform(path$1)];
1386 exists[pathTransform(path$1)] = this;
1387 }
1388
1389 this.meta = {};
1390 this.subproject = subProject;
1391 this.remote = remote;
1392
1393 if (lib.isLocalEnv(this.remote)) {
1394 if (path$1) {
1395 this.path = path$1;
1396 let pathspl = this.path.split(".");
1397 this.ext = pathspl[pathspl.length - 1];
1398
1399 try {
1400 this.code = this.getLocalCode();
1401 } catch (e) {
1402 if (e.code === "ENOENT" && exports.configObject.ignoreMissing) {
1403 this.missing = true;
1404 return undefined;
1405 } else {
1406 log(chalk`{red Node Error} ${e.message}`);
1407 throw new AbortError("Could not load code of local file");
1408 }
1409 }
1410
1411 let name = this.parseFilenameForName() || this.parseCodeForName();
1412
1413 try {
1414 this.data = this.getLocalMetadata();
1415 this.isGeneric = true;
1416 name = this.name;
1417 } catch (e) {
1418 log(chalk`{yellow Warning}: ${path$1} does not have a readable metadata file! Looking for ${this.localmetadatapath}`);
1419 this.data = Preset.newShell(name);
1420 this.isGeneric = false;
1421 }
1422
1423 this.name = name;
1424 } else {
1425 this.data = Preset.newShell();
1426 }
1427 } else {
1428 this.data = data; //this.name = data.attributes.name;
1429 //this.id = data.id;
1430
1431 this.isGeneric = false;
1432 }
1433
1434 this.data.attributes.rallyConfiguration = undefined;
1435 this.data.attributes.systemManaged = undefined;
1436 } //Given a metadata file, get its actualy file
1437
1438
1439 static async fromMetadata(path, subproject) {
1440 let data;
1441
1442 try {
1443 data = JSON.parse(readFileSync(path));
1444 } catch (e) {
1445 if (e.code === "ENOENT" && exports.configObject.ignoreMissing) {
1446 return null;
1447 } else {
1448 throw e;
1449 }
1450 }
1451
1452 let providerType = data.relationships.providerType.data.name;
1453 let provider = await Provider.getByName("DEV", providerType);
1454
1455 if (!provider) {
1456 log(chalk`{red The provider type {green ${providerType}} does not exist}`);
1457 log(chalk`{red Skipping {green ${path}}.}`);
1458 return null;
1459 }
1460
1461 let ext = await provider.getFileExtension();
1462 let name = data.attributes.name;
1463 let realpath = Preset.getLocalPath(name, ext, subproject);
1464 return new Preset({
1465 path: realpath,
1466 subProject: subproject
1467 });
1468 }
1469
1470 static newShell(name = undefined) {
1471 return {
1472 "attributes": {
1473 "providerSettings": {
1474 "PresetName": name
1475 }
1476 },
1477 "relationships": {},
1478 "type": "presets"
1479 };
1480 }
1481
1482 cleanup() {
1483 super.cleanup();
1484 delete this.attributes["createdAt"];
1485 delete this.attributes["updatedAt"];
1486 }
1487
1488 async acclimatize(env) {
1489 if (!this.isGeneric) throw new AbortError("Cannot acclimatize non-generics or shells");
1490 let providers = await Provider.getAll(env);
1491 let ptype = this.relationships["providerType"];
1492 ptype = ptype.data;
1493 let provider = providers.findByName(ptype.name);
1494 ptype.id = provider.id;
1495 }
1496
1497 get test() {
1498 if (!this.code) return [];
1499 const regex = /[^-]autotest:\s?([\w\d_\-. \/]+)[\r\s\n]*?/gm;
1500 let match;
1501 let matches = [];
1502
1503 while (match = regex.exec(this.code)) {
1504 matches.push(match[1]);
1505 }
1506
1507 return matches;
1508 }
1509
1510 async runTest(env) {
1511 let remote = await Preset.getByName(env, this.name);
1512
1513 for (let test of this.test) {
1514 log("Tests...");
1515 let asset;
1516
1517 if (test.startsWith("id")) {
1518 let match = /id:\s*(\d+)/g.exec(test);
1519
1520 if (!match) {
1521 log(chalk`{red Could not parse autotest} ${test}.`);
1522 throw new AbortError("Could not properly parse the preset header");
1523 }
1524
1525 asset = await Asset.getById(env, match[1]);
1526 } else {
1527 asset = await Asset.getByName(env, test);
1528 }
1529
1530 if (!asset) {
1531 log(chalk`{yellow No movie found}, skipping test.`);
1532 continue;
1533 }
1534
1535 log(chalk`Starting job {green ${this.name}} on ${asset.chalkPrint(false)}... `);
1536 await asset.startEvaluate(remote.id);
1537 }
1538 }
1539
1540 async resolve() {
1541 if (this.isGeneric) return;
1542 let proType = await this.resolveField(Provider, "providerType");
1543 this.ext = await proType.getFileExtension();
1544 this.isGeneric = true;
1545 return {
1546 proType
1547 };
1548 }
1549
1550 async saveLocal() {
1551 await this.saveLocalMetadata();
1552 await this.saveLocalFile();
1553 }
1554
1555 async saveLocalMetadata() {
1556 if (!this.isGeneric) {
1557 await this.resolve();
1558 this.cleanup();
1559 }
1560
1561 writeFileSync(this.localmetadatapath, JSON.stringify(this.data, null, 4));
1562 }
1563
1564 async saveLocalFile() {
1565 writeFileSync(this.localpath, this.code);
1566 }
1567
1568 async uploadRemote(env) {
1569 await this.uploadCodeToEnv(env, true);
1570 }
1571
1572 async save(env) {
1573 this.saved = true;
1574
1575 if (!this.isGeneric) {
1576 await this.resolve();
1577 }
1578
1579 this.cleanup();
1580
1581 if (lib.isLocalEnv(env)) {
1582 log(chalk`Saving preset {green ${this.name}} to {blue ${lib.envName(env)}}.`);
1583 await this.saveLocal();
1584 } else {
1585 await this.uploadRemote(env);
1586 }
1587 }
1588
1589 async downloadCode() {
1590 if (!this.remote || this.code) return this.code;
1591 let code = await lib.makeAPIRequest({
1592 env: this.remote,
1593 path_full: this.data.links.providerData,
1594 json: false
1595 }); //match header like
1596 // # c: d
1597 // # b
1598 // # a
1599 // ##################
1600
1601 let headerRegex = /(^# .+[\r\n]+)+#+[\r\n]+/gim;
1602 let hasHeader = headerRegex.exec(code);
1603
1604 if (hasHeader) {
1605 this.header = code.substring(0, hasHeader[0].length - 1);
1606 code = code.substring(hasHeader[0].length);
1607 }
1608
1609 return this.code = code;
1610 }
1611
1612 get code() {
1613 if (this._code) return this._code;
1614 }
1615
1616 set code(v) {
1617 this._code = v;
1618 }
1619
1620 chalkPrint(pad = true) {
1621 let id = String("P-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
1622 let sub = "";
1623
1624 if (this.subproject) {
1625 sub = chalk`{yellow ${this.subproject}}`;
1626 }
1627
1628 if (pad) id = id.padStart(10);
1629
1630 if (this.name == undefined) {
1631 return chalk`{green ${id}}: ${sub}{red ${this.path}}`;
1632 } else if (this.meta.proType) {
1633 return chalk`{green ${id}}: ${sub}{red ${this.meta.proType.name}} {blue ${this.name}}`;
1634 } else {
1635 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
1636 }
1637 }
1638
1639 parseFilenameForName() {
1640 if (this.path.endsWith(".jinja") || this.path.endsWith(".json")) {
1641 return path.basename(this.path).replace("_", " ").replace("-", " ").replace(".json", "").replace(".jinja", "");
1642 }
1643 }
1644
1645 parseCodeForName() {
1646 const name_regex = /name\s?:\s([\w\d. \/]+)[\r\s\n]*?/;
1647 const match = name_regex.exec(this.code);
1648 if (match) return match[1];
1649 }
1650
1651 findStringsInCode(strings) {
1652 if (!this.code) return [];
1653 return strings.filter(str => {
1654 let regex = new RegExp(str);
1655 return !!this.code.match(regex);
1656 });
1657 }
1658
1659 static getLocalPath(name, ext, subproject) {
1660 return path__default.join(exports.configObject.repodir, subproject || "", "silo-presets", name + "." + ext);
1661 }
1662
1663 get localpath() {
1664 return Preset.getLocalPath(this.name, this.ext, this.subproject);
1665 }
1666
1667 get path() {
1668 if (this._path) return this._path;
1669 }
1670
1671 set path(val) {
1672 this._path = val;
1673 }
1674
1675 get name() {
1676 return this._nameOuter;
1677 }
1678
1679 set name(val) {
1680 if (!this._nameInner) this._nameInner = val;
1681 this._nameOuter = val;
1682 }
1683
1684 set providerType(value) {
1685 this.relationships["providerType"] = {
1686 data: { ...value,
1687 type: "providerTypes"
1688 }
1689 };
1690 }
1691
1692 get localmetadatapath() {
1693 if (this.path) {
1694 return this.path.replace("silo-presets", "silo-metadata").replace(new RegExp(this.ext + "$"), "json");
1695 }
1696
1697 return path__default.join(exports.configObject.repodir, this.subproject || "", "silo-metadata", this.name + ".json");
1698 }
1699
1700 get immutable() {
1701 return this.name.includes("Constant") && !exports.configObject.updateImmutable;
1702 }
1703
1704 async uploadPresetData(env, id) {
1705 var _this$relationships, _this$relationships$p, _this$relationships$p2;
1706
1707 if (this.code.trim() === "NOUPLOAD") {
1708 write(chalk`code skipped {yellow :)}, `);
1709 return;
1710 }
1711
1712 let code = this.code;
1713 let headers = {};
1714 let providerName = (_this$relationships = this.relationships) === null || _this$relationships === void 0 ? void 0 : (_this$relationships$p = _this$relationships.providerType) === null || _this$relationships$p === void 0 ? void 0 : (_this$relationships$p2 = _this$relationships$p.data) === null || _this$relationships$p2 === void 0 ? void 0 : _this$relationships$p2.name;
1715
1716 if (!exports.configObject.skipHeader && (providerName === "SdviEvaluate" || providerName === "SdviEvalPro")) {
1717 write(chalk`generate header, `);
1718 let repodir = exports.configObject.repodir;
1719 let localpath = this.path.replace(repodir, "");
1720 if (localpath.startsWith("/")) localpath = localpath.substring(1);
1721
1722 try {
1723 let {
1724 stdout: headerText
1725 } = await spawn({
1726 noecho: true
1727 }, "sh", [path__default.join(exports.configObject.repodir, `bin/header.sh`), moment(Date.now()).format("ddd YYYY/MM/DD hh:mm:ssa"), localpath]);
1728 code = headerText + code;
1729 write(chalk`header ok, `);
1730 } catch (e) {
1731 write(chalk`missing unix, `);
1732 }
1733 } //binary presets
1734
1735
1736 if (providerName == "Vantage") {
1737 code = code.toString("base64");
1738 headers["Content-Transfer-Encoding"] = "base64";
1739 }
1740
1741 let res = await lib.makeAPIRequest({
1742 env,
1743 path: `/presets/${id}/providerData`,
1744 body: code,
1745 method: "PUT",
1746 fullResponse: true,
1747 timeout: 10000,
1748 headers
1749 });
1750 write(chalk`code up {yellow ${res.statusCode}}, `);
1751 }
1752
1753 async grabMetadata(env) {
1754 let remote = await Preset.getByName(env, this.name);
1755 this.isGeneric = false;
1756
1757 if (!remote) {
1758 throw new AbortError(`No file found on remote ${env} with name ${this.name}`);
1759 }
1760
1761 this.data = remote.data;
1762 this.remote = env;
1763 }
1764
1765 async deleteRemoteVersion(env, id = null) {
1766 if (lib.isLocalEnv(env)) return false;
1767
1768 if (!id) {
1769 let remote = await Preset.getByName(env, this.name);
1770 id = remote.id;
1771 }
1772
1773 return await lib.makeAPIRequest({
1774 env,
1775 path: `/presets/${id}`,
1776 method: "DELETE"
1777 });
1778 }
1779
1780 async delete() {
1781 if (lib.isLocalEnv(this.remote)) return false;
1782 return await this.deleteRemoteVersion(this.remote, this.id);
1783 }
1784
1785 async uploadCodeToEnv(env, includeMetadata, shouldTest = true) {
1786 if (!this.name) {
1787 let match;
1788
1789 if (match = /^(#|["']{3})\s*EPH (\d+)/.exec(this.code.trim())) {
1790 let a = await Asset.getById(env, Number(match[2]));
1791 return a.startEphemeralEvaluateIdeal(this);
1792 } else {
1793 log(chalk`Failed uploading {red ${this.path}}. No name found.`);
1794 return;
1795 }
1796 }
1797
1798 write(chalk`Uploading preset {green ${this.name}} to {green ${env}}: `);
1799
1800 if (this.immutable) {
1801 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
1802 return;
1803 } //First query the api to see if this already exists.
1804
1805
1806 let remote = await Preset.getByName(env, this.name);
1807
1808 if (remote) {
1809 //If it exists we can replace it
1810 write("replace, ");
1811
1812 if (includeMetadata) {
1813 let payload = {
1814 data: {
1815 attributes: this.data.attributes,
1816 type: "presets"
1817 }
1818 };
1819
1820 if (this.relationships.tagNames) {
1821 payload.relationships = {
1822 tagNames: this.relationships.tagNames
1823 };
1824 }
1825
1826 let res = await lib.makeAPIRequest({
1827 env,
1828 path: `/presets/${remote.id}`,
1829 method: "PATCH",
1830 payload,
1831 fullResponse: true
1832 });
1833 write(chalk`metadata {yellow ${res.statusCode}}, `);
1834
1835 if (res.statusCode == 500) {
1836 log(chalk`skipping code upload, did not successfully upload metadata`);
1837 return;
1838 }
1839 }
1840
1841 await this.uploadPresetData(env, remote.id);
1842 } else {
1843 write("create, ");
1844 let metadata = {
1845 data: this.data
1846 };
1847
1848 if (!this.relationships["providerType"]) {
1849 throw new AbortError("Cannot acclimatize shelled presets. (try creating it on the env first)");
1850 }
1851
1852 await this.acclimatize(env);
1853 write("Posting to create preset... ");
1854 let res = await lib.makeAPIRequest({
1855 env,
1856 path: `/presets`,
1857 method: "POST",
1858 payload: metadata,
1859 timeout: 5000
1860 });
1861 let id = res.data.id;
1862 write(chalk`Created id {green ${id}}... Uploading Code... `);
1863 await this.uploadPresetData(env, id);
1864 }
1865
1866 if (this.test[0] && shouldTest) {
1867 await this.runTest(env);
1868 } else {
1869 log("No tests. Done.");
1870 }
1871 }
1872
1873 getLocalMetadata() {
1874 return JSON.parse(readFileSync(this.localmetadatapath, "utf-8"));
1875 }
1876
1877 getLocalCode() {
1878 //todo fixup for binary presets, see uploadPresetData
1879 return readFileSync(this.path, "utf-8");
1880 }
1881
1882 parseHeaderInfo() {
1883 var _$exec$, _$exec$2, _$exec$3, _$exec$4, _$exec$5, _$exec$6, _$exec$7;
1884
1885 if (!this.header) return null;
1886 let abs = {
1887 built: (_$exec$ = /Built On:(.+)/.exec(this.header)[1]) === null || _$exec$ === void 0 ? void 0 : _$exec$.trim(),
1888 author: (_$exec$2 = /Author:(.+)/.exec(this.header)[1]) === null || _$exec$2 === void 0 ? void 0 : _$exec$2.trim(),
1889 build: (_$exec$3 = /Build:(.+)/.exec(this.header)[1]) === null || _$exec$3 === void 0 ? void 0 : _$exec$3.trim(),
1890 version: (_$exec$4 = /Version:(.+)/.exec(this.header)[1]) === null || _$exec$4 === void 0 ? void 0 : _$exec$4.trim(),
1891 branch: (_$exec$5 = /Branch:(.+)/.exec(this.header)[1]) === null || _$exec$5 === void 0 ? void 0 : _$exec$5.trim(),
1892 commit: (_$exec$6 = /Commit:(.+)/.exec(this.header)[1]) === null || _$exec$6 === void 0 ? void 0 : _$exec$6.trim(),
1893 local: (_$exec$7 = /Local File:(.+)/.exec(this.header)[1]) === null || _$exec$7 === void 0 ? void 0 : _$exec$7.trim()
1894 };
1895 let tryFormats = [[true, "ddd MMM DD HH:mm:ss YYYY"], [false, "ddd YYYY/MM/DD LTS"]];
1896
1897 for (let [isUTC, format] of tryFormats) {
1898 let date;
1899
1900 if (isUTC) {
1901 date = moment.utc(abs.built, format);
1902 } else {
1903 date = moment(abs.built, format);
1904 }
1905
1906 if (!date.isValid()) continue;
1907 abs.offset = date.fromNow();
1908 break;
1909 }
1910
1911 return abs;
1912 }
1913
1914 async printRemoteInfo(env) {
1915 let remote = await Preset.getByName(env, this.name);
1916 await remote.downloadCode();
1917 let i = remote.parseHeaderInfo();
1918
1919 if (i) {
1920 log(chalk`
1921 ENV: {red ${env}}, updated {yellow ~${i.offset}}
1922 Built on {blue ${i.built}} by {green ${i.author}}
1923 From ${i.build || "(unknown)"} on ${i.branch} ({yellow ${i.commit}})
1924 `.replace(/^[ \t]+/gim, "").trim());
1925 } else {
1926 log(chalk`No header on {red ${env}}`);
1927 }
1928 }
1929
1930 async getInfo(envs) {
1931 await this.printDepends();
1932
1933 for (let env of envs.split(",")) {
1934 await this.printRemoteInfo(env);
1935 }
1936 }
1937
1938 async printDepends(indent = 0, locals = null, seen = {}) {
1939 let includeRegex = /@include "(.+)"/gim; //let includeRegex = /@include/g;
1940
1941 let includes = [];
1942 let inc;
1943
1944 while (inc = includeRegex.exec(this.code)) {
1945 includes.push(inc[1]);
1946 } //let includes = this.code
1947 //.split("\n")
1948 //.map(x => includeRegex.exec(x))
1949 //.filter(x => x)
1950 //.map(x => x[1]);
1951 //log(includes);
1952
1953
1954 if (!locals) {
1955 locals = new Collection((await loadLocals("silo-presets", Preset)));
1956 }
1957
1958 log(Array(indent + 1).join(" ") + "- " + this.name);
1959
1960 for (let include of includes) {
1961 if (seen[include]) {
1962 log(Array(indent + 1).join(" ") + " - (seen) " + include);
1963 } else {
1964 seen[include] = true;
1965 let file = await locals.findByName(include);
1966
1967 if (file) {
1968 await file.printDepends(indent + 2, locals, seen);
1969 } else {
1970 log(Array(indent + 1).join(" ") + " - (miss) " + include);
1971 }
1972 }
1973 }
1974 }
1975
1976 }
1977
1978 defineAssoc(Preset, "_nameInner", "data.attributes.providerSettings.PresetName");
1979 defineAssoc(Preset, "_nameOuter", "data.attributes.name");
1980 defineAssoc(Preset, "id", "data.id");
1981 defineAssoc(Preset, "attributes", "data.attributes");
1982 defineAssoc(Preset, "relationships", "data.relationships");
1983 defineAssoc(Preset, "remote", "meta.remote");
1984 defineAssoc(Preset, "_code", "meta.code");
1985 defineAssoc(Preset, "_path", "meta.path");
1986 defineAssoc(Preset, "isGeneric", "meta.isGeneric");
1987 defineAssoc(Preset, "ext", "meta.ext");
1988 defineAssoc(Preset, "subproject", "meta.project");
1989 defineAssoc(Preset, "metastring", "meta.metastring");
1990 Preset.endpoint = "presets";
1991
1992 class Notification extends RallyBase {
1993 constructor({
1994 data,
1995 remote
1996 }) {
1997 super();
1998 this.data = data;
1999 this.meta = {};
2000 this.remote = remote;
2001 }
2002
2003 static async getAllPreCollect(notifications) {
2004 return notifications.sort((a, b) => {
2005 return a.attributes.type.localeCompare(b.attributes.type) || a.attributes.name.localeCompare(b.attributes.name);
2006 });
2007 }
2008
2009 chalkPrint(pad = false) {
2010 let id = String("N-" + this.id);
2011 if (pad) id = id.padStart(4);
2012 return chalk`{green ${id}}: {blue ${this.type}} - {green ${this.name}}`;
2013 }
2014
2015 }
2016
2017 defineAssoc(Notification, "id", "data.id");
2018 defineAssoc(Notification, "name", "data.attributes.name");
2019 defineAssoc(Notification, "address", "data.attributes.address");
2020 defineAssoc(Notification, "type", "data.attributes.type");
2021 defineAssoc(Notification, "remote", "meta.remote");
2022 Notification.endpoint = "notificationPresets";
2023
2024 class Rule extends RallyBase {
2025 constructor({
2026 path: path$1,
2027 data,
2028 remote,
2029 subProject
2030 } = {}) {
2031 super();
2032
2033 if (path$1) {
2034 path$1 = path.resolve(path$1);
2035
2036 try {
2037 let f = readFileSync(path$1, "utf-8");
2038 data = JSON.parse(readFileSync(path$1, "utf-8"));
2039 } catch (e) {
2040 if (e.code === "ENOENT") {
2041 if (exports.configObject.ignoreMissing) {
2042 this.missing = true;
2043 return undefined;
2044 } else {
2045 throw new AbortError("Could not load code of local file");
2046 }
2047 } else {
2048 throw new AbortError(`Unreadable JSON in ${path$1}. ${e}`);
2049 }
2050 }
2051 }
2052
2053 this.meta = {};
2054 this.subproject = subProject;
2055
2056 if (!data) {
2057 data = Rule.newShell();
2058 }
2059
2060 this.data = data;
2061 this.remote = remote;
2062 this.isGeneric = !this.remote;
2063 }
2064
2065 static newShell() {
2066 return {
2067 "attributes": {
2068 "description": "-",
2069 "priority": "PriorityNorm",
2070 "starred": false
2071 },
2072 "relationships": {},
2073 "type": "workflowRules"
2074 };
2075 }
2076
2077 async acclimatize(env) {
2078 this.remote = env;
2079 let preset = await this.resolveField(Preset, "preset", false, "specific");
2080 let pNext = await this.resolveField(Rule, "passNext", false, "specific");
2081 let eNext = await this.resolveField(Rule, "errorNext", false, "specific");
2082 let proType = await this.resolveField(Provider, "providerType", false, "specific");
2083 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true, "specific");
2084 let enterNotif = await this.resolveField(Notification, "enterNotifications", true, "specific");
2085 let errorNotif = await this.resolveField(Notification, "errorNotifications", true, "specific");
2086 let passNotif = await this.resolveField(Notification, "passNotifications", true, "specific");
2087 }
2088
2089 async saveA(env) {
2090 if (lib.isLocalEnv(env)) return;
2091 return await this.createIfNotExist(env);
2092 }
2093
2094 async saveB(env) {
2095 if (!this.isGeneric) {
2096 await this.resolve();
2097 }
2098
2099 this.cleanup();
2100
2101 if (lib.isLocalEnv(env)) {
2102 log(chalk`Saving rule {green ${this.name}} to {blue ${lib.envName(env)}}.`);
2103 writeFileSync(this.localpath, JSON.stringify(this.data, null, 4));
2104 } else {
2105 await this.acclimatize(env);
2106 await this.uploadRemote(env);
2107 }
2108 }
2109
2110 get immutable() {
2111 return false;
2112 }
2113
2114 async createIfNotExist(env) {
2115 write(chalk`First pass rule {green ${this.name}} to {green ${env}}: `);
2116
2117 if (this.immutable) {
2118 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
2119 return;
2120 } //First query the api to see if this already exists.
2121
2122
2123 let remote = await Rule.getByName(env, this.name);
2124 this.idMap = this.idMap || {};
2125
2126 if (remote) {
2127 this.idMap[env] = remote.id;
2128 log(chalk`exists ${remote.chalkPrint(false)}`);
2129 return;
2130 } //If it exists we can replace it
2131
2132
2133 write("create, ");
2134 let res = await lib.makeAPIRequest({
2135 env,
2136 path: `/workflowRules`,
2137 method: "POST",
2138 payload: {
2139 data: {
2140 attributes: {
2141 name: this.name
2142 },
2143 type: "workflowRules"
2144 }
2145 }
2146 });
2147 this.idMap = this.idMap || {};
2148 this.idMap[env] = res.data.id;
2149 write("id ");
2150 log(this.idMap[env]);
2151 }
2152
2153 async patchStrip() {
2154 delete this.data.attributes.createdAt;
2155 delete this.data.attributes.starred;
2156 delete this.data.attributes.updatedAt; // TEMP FIX FOR BUG IN SDVI
2157
2158 if (this.relationships.passMetadata && this.relationships.passMetadata[0]) {
2159 log("HAS PASS");
2160 log(this.name);
2161 log("HAS PASS");
2162 }
2163
2164 delete this.relationships.passMetadata;
2165
2166 if (this.relationships.errorMetadata && this.relationships.errorMetadata[0]) {
2167 log("HAS PASS");
2168 log(this.name);
2169 log("HAS PASS");
2170 }
2171
2172 delete this.relationships.errorMetadata; // This is commented out because it was fixed.
2173 //for(let key in this.relationships){
2174 //let relationship = this.relationships[key];
2175 //if(!relationship.data || relationship.data instanceof Array && !relationship.data[0]){
2176 //delete this.relationships[key];
2177 //}
2178 //}
2179 }
2180
2181 async uploadRemote(env) {
2182 write(chalk`Uploading rule {green ${this.name}} to {green ${env}}: `);
2183
2184 if (this.immutable) {
2185 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
2186 return;
2187 }
2188
2189 if (this.idMap[env]) {
2190 this.remote = env;
2191 await this.patchStrip();
2192 this.data.id = this.idMap[env]; //If it exists we can replace it
2193
2194 write("replace, ");
2195 let res = await lib.makeAPIRequest({
2196 env,
2197 path: `/workflowRules/${this.idMap[env]}`,
2198 method: "PATCH",
2199 payload: {
2200 data: this.data
2201 },
2202 fullResponse: true
2203 });
2204 log(chalk`response {yellow ${res.statusCode}}`);
2205
2206 if (res.statusCode !== 200) {
2207 log(res.body);
2208 log(JSON.stringify(this.data, null, 4));
2209 }
2210 } else {
2211 throw Error("Bad idmap!");
2212 }
2213 }
2214
2215 get localpath() {
2216 return path.join(exports.configObject.repodir, this.subproject || "", "silo-rules", this.name + ".json");
2217 }
2218
2219 async resolve() {
2220 let preset = await this.resolveField(Preset, "preset", false); //log(preset);
2221
2222 let pNext = await this.resolveField(Rule, "passNext", false);
2223 let eNext = await this.resolveField(Rule, "errorNext", false);
2224 let proType = await this.resolveField(Provider, "providerType", false); //log("Dynamic nexts")
2225
2226 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true); //log(dynamicNexts);
2227
2228 let enterNotif = await this.resolveField(Notification, "enterNotifications", true);
2229 let errorNotif = await this.resolveField(Notification, "errorNotifications", true);
2230 let passNotif = await this.resolveField(Notification, "passNotifications", true); //TODO Unsupported
2231
2232 delete this.relationships["enterMetadata"];
2233 delete this.relationships["errorMetadata"];
2234 this.isGeneric = true;
2235 return {
2236 preset,
2237 proType,
2238 pNext,
2239 eNext,
2240 dynamicNexts,
2241 errorNotif,
2242 enterNotif,
2243 passNotif
2244 };
2245 }
2246
2247 chalkPrint(pad = true) {
2248 let id = String("R-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
2249 let sub = "";
2250
2251 if (this.subproject) {
2252 sub = chalk`{yellow ${this.subproject}}`;
2253 }
2254
2255 if (pad) id = id.padStart(10);
2256
2257 try {
2258 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
2259 } catch (e) {
2260 return this.data;
2261 }
2262 }
2263
2264 }
2265
2266 defineAssoc(Rule, "name", "data.attributes.name");
2267 defineAssoc(Rule, "description", "data.attributes.description");
2268 defineAssoc(Rule, "id", "data.id");
2269 defineAssoc(Rule, "relationships", "data.relationships");
2270 defineAssoc(Rule, "isGeneric", "meta.isGeneric");
2271 defineAssoc(Rule, "remote", "meta.remote");
2272 defineAssoc(Rule, "subproject", "meta.project");
2273 defineAssoc(Rule, "idMap", "meta.idMap");
2274 Rule.endpoint = "workflowRules";
2275
2276 //Move project into silo metadata
2277 //move autotest into silo metadata
2278 //
2279
2280 class SupplyChain {
2281 constructor(startingRule, stopRule) {
2282 if (startingRule) {
2283 this.startingRule = startingRule;
2284 this.stopRule = stopRule;
2285 this.remote = startingRule.remote;
2286 }
2287 }
2288
2289 async downloadPresetCode(objs = this.allPresets) {
2290 log("Downloading code... ");
2291 await lib.keepalive(objs.arr.map(x => () => x.downloadCode()));
2292 }
2293
2294 async calculate() {
2295 log("Getting rules... ");
2296 this.allRules = await Rule.getAll(this.remote);
2297 log(this.allRules.length);
2298 log("Getting presets... ");
2299 this.allPresets = await Preset.getAll(this.remote);
2300 log(this.allPresets.length);
2301 log("Getting providers... ");
2302 this.allProviders = await Provider.getAll(this.remote);
2303 log(this.allProviders.length);
2304 log("Getting notifications... ");
2305 this.allNotifications = await Notification.getAll(this.remote);
2306 log(this.allNotifications.length);
2307
2308 if (!this.startingRule) {
2309 this.rules = this.allRules;
2310 this.presets = this.allPresets;
2311 this.notifications = new Collection([]);
2312 await this.downloadPresetCode();
2313 return;
2314 } else {
2315 await this.downloadPresetCode();
2316 }
2317
2318 log("Done!"); //Now we have everything we need to find a whole supply chain
2319
2320 write("Calculating Supply chain... ");
2321 log(this.startingRule.chalkPrint());
2322 let allRuleNames = this.allRules.arr.map(x => x.name).filter(x => x.length >= 4);
2323 let allPresetNames = this.allPresets.arr.map(x => x.name).filter(x => x.length >= 4);
2324 let allNotifNames = this.allNotifications.arr.map(x => x.name).filter(x => x.length >= 4);
2325 let requiredNotifications = new Set();
2326 let ruleQueue = [this.startingRule];
2327 let presetQueue = [];
2328
2329 for (let currentRule of ruleQueue) {
2330 if (currentRule === this.stopRule) continue;
2331 let {
2332 eNext,
2333 pNext,
2334 preset,
2335 passNotif,
2336 errorNotif,
2337 enterNotif
2338 } = await currentRule.resolve();
2339 passNotif.forEach(n => requiredNotifications.add(n));
2340 enterNotif.forEach(n => requiredNotifications.add(n));
2341 errorNotif.forEach(n => requiredNotifications.add(n));
2342 if (eNext && !ruleQueue.includes(eNext)) ruleQueue.push(eNext);
2343 if (pNext && !ruleQueue.includes(eNext)) ruleQueue.push(pNext);
2344 let neededPresets = preset.findStringsInCode(allPresetNames);
2345 neededPresets = neededPresets.map(x => this.allPresets.findByName(x));
2346 let neededRules = preset.findStringsInCode(allRuleNames);
2347 neededRules = neededRules.map(x => this.allRules.findByName(x));
2348 preset.findStringsInCode(allNotifNames).map(str => this.allNotifications.findByName(str)).forEach(notif => requiredNotifications.add(notif));
2349 neededPresets.push(preset);
2350
2351 for (let p of neededPresets) if (!presetQueue.includes(p)) presetQueue.push(p);
2352
2353 for (let p of neededRules) if (!ruleQueue.includes(p)) ruleQueue.push(p);
2354
2355 if (exports.configObject.verbose) {
2356 write(currentRule.chalkPrint(false));
2357 log(":");
2358 write(" ");
2359 write(preset.chalkPrint(false));
2360 log(":");
2361 write(" Pass Next: ");
2362 if (pNext) write(pNext.chalkPrint(false));else write("None");
2363 log("");
2364 write(" Err Next: ");
2365 if (eNext) write(eNext.chalkPrint(false));else write("None");
2366 log("");
2367 log(" Rules:");
2368
2369 for (let p of neededRules) log(" " + p.chalkPrint(true));
2370
2371 log(" Presets:");
2372
2373 for (let p of neededPresets) log(" " + p.chalkPrint(true));
2374
2375 log("\n");
2376 }
2377 }
2378
2379 log("Done!");
2380 this.rules = new Collection(ruleQueue);
2381 this.presets = new Collection(presetQueue);
2382 requiredNotifications.delete(undefined);
2383 this.notifications = new Collection([...requiredNotifications]);
2384 }
2385
2386 async log() {
2387 if (this.notifications.arr.length > 0) {
2388 log("Required notifications: ");
2389 this.notifications.log();
2390 }
2391
2392 if (this.rules.arr.length > 0) {
2393 write("Required rules: ");
2394 log(this.rules.arr.length);
2395 this.rules.log();
2396 }
2397
2398 if (this.presets.arr.length > 0) {
2399 write("Required presets: ");
2400 log(this.presets.arr.length);
2401 this.presets.log();
2402 }
2403
2404 if (exports.configObject.rawOutput) {
2405 return {
2406 presets: this.presets.arr,
2407 rules: this.rules.arr,
2408 notifications: this.notifications.arr
2409 };
2410 }
2411 }
2412
2413 async deleteTo(env) {
2414 for (let preset of this.presets) {
2415 try {
2416 await preset.deleteRemoteVersion(env);
2417 } catch (e) {
2418 log(e);
2419 }
2420 }
2421 }
2422
2423 async syncTo(env) {
2424 for (let preset of this.presets) {
2425 try {
2426 await preset.save(env);
2427 } catch (e) {
2428 log(e);
2429 }
2430 }
2431
2432 if (this.rules.arr[0]) {
2433 log("Starting create phase for rules");
2434
2435 for (let rule of this.rules) {
2436 try {
2437 await rule.saveA(env);
2438 } catch (e) {
2439 log(e);
2440 }
2441 }
2442
2443 log("OK");
2444 log("Starting link phase for rules");
2445 Rule.removeCache(env);
2446
2447 for (let rule of this.rules) {
2448 try {
2449 await rule.saveB(env);
2450 } catch (e) {
2451 log(e);
2452 }
2453 }
2454 }
2455 }
2456
2457 }
2458
2459 class User extends RallyBase {
2460 constructor({
2461 data,
2462 remote
2463 }) {
2464 super();
2465 this.data = data;
2466 this.meta = {};
2467 this.remote = remote;
2468 }
2469
2470 chalkPrint(pad = false) {
2471 let id = String("U-" + this.id);
2472 if (pad) id = id.padStart(7);
2473 return chalk`{green ${id}}: {blue ${this.name}}`;
2474 }
2475
2476 }
2477
2478 defineAssoc(User, "id", "data.id");
2479 defineAssoc(User, "name", "data.attributes.name");
2480 defineAssoc(User, "email", "data.attributes.email");
2481 defineAssoc(User, "remote", "meta.remote");
2482 User.endpoint = "users";
2483
2484 class Tag extends RallyBase {
2485 constructor({
2486 data,
2487 remote
2488 } = {}) {
2489 super();
2490 this.meta = {};
2491 this.remote = remote;
2492 this.data = data; //this.data.attributes.rallyConfiguration = undefined;
2493 //this.data.attributes.systemManaged = undefined;
2494 }
2495
2496 chalkPrint(pad = true) {
2497 let id = String("T-" + this.remote + "-" + this.id);
2498 if (pad) id = id.padStart(10);
2499 let prefix = this.curated ? "blue +" : "red -";
2500 return chalk`{green ${id}}: {${prefix}${this.name}}`;
2501 }
2502
2503 static async create(env, name, {
2504 notCurated
2505 } = {}) {
2506 return new Tag({
2507 data: await lib.makeAPIRequest({
2508 env,
2509 path: `/${this.endpoint}`,
2510 method: "POST",
2511 payload: {
2512 data: {
2513 attributes: {
2514 name,
2515 curated: notCurated ? false : true
2516 },
2517 type: "tagNames"
2518 }
2519 }
2520 }),
2521 remote: env
2522 });
2523 }
2524
2525 }
2526
2527 defineAssoc(Tag, "id", "data.id");
2528 defineAssoc(Tag, "attributes", "data.attributes");
2529 defineAssoc(Tag, "relationships", "data.relationships");
2530 defineAssoc(Tag, "name", "data.attributes.name");
2531 defineAssoc(Tag, "curated", "data.attributes.curated");
2532 defineAssoc(Tag, "remote", "meta.remote");
2533 Tag.endpoint = "tagNames";
2534
2535 async function findLineInFile(renderedPreset, lineNumber) {
2536 let trueFileLine = lineNumber;
2537 let linedRenderedPreset = renderedPreset.split("\n").slice(2, -2);
2538 renderedPreset = renderedPreset.split("\n").slice(2, -2).join("\n");
2539 let includeLocation = renderedPreset.split("\n").filter(x => x.includes("@include"));
2540 let endIncludeNumber = -1,
2541 addTabDepth = 2;
2542 let lineBeforeIncludeStatement = '';
2543 let withinInclude = true;
2544
2545 if (lineNumber > linedRenderedPreset.indexOf(includeLocation[includeLocation.length - 1])) {
2546 addTabDepth = 0;
2547 withinInclude = false;
2548 }
2549
2550 for (let index = includeLocation.length - 1; index >= 0; index--) {
2551 let currIncludeIndex = linedRenderedPreset.indexOf(includeLocation[index]);
2552 let tabDepth = includeLocation[index].split(" ").length;
2553
2554 if (lineNumber > currIncludeIndex) {
2555 if (includeLocation[index].split(" ").filter(Boolean)[1] != "ERROR:") {
2556 if (lineBeforeIncludeStatement.split(" ").length == tabDepth && withinInclude) {
2557 trueFileLine = trueFileLine - currIncludeIndex;
2558 break;
2559 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth && endIncludeNumber == -1) {
2560 endIncludeNumber = currIncludeIndex;
2561 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth) {
2562 trueFileLine = trueFileLine - (endIncludeNumber - currIncludeIndex);
2563 endIncludeNumber = -1;
2564 }
2565 }
2566 } else {
2567 lineBeforeIncludeStatement = includeLocation[index];
2568 }
2569 }
2570
2571 let funcLine = "";
2572
2573 for (let line of linedRenderedPreset.slice(0, lineNumber).reverse()) {
2574 let match = /def (\w+)/.exec(line);
2575
2576 if (match) {
2577 funcLine = match[1];
2578 break;
2579 }
2580 }
2581
2582 let includeFilename;
2583
2584 if (lineBeforeIncludeStatement != "") {
2585 includeFilename = lineBeforeIncludeStatement.slice(1).trim().slice(14, -1);
2586 } else {
2587 includeFilename = null;
2588 }
2589
2590 if (includeLocation.length !== 0) {
2591 trueFileLine -= 1;
2592 lineNumber -= 1;
2593 }
2594
2595 return {
2596 lineNumber: trueFileLine,
2597 includeFilename,
2598 line: linedRenderedPreset[lineNumber],
2599 funcLine
2600 };
2601 }
2602 function printOutLine(eLine) {
2603 return log(chalk`{blue ${eLine.includeFilename || "Main"}}:{green ${eLine.lineNumber}} in ${eLine.funcLine}
2604${eLine.line}`);
2605 }
2606 async function getInfo(env, jobid) {
2607 log(env, jobid);
2608 let trace = lib.makeAPIRequest({
2609 env,
2610 path: `/jobs/${jobid}/artifacts/trace`
2611 }).catch(x => null);
2612 let renderedPreset = lib.makeAPIRequest({
2613 env,
2614 path: `/jobs/${jobid}/artifacts/preset`
2615 }).catch(x => null);
2616 let result = lib.makeAPIRequest({
2617 env,
2618 path: `/jobs/${jobid}/artifacts/result`
2619 }).catch(x => null);
2620 let error = lib.makeAPIRequest({
2621 env,
2622 path: `/jobs/${jobid}/artifacts/error`
2623 }).catch(x => null);
2624 let output = lib.makeAPIRequest({
2625 env,
2626 path: `/jobs/${jobid}/artifacts/output`
2627 }).catch(x => null);
2628 [trace, renderedPreset, result, output, error] = await Promise.all([trace, renderedPreset, result, output, error]);
2629 return {
2630 trace,
2631 renderedPreset,
2632 result,
2633 output,
2634 error
2635 };
2636 }
2637 async function parseTrace(env, jobid) {
2638 let {
2639 trace,
2640 renderedPreset
2641 } = await getInfo(env, jobid);
2642 let lineNumber = -1;
2643 let errorLines = [];
2644 let shouldBreak = 0;
2645
2646 for (let tr of trace.split("\n\n").reverse()) {
2647 errorLines.push(tr);
2648 shouldBreak--;
2649 if (tr.includes("Exception")) shouldBreak = 1;
2650 if (tr.includes("raised")) shouldBreak = 1;
2651 if (!shouldBreak) break;
2652 }
2653
2654 let errorList = [];
2655
2656 for (let errLine of errorLines) {
2657 lineNumber = /^[\w ]+:(\d+):/g.exec(errLine);
2658
2659 if (lineNumber && lineNumber[1]) {
2660 errorList.push((await findLineInFile(renderedPreset, lineNumber[1])));
2661 } else {
2662 errorList.push(errLine);
2663 }
2664 }
2665
2666 return errorList;
2667 }
2668 const Trace = {
2669 parseTrace,
2670 printOutLine,
2671 getInfo,
2672 findLineInFile
2673 };
2674
2675 require("source-map-support").install();
2676 const rallyFunctions = {
2677 async bestPagintation() {
2678 global.silentAPI = true;
2679
2680 for (let i = 10; i <= 30; i += 5) {
2681 console.time("test with " + i);
2682 let dl = await lib.indexPathFast("DEV", `/workflowRules?page=1p${i}`);
2683 console.timeEnd("test with " + i);
2684 }
2685 },
2686
2687 async uploadPresets(env, presets, createFunc = () => false) {
2688 for (let preset of presets) {
2689 await preset.uploadCodeToEnv(env, createFunc);
2690 }
2691 },
2692
2693 //Dummy test access
2694 async testAccess(env) {
2695 if (lib.isLocalEnv(env)) {
2696 //TODO
2697 return true;
2698 }
2699
2700 let result = await lib.makeAPIRequest({
2701 env,
2702 path: "/providers?page=1p1",
2703 fullResponse: true,
2704 timeout: 1000
2705 });
2706 return result.statusCode;
2707 }
2708
2709 };
2710
2711 exports.APIError = APIError;
2712 exports.AbortError = AbortError;
2713 exports.Asset = Asset;
2714 exports.Collection = Collection;
2715 exports.FileTooLargeError = FileTooLargeError;
2716 exports.Notification = Notification;
2717 exports.Preset = Preset;
2718 exports.ProtectedEnvError = ProtectedEnvError;
2719 exports.Provider = Provider;
2720 exports.RallyBase = RallyBase;
2721 exports.Rule = Rule;
2722 exports.SupplyChain = SupplyChain;
2723 exports.Tag = Tag;
2724 exports.Trace = Trace;
2725 exports.UnconfiguredEnvError = UnconfiguredEnvError;
2726 exports.User = User;
2727 exports.lib = lib;
2728 exports.loadConfig = loadConfig;
2729 exports.loadConfigFromArgs = loadConfigFromArgs;
2730 exports.rallyFunctions = rallyFunctions;
2731 exports.setConfig = setConfig;
2732 exports.sleep = sleep;
2733
2734 Object.defineProperty(exports, '__esModule', { value: true });
2735
2736})));
2737//# sourceMappingURL=web.js.map