UNPKG

81.3 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) {
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 providerData: Buffer.from(preset.code, "binary").toString("base64"),
1141 dynamicPresetData
1142 },
1143 type: "jobs",
1144 relationships: {
1145 movie: {
1146 data: {
1147 id: this.id,
1148 type: "movies"
1149 }
1150 }
1151 }
1152 }
1153 }
1154 });
1155 write(" Waiting for finish...");
1156
1157 for (;;) {
1158 res = await lib.makeAPIRequest({
1159 env,
1160 path_full: evalInfo.data.links.self
1161 });
1162 write(".");
1163
1164 if (res.data.attributes.state == "Complete") {
1165 write(chalk`{green Done}...\n`);
1166 break;
1167 }
1168
1169 await sleep(300);
1170 }
1171
1172 return;
1173 }
1174
1175 async startEvaluate(presetid, dynamicPresetData) {
1176 // Fire and forget.
1177 let data = await lib.makeAPIRequest({
1178 env: this.remote,
1179 path: "/jobs",
1180 method: "POST",
1181 payload: {
1182 data: {
1183 type: "jobs",
1184 attributes: {
1185 dynamicPresetData
1186 },
1187 relationships: {
1188 movie: {
1189 data: {
1190 id: this.id,
1191 type: "movies"
1192 }
1193 },
1194 preset: {
1195 data: {
1196 id: presetid,
1197 type: "presets"
1198 }
1199 }
1200 }
1201 }
1202 }
1203 });
1204 return data;
1205 }
1206
1207 async rename(newName) {
1208 let req = await lib.makeAPIRequest({
1209 env: this.remote,
1210 path: `/assets/${this.id}`,
1211 method: "PATCH",
1212 payload: {
1213 data: {
1214 attributes: {
1215 name: newName
1216 },
1217 type: "assets"
1218 }
1219 }
1220 });
1221 this.name = newName;
1222 return req;
1223 }
1224
1225 async migrate(targetEnv) {
1226 exports.configObject.globalProgress = false;
1227 log(`Creating paired file in ${targetEnv}`); //Fetch metadata in parallel, we await it later
1228
1229 let _mdPromise = this.getMetadata();
1230
1231 let targetAsset = await Asset.getByName(targetEnv, this.name);
1232
1233 if (targetAsset) {
1234 log(`Asset already exists ${targetAsset.chalkPrint()}`); //if(configObject.script) process.exit(10);
1235 } else {
1236 targetAsset = await Asset.createNew(this.name, targetEnv);
1237 log(`Asset created ${targetAsset.chalkPrint()}`);
1238 } //wait for metadata to be ready before patching
1239
1240
1241 await _mdPromise;
1242 log("Adding asset metadata");
1243 await targetAsset.patchMetadata(this.md);
1244 let fileCreations = [];
1245
1246 for (let file of await this.getFiles()) {
1247 //Check for any valid copy-able instances
1248 for (let inst of file.instancesList) {
1249 //We need to skip internal files
1250 if (inst.storageLocationName === "Rally Platform Bucket") continue;
1251 log(`Adding file: ${file.chalkPrint()}`);
1252 fileCreations.push(targetAsset.addFileInstance(file, inst));
1253 }
1254 }
1255
1256 await Promise.all(fileCreations);
1257 }
1258
1259 async addFileInstance(file, inst, tagList = []) {
1260 let newInst = {
1261 uri: File.rslURL(inst),
1262 name: inst.name,
1263 size: inst.size,
1264 lastModified: inst.lastModified,
1265 storageLocationName: inst.storageLocationName
1266 };
1267 let request = lib.makeAPIRequest({
1268 env: this.remote,
1269 path: `/files`,
1270 method: "POST",
1271 payload: {
1272 data: {
1273 type: "files",
1274 attributes: {
1275 label: file.label,
1276 tagList,
1277 instances: {
1278 "1": newInst
1279 }
1280 },
1281 relationships: {
1282 asset: {
1283 data: {
1284 id: this.id,
1285 type: "assets"
1286 }
1287 }
1288 }
1289 }
1290 }
1291 });
1292
1293 try {
1294 let fileData = await request;
1295 let newFile = new File({
1296 data: fileData.data,
1297 remote: this.remote,
1298 parent: this
1299 });
1300 if (exports.configObject.script) console.log(inst.uri, newFile.instancesList[0].uri);
1301 } catch (e) {
1302 log(chalk`{red Failed file: ${file.chalkPrint()}}`);
1303 }
1304 }
1305
1306 }
1307
1308 defineAssoc(Asset, "id", "data.id");
1309 defineAssoc(Asset, "name", "data.attributes.name");
1310 defineAssoc(Asset, "remote", "meta.remote");
1311 defineAssoc(Asset, "md", "meta.metadata");
1312 defineAssoc(Asset, "lite", "meta.lite");
1313 Asset.endpoint = "movies";
1314
1315 let home;
1316
1317 if (os.homedir) {
1318 home = os.homedir();
1319 }
1320
1321 const colon = /:/g;
1322 const siloLike = /(silo\-\w+?)s?\/([^\/]+)\.([\w1234567890]+)$/g;
1323 function pathTransform(path) {
1324 if (path.includes(":")) {
1325 //Ignore the first colon in window-like filesystems
1326 path = path.slice(0, 3) + path.slice(3).replace(colon, "--");
1327 }
1328
1329 if (exports.configObject.invertedPath) {
1330 path = path.replace(siloLike, "$2-$1.$3");
1331 }
1332
1333 if (path.includes("\\342\\200\\220")) {
1334 path = path.replace("\\342\\200\\220", "‐");
1335 }
1336
1337 return path;
1338 }
1339 function readFileSync(path, options) {
1340 return fs__default.readFileSync(pathTransform(path), options);
1341 } //Create writefilesync, with ability to create directory if it doesnt exist
1342
1343 function writeFileSync(path$1, data, options, dircreated = false) {
1344 path$1 = pathTransform(path$1);
1345
1346 try {
1347 return fs__default.writeFileSync(path$1, data, options);
1348 } catch (e) {
1349 if (dircreated) throw e;
1350 let directory = path.dirname(path$1);
1351
1352 try {
1353 fs__default.statSync(directory);
1354 throw e;
1355 } catch (nodir) {
1356 fs__default.mkdirSync(directory);
1357 return writeFileSync(path$1, data, options, true);
1358 }
1359 }
1360 }
1361
1362 let exists = {};
1363
1364 class Preset extends RallyBase {
1365 constructor({
1366 path: path$1,
1367 remote,
1368 data,
1369 subProject
1370 } = {}) {
1371 // Get full path if possible
1372 if (path$1) {
1373 path$1 = path.resolve(path$1);
1374
1375 if (path.dirname(path$1).includes("silo-metadata")) {
1376 throw new AbortError("Constructing preset from metadata file");
1377 }
1378 }
1379
1380 super(); // Cache by path
1381
1382 if (path$1) {
1383 if (exists[pathTransform(path$1)]) return exists[pathTransform(path$1)];
1384 exists[pathTransform(path$1)] = this;
1385 }
1386
1387 this.meta = {};
1388 this.subproject = subProject;
1389 this.remote = remote;
1390
1391 if (lib.isLocalEnv(this.remote)) {
1392 if (path$1) {
1393 this.path = path$1;
1394 let pathspl = this.path.split(".");
1395 this.ext = pathspl[pathspl.length - 1];
1396
1397 try {
1398 this.code = this.getLocalCode();
1399 } catch (e) {
1400 if (e.code === "ENOENT" && exports.configObject.ignoreMissing) {
1401 this.missing = true;
1402 return undefined;
1403 } else {
1404 log(chalk`{red Node Error} ${e.message}`);
1405 throw new AbortError("Could not load code of local file");
1406 }
1407 }
1408
1409 let name = this.parseFilenameForName() || this.parseCodeForName();
1410
1411 try {
1412 this.data = this.getLocalMetadata();
1413 this.isGeneric = true;
1414 name = this.name;
1415 } catch (e) {
1416 log(chalk`{yellow Warning}: ${path$1} does not have a readable metadata file! Looking for ${this.localmetadatapath}`);
1417 this.data = Preset.newShell(name);
1418 this.isGeneric = false;
1419 }
1420
1421 this.name = name;
1422 } else {
1423 this.data = Preset.newShell();
1424 }
1425 } else {
1426 this.data = data; //this.name = data.attributes.name;
1427 //this.id = data.id;
1428
1429 this.isGeneric = false;
1430 }
1431
1432 this.data.attributes.rallyConfiguration = undefined;
1433 this.data.attributes.systemManaged = undefined;
1434 } //Given a metadata file, get its actualy file
1435
1436
1437 static async fromMetadata(path, subproject) {
1438 let data;
1439
1440 try {
1441 data = JSON.parse(readFileSync(path));
1442 } catch (e) {
1443 if (e.code === "ENOENT" && exports.configObject.ignoreMissing) {
1444 return null;
1445 } else {
1446 throw e;
1447 }
1448 }
1449
1450 let providerType = data.relationships.providerType.data.name;
1451 let provider = await Provider.getByName("DEV", providerType);
1452
1453 if (!provider) {
1454 log(chalk`{red The provider type {green ${providerType}} does not exist}`);
1455 log(chalk`{red Skipping {green ${path}}.}`);
1456 return null;
1457 }
1458
1459 let ext = await provider.getFileExtension();
1460 let name = data.attributes.name;
1461 let realpath = Preset.getLocalPath(name, ext, subproject);
1462 return new Preset({
1463 path: realpath,
1464 subProject: subproject
1465 });
1466 }
1467
1468 static newShell(name = undefined) {
1469 return {
1470 "attributes": {
1471 "providerSettings": {
1472 "PresetName": name
1473 }
1474 },
1475 "relationships": {},
1476 "type": "presets"
1477 };
1478 }
1479
1480 cleanup() {
1481 super.cleanup();
1482 delete this.attributes["createdAt"];
1483 delete this.attributes["updatedAt"];
1484 }
1485
1486 async acclimatize(env) {
1487 if (!this.isGeneric) throw new AbortError("Cannot acclimatize non-generics or shells");
1488 let providers = await Provider.getAll(env);
1489 let ptype = this.relationships["providerType"];
1490 ptype = ptype.data;
1491 let provider = providers.findByName(ptype.name);
1492 ptype.id = provider.id;
1493 }
1494
1495 get test() {
1496 if (!this.code) return [];
1497 const regex = /[^-]autotest:\s?([\w\d_\-. \/]+)[\r\s\n]*?/gm;
1498 let match;
1499 let matches = [];
1500
1501 while (match = regex.exec(this.code)) {
1502 matches.push(match[1]);
1503 }
1504
1505 return matches;
1506 }
1507
1508 async runTest(env) {
1509 let remote = await Preset.getByName(env, this.name);
1510
1511 for (let test of this.test) {
1512 log("Tests...");
1513 let asset;
1514
1515 if (test.startsWith("id")) {
1516 let match = /id:\s*(\d+)/g.exec(test);
1517
1518 if (!match) {
1519 log(chalk`{red Could not parse autotest} ${test}.`);
1520 throw new AbortError("Could not properly parse the preset header");
1521 }
1522
1523 asset = await Asset.getById(env, match[1]);
1524 } else {
1525 asset = await Asset.getByName(env, test);
1526 }
1527
1528 if (!asset) {
1529 log(chalk`{yellow No movie found}, skipping test.`);
1530 continue;
1531 }
1532
1533 log(chalk`Starting job {green ${this.name}} on ${asset.chalkPrint(false)}... `);
1534 await asset.startEvaluate(remote.id);
1535 }
1536 }
1537
1538 async resolve() {
1539 if (this.isGeneric) return;
1540 let proType = await this.resolveField(Provider, "providerType");
1541 this.ext = await proType.getFileExtension();
1542 this.isGeneric = true;
1543 return {
1544 proType
1545 };
1546 }
1547
1548 async saveLocal() {
1549 await this.saveLocalMetadata();
1550 await this.saveLocalFile();
1551 }
1552
1553 async saveLocalMetadata() {
1554 if (!this.isGeneric) {
1555 await this.resolve();
1556 this.cleanup();
1557 }
1558
1559 writeFileSync(this.localmetadatapath, JSON.stringify(this.data, null, 4));
1560 }
1561
1562 async saveLocalFile() {
1563 writeFileSync(this.localpath, this.code);
1564 }
1565
1566 async uploadRemote(env) {
1567 await this.uploadCodeToEnv(env, true);
1568 }
1569
1570 async save(env) {
1571 this.saved = true;
1572
1573 if (!this.isGeneric) {
1574 await this.resolve();
1575 }
1576
1577 this.cleanup();
1578
1579 if (lib.isLocalEnv(env)) {
1580 log(chalk`Saving preset {green ${this.name}} to {blue ${lib.envName(env)}}.`);
1581 await this.saveLocal();
1582 } else {
1583 await this.uploadRemote(env);
1584 }
1585 }
1586
1587 async downloadCode() {
1588 if (!this.remote || this.code) return this.code;
1589 let code = await lib.makeAPIRequest({
1590 env: this.remote,
1591 path_full: this.data.links.providerData,
1592 json: false
1593 }); //match header like
1594 // # c: d
1595 // # b
1596 // # a
1597 // ##################
1598
1599 let headerRegex = /(^# .+[\r\n]+)+#+[\r\n]+/gim;
1600 let hasHeader = headerRegex.exec(code);
1601
1602 if (hasHeader) {
1603 this.header = code.substring(0, hasHeader[0].length - 1);
1604 code = code.substring(hasHeader[0].length);
1605 }
1606
1607 return this.code = code;
1608 }
1609
1610 get code() {
1611 if (this._code) return this._code;
1612 }
1613
1614 set code(v) {
1615 this._code = v;
1616 }
1617
1618 chalkPrint(pad = true) {
1619 let id = String("P-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
1620 let sub = "";
1621
1622 if (this.subproject) {
1623 sub = chalk`{yellow ${this.subproject}}`;
1624 }
1625
1626 if (pad) id = id.padStart(10);
1627
1628 if (this.name == undefined) {
1629 return chalk`{green ${id}}: ${sub}{red ${this.path}}`;
1630 } else if (this.meta.proType) {
1631 return chalk`{green ${id}}: ${sub}{red ${this.meta.proType.name}} {blue ${this.name}}`;
1632 } else {
1633 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
1634 }
1635 }
1636
1637 parseFilenameForName() {
1638 if (this.path.endsWith(".jinja") || this.path.endsWith(".json")) {
1639 return path.basename(this.path).replace("_", " ").replace("-", " ").replace(".json", "").replace(".jinja", "");
1640 }
1641 }
1642
1643 parseCodeForName() {
1644 const name_regex = /name\s?:\s([\w\d. \/]+)[\r\s\n]*?/;
1645 const match = name_regex.exec(this.code);
1646 if (match) return match[1];
1647 }
1648
1649 findStringsInCode(strings) {
1650 if (!this.code) return [];
1651 return strings.filter(str => {
1652 let regex = new RegExp(str);
1653 return !!this.code.match(regex);
1654 });
1655 }
1656
1657 static getLocalPath(name, ext, subproject) {
1658 return path__default.join(exports.configObject.repodir, subproject || "", "silo-presets", name + "." + ext);
1659 }
1660
1661 get localpath() {
1662 return Preset.getLocalPath(this.name, this.ext, this.subproject);
1663 }
1664
1665 get path() {
1666 if (this._path) return this._path;
1667 }
1668
1669 set path(val) {
1670 this._path = val;
1671 }
1672
1673 get name() {
1674 return this._nameOuter;
1675 }
1676
1677 set name(val) {
1678 if (!this._nameInner) this._nameInner = val;
1679 this._nameOuter = val;
1680 }
1681
1682 set providerType(value) {
1683 this.relationships["providerType"] = {
1684 data: { ...value,
1685 type: "providerTypes"
1686 }
1687 };
1688 }
1689
1690 get localmetadatapath() {
1691 if (this.path) {
1692 return this.path.replace("silo-presets", "silo-metadata").replace(new RegExp(this.ext + "$"), "json");
1693 }
1694
1695 return path__default.join(exports.configObject.repodir, this.subproject || "", "silo-metadata", this.name + ".json");
1696 }
1697
1698 get immutable() {
1699 return this.name.includes("Constant") && !exports.configObject.updateImmutable;
1700 }
1701
1702 async uploadPresetData(env, id) {
1703 var _this$relationships, _this$relationships$p, _this$relationships$p2;
1704
1705 if (this.code.trim() === "NOUPLOAD") {
1706 write(chalk`code skipped {yellow :)}, `);
1707 return;
1708 }
1709
1710 let code = this.code;
1711 let headers = {};
1712 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;
1713
1714 if (!exports.configObject.skipHeader && (providerName === "SdviEvaluate" || providerName === "SdviEvalPro")) {
1715 write(chalk`generate header, `);
1716 let repodir = exports.configObject.repodir;
1717 let localpath = this.path.replace(repodir, "");
1718 if (localpath.startsWith("/")) localpath = localpath.substring(1);
1719
1720 try {
1721 let {
1722 stdout: headerText
1723 } = await spawn({
1724 noecho: true
1725 }, "sh", [path__default.join(exports.configObject.repodir, `bin/header.sh`), moment(Date.now()).format("ddd YYYY/MM/DD hh:mm:ssa"), localpath]);
1726 code = headerText + code;
1727 write(chalk`header ok, `);
1728 } catch (e) {
1729 write(chalk`missing unix, `);
1730 }
1731 } //binary presets
1732
1733
1734 if (providerName == "Vantage") {
1735 code = code.toString("base64");
1736 headers["Content-Transfer-Encoding"] = "base64";
1737 }
1738
1739 let res = await lib.makeAPIRequest({
1740 env,
1741 path: `/presets/${id}/providerData`,
1742 body: code,
1743 method: "PUT",
1744 fullResponse: true,
1745 timeout: 10000,
1746 headers
1747 });
1748 write(chalk`code up {yellow ${res.statusCode}}, `);
1749 }
1750
1751 async grabMetadata(env) {
1752 let remote = await Preset.getByName(env, this.name);
1753 this.isGeneric = false;
1754
1755 if (!remote) {
1756 throw new AbortError(`No file found on remote ${env} with name ${this.name}`);
1757 }
1758
1759 this.data = remote.data;
1760 this.remote = env;
1761 }
1762
1763 async deleteRemoteVersion(env, id = null) {
1764 if (lib.isLocalEnv(env)) return false;
1765
1766 if (!id) {
1767 let remote = await Preset.getByName(env, this.name);
1768 id = remote.id;
1769 }
1770
1771 return await lib.makeAPIRequest({
1772 env,
1773 path: `/presets/${id}`,
1774 method: "DELETE"
1775 });
1776 }
1777
1778 async delete() {
1779 if (lib.isLocalEnv(this.remote)) return false;
1780 return await this.deleteRemoteVersion(this.remote, this.id);
1781 }
1782
1783 async uploadCodeToEnv(env, includeMetadata, shouldTest = true) {
1784 if (!this.name) {
1785 let match;
1786
1787 if (match = /^(#|["']{3})\s*EPH (\d+)/.exec(this.code.trim())) {
1788 let a = await Asset.getById(env, Number(match[2]));
1789 return a.startEphemeralEvaluateIdeal(this);
1790 } else {
1791 log(chalk`Failed uploading {red ${this.path}}. No name found.`);
1792 return;
1793 }
1794 }
1795
1796 write(chalk`Uploading preset {green ${this.name}} to {green ${env}}: `);
1797
1798 if (this.immutable) {
1799 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
1800 return;
1801 } //First query the api to see if this already exists.
1802
1803
1804 let remote = await Preset.getByName(env, this.name);
1805
1806 if (remote) {
1807 //If it exists we can replace it
1808 write("replace, ");
1809
1810 if (includeMetadata) {
1811 let payload = {
1812 data: {
1813 attributes: this.data.attributes,
1814 type: "presets"
1815 }
1816 };
1817
1818 if (this.relationships.tagNames) {
1819 payload.relationships = {
1820 tagNames: this.relationships.tagNames
1821 };
1822 }
1823
1824 let res = await lib.makeAPIRequest({
1825 env,
1826 path: `/presets/${remote.id}`,
1827 method: "PATCH",
1828 payload,
1829 fullResponse: true
1830 });
1831 write(chalk`metadata {yellow ${res.statusCode}}, `);
1832
1833 if (res.statusCode == 500) {
1834 log(chalk`skipping code upload, did not successfully upload metadata`);
1835 return;
1836 }
1837 }
1838
1839 await this.uploadPresetData(env, remote.id);
1840 } else {
1841 write("create, ");
1842 let metadata = {
1843 data: this.data
1844 };
1845
1846 if (!this.relationships["providerType"]) {
1847 throw new AbortError("Cannot acclimatize shelled presets. (try creating it on the env first)");
1848 }
1849
1850 await this.acclimatize(env);
1851 write("Posting to create preset... ");
1852 let res = await lib.makeAPIRequest({
1853 env,
1854 path: `/presets`,
1855 method: "POST",
1856 payload: metadata,
1857 timeout: 5000
1858 });
1859 let id = res.data.id;
1860 write(chalk`Created id {green ${id}}... Uploading Code... `);
1861 await this.uploadPresetData(env, id);
1862 }
1863
1864 if (this.test[0] && shouldTest) {
1865 await this.runTest(env);
1866 } else {
1867 log("No tests. Done.");
1868 }
1869 }
1870
1871 getLocalMetadata() {
1872 return JSON.parse(readFileSync(this.localmetadatapath, "utf-8"));
1873 }
1874
1875 getLocalCode() {
1876 //todo fixup for binary presets, see uploadPresetData
1877 return readFileSync(this.path, "utf-8");
1878 }
1879
1880 parseHeaderInfo() {
1881 var _$exec$, _$exec$2, _$exec$3, _$exec$4, _$exec$5, _$exec$6, _$exec$7;
1882
1883 if (!this.header) return null;
1884 let abs = {
1885 built: (_$exec$ = /Built On:(.+)/.exec(this.header)[1]) === null || _$exec$ === void 0 ? void 0 : _$exec$.trim(),
1886 author: (_$exec$2 = /Author:(.+)/.exec(this.header)[1]) === null || _$exec$2 === void 0 ? void 0 : _$exec$2.trim(),
1887 build: (_$exec$3 = /Build:(.+)/.exec(this.header)[1]) === null || _$exec$3 === void 0 ? void 0 : _$exec$3.trim(),
1888 version: (_$exec$4 = /Version:(.+)/.exec(this.header)[1]) === null || _$exec$4 === void 0 ? void 0 : _$exec$4.trim(),
1889 branch: (_$exec$5 = /Branch:(.+)/.exec(this.header)[1]) === null || _$exec$5 === void 0 ? void 0 : _$exec$5.trim(),
1890 commit: (_$exec$6 = /Commit:(.+)/.exec(this.header)[1]) === null || _$exec$6 === void 0 ? void 0 : _$exec$6.trim(),
1891 local: (_$exec$7 = /Local File:(.+)/.exec(this.header)[1]) === null || _$exec$7 === void 0 ? void 0 : _$exec$7.trim()
1892 };
1893 let tryFormats = [[true, "ddd MMM DD HH:mm:ss YYYY"], [false, "ddd YYYY/MM/DD LTS"]];
1894
1895 for (let [isUTC, format] of tryFormats) {
1896 let date;
1897
1898 if (isUTC) {
1899 date = moment.utc(abs.built, format);
1900 } else {
1901 date = moment(abs.built, format);
1902 }
1903
1904 if (!date.isValid()) continue;
1905 abs.offset = date.fromNow();
1906 break;
1907 }
1908
1909 return abs;
1910 }
1911
1912 async printRemoteInfo(env) {
1913 let remote = await Preset.getByName(env, this.name);
1914 await remote.downloadCode();
1915 let i = remote.parseHeaderInfo();
1916
1917 if (i) {
1918 log(chalk`
1919 ENV: {red ${env}}, updated {yellow ~${i.offset}}
1920 Built on {blue ${i.built}} by {green ${i.author}}
1921 From ${i.build || "(unknown)"} on ${i.branch} ({yellow ${i.commit}})
1922 `.replace(/^[ \t]+/gim, "").trim());
1923 } else {
1924 log(chalk`No header on {red ${env}}`);
1925 }
1926 }
1927
1928 async getInfo(envs) {
1929 await this.printDepends();
1930
1931 for (let env of envs.split(",")) {
1932 await this.printRemoteInfo(env);
1933 }
1934 }
1935
1936 async printDepends(indent = 0, locals = null, seen = {}) {
1937 let includeRegex = /@include "(.+)"/gim; //let includeRegex = /@include/g;
1938
1939 let includes = [];
1940 let inc;
1941
1942 while (inc = includeRegex.exec(this.code)) {
1943 includes.push(inc[1]);
1944 } //let includes = this.code
1945 //.split("\n")
1946 //.map(x => includeRegex.exec(x))
1947 //.filter(x => x)
1948 //.map(x => x[1]);
1949 //log(includes);
1950
1951
1952 if (!locals) {
1953 locals = new Collection((await loadLocals("silo-presets", Preset)));
1954 }
1955
1956 log(Array(indent + 1).join(" ") + "- " + this.name);
1957
1958 for (let include of includes) {
1959 if (seen[include]) {
1960 log(Array(indent + 1).join(" ") + " - (seen) " + include);
1961 } else {
1962 seen[include] = true;
1963 let file = await locals.findByName(include);
1964
1965 if (file) {
1966 await file.printDepends(indent + 2, locals, seen);
1967 } else {
1968 log(Array(indent + 1).join(" ") + " - (miss) " + include);
1969 }
1970 }
1971 }
1972 }
1973
1974 }
1975
1976 defineAssoc(Preset, "_nameInner", "data.attributes.providerSettings.PresetName");
1977 defineAssoc(Preset, "_nameOuter", "data.attributes.name");
1978 defineAssoc(Preset, "id", "data.id");
1979 defineAssoc(Preset, "attributes", "data.attributes");
1980 defineAssoc(Preset, "relationships", "data.relationships");
1981 defineAssoc(Preset, "remote", "meta.remote");
1982 defineAssoc(Preset, "_code", "meta.code");
1983 defineAssoc(Preset, "_path", "meta.path");
1984 defineAssoc(Preset, "isGeneric", "meta.isGeneric");
1985 defineAssoc(Preset, "ext", "meta.ext");
1986 defineAssoc(Preset, "subproject", "meta.project");
1987 defineAssoc(Preset, "metastring", "meta.metastring");
1988 Preset.endpoint = "presets";
1989
1990 class Notification extends RallyBase {
1991 constructor({
1992 data,
1993 remote
1994 }) {
1995 super();
1996 this.data = data;
1997 this.meta = {};
1998 this.remote = remote;
1999 }
2000
2001 static async getAllPreCollect(notifications) {
2002 return notifications.sort((a, b) => {
2003 return a.attributes.type.localeCompare(b.attributes.type) || a.attributes.name.localeCompare(b.attributes.name);
2004 });
2005 }
2006
2007 chalkPrint(pad = false) {
2008 let id = String("N-" + this.id);
2009 if (pad) id = id.padStart(4);
2010 return chalk`{green ${id}}: {blue ${this.type}} - {green ${this.name}}`;
2011 }
2012
2013 }
2014
2015 defineAssoc(Notification, "id", "data.id");
2016 defineAssoc(Notification, "name", "data.attributes.name");
2017 defineAssoc(Notification, "address", "data.attributes.address");
2018 defineAssoc(Notification, "type", "data.attributes.type");
2019 defineAssoc(Notification, "remote", "meta.remote");
2020 Notification.endpoint = "notificationPresets";
2021
2022 class Rule extends RallyBase {
2023 constructor({
2024 path: path$1,
2025 data,
2026 remote,
2027 subProject
2028 } = {}) {
2029 super();
2030
2031 if (path$1) {
2032 path$1 = path.resolve(path$1);
2033
2034 try {
2035 let f = readFileSync(path$1, "utf-8");
2036 data = JSON.parse(readFileSync(path$1, "utf-8"));
2037 } catch (e) {
2038 if (e.code === "ENOENT") {
2039 if (exports.configObject.ignoreMissing) {
2040 this.missing = true;
2041 return undefined;
2042 } else {
2043 throw new AbortError("Could not load code of local file");
2044 }
2045 } else {
2046 throw new AbortError(`Unreadable JSON in ${path$1}. ${e}`);
2047 }
2048 }
2049 }
2050
2051 this.meta = {};
2052 this.subproject = subProject;
2053
2054 if (!data) {
2055 data = Rule.newShell();
2056 }
2057
2058 this.data = data;
2059 this.remote = remote;
2060 this.isGeneric = !this.remote;
2061 }
2062
2063 static newShell() {
2064 return {
2065 "attributes": {
2066 "description": "-",
2067 "priority": "PriorityNorm",
2068 "starred": false
2069 },
2070 "relationships": {},
2071 "type": "workflowRules"
2072 };
2073 }
2074
2075 async acclimatize(env) {
2076 this.remote = env;
2077 let preset = await this.resolveField(Preset, "preset", false, "specific");
2078 let pNext = await this.resolveField(Rule, "passNext", false, "specific");
2079 let eNext = await this.resolveField(Rule, "errorNext", false, "specific");
2080 let proType = await this.resolveField(Provider, "providerType", false, "specific");
2081 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true, "specific");
2082 let enterNotif = await this.resolveField(Notification, "enterNotifications", true, "specific");
2083 let errorNotif = await this.resolveField(Notification, "errorNotifications", true, "specific");
2084 let passNotif = await this.resolveField(Notification, "passNotifications", true, "specific");
2085 }
2086
2087 async saveA(env) {
2088 if (lib.isLocalEnv(env)) return;
2089 return await this.createIfNotExist(env);
2090 }
2091
2092 async saveB(env) {
2093 if (!this.isGeneric) {
2094 await this.resolve();
2095 }
2096
2097 this.cleanup();
2098
2099 if (lib.isLocalEnv(env)) {
2100 log(chalk`Saving rule {green ${this.name}} to {blue ${lib.envName(env)}}.`);
2101 writeFileSync(this.localpath, JSON.stringify(this.data, null, 4));
2102 } else {
2103 await this.acclimatize(env);
2104 await this.uploadRemote(env);
2105 }
2106 }
2107
2108 get immutable() {
2109 return false;
2110 }
2111
2112 async createIfNotExist(env) {
2113 write(chalk`First pass rule {green ${this.name}} to {green ${env}}: `);
2114
2115 if (this.immutable) {
2116 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
2117 return;
2118 } //First query the api to see if this already exists.
2119
2120
2121 let remote = await Rule.getByName(env, this.name);
2122 this.idMap = this.idMap || {};
2123
2124 if (remote) {
2125 this.idMap[env] = remote.id;
2126 log(chalk`exists ${remote.chalkPrint(false)}`);
2127 return;
2128 } //If it exists we can replace it
2129
2130
2131 write("create, ");
2132 let res = await lib.makeAPIRequest({
2133 env,
2134 path: `/workflowRules`,
2135 method: "POST",
2136 payload: {
2137 data: {
2138 attributes: {
2139 name: this.name
2140 },
2141 type: "workflowRules"
2142 }
2143 }
2144 });
2145 this.idMap = this.idMap || {};
2146 this.idMap[env] = res.data.id;
2147 write("id ");
2148 log(this.idMap[env]);
2149 }
2150
2151 async patchStrip() {
2152 delete this.data.attributes.createdAt;
2153 delete this.data.attributes.starred;
2154 delete this.data.attributes.updatedAt; // TEMP FIX FOR BUG IN SDVI
2155
2156 if (this.relationships.passMetadata && this.relationships.passMetadata[0]) {
2157 log("HAS PASS");
2158 log(this.name);
2159 log("HAS PASS");
2160 }
2161
2162 delete this.relationships.passMetadata;
2163
2164 if (this.relationships.errorMetadata && this.relationships.errorMetadata[0]) {
2165 log("HAS PASS");
2166 log(this.name);
2167 log("HAS PASS");
2168 }
2169
2170 delete this.relationships.errorMetadata; // This is commented out because it was fixed.
2171 //for(let key in this.relationships){
2172 //let relationship = this.relationships[key];
2173 //if(!relationship.data || relationship.data instanceof Array && !relationship.data[0]){
2174 //delete this.relationships[key];
2175 //}
2176 //}
2177 }
2178
2179 async uploadRemote(env) {
2180 write(chalk`Uploading rule {green ${this.name}} to {green ${env}}: `);
2181
2182 if (this.immutable) {
2183 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
2184 return;
2185 }
2186
2187 if (this.idMap[env]) {
2188 this.remote = env;
2189 await this.patchStrip();
2190 this.data.id = this.idMap[env]; //If it exists we can replace it
2191
2192 write("replace, ");
2193 let res = await lib.makeAPIRequest({
2194 env,
2195 path: `/workflowRules/${this.idMap[env]}`,
2196 method: "PATCH",
2197 payload: {
2198 data: this.data
2199 },
2200 fullResponse: true
2201 });
2202 log(chalk`response {yellow ${res.statusCode}}`);
2203
2204 if (res.statusCode !== 200) {
2205 log(res.body);
2206 log(JSON.stringify(this.data, null, 4));
2207 }
2208 } else {
2209 throw Error("Bad idmap!");
2210 }
2211 }
2212
2213 get localpath() {
2214 return path.join(exports.configObject.repodir, this.subproject || "", "silo-rules", this.name + ".json");
2215 }
2216
2217 async resolve() {
2218 let preset = await this.resolveField(Preset, "preset", false); //log(preset);
2219
2220 let pNext = await this.resolveField(Rule, "passNext", false);
2221 let eNext = await this.resolveField(Rule, "errorNext", false);
2222 let proType = await this.resolveField(Provider, "providerType", false); //log("Dynamic nexts")
2223
2224 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true); //log(dynamicNexts);
2225
2226 let enterNotif = await this.resolveField(Notification, "enterNotifications", true);
2227 let errorNotif = await this.resolveField(Notification, "errorNotifications", true);
2228 let passNotif = await this.resolveField(Notification, "passNotifications", true); //TODO Unsupported
2229
2230 delete this.relationships["enterMetadata"];
2231 delete this.relationships["errorMetadata"];
2232 this.isGeneric = true;
2233 return {
2234 preset,
2235 proType,
2236 pNext,
2237 eNext,
2238 dynamicNexts,
2239 errorNotif,
2240 enterNotif,
2241 passNotif
2242 };
2243 }
2244
2245 chalkPrint(pad = true) {
2246 let id = String("R-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
2247 let sub = "";
2248
2249 if (this.subproject) {
2250 sub = chalk`{yellow ${this.subproject}}`;
2251 }
2252
2253 if (pad) id = id.padStart(10);
2254
2255 try {
2256 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
2257 } catch (e) {
2258 return this.data;
2259 }
2260 }
2261
2262 }
2263
2264 defineAssoc(Rule, "name", "data.attributes.name");
2265 defineAssoc(Rule, "description", "data.attributes.description");
2266 defineAssoc(Rule, "id", "data.id");
2267 defineAssoc(Rule, "relationships", "data.relationships");
2268 defineAssoc(Rule, "isGeneric", "meta.isGeneric");
2269 defineAssoc(Rule, "remote", "meta.remote");
2270 defineAssoc(Rule, "subproject", "meta.project");
2271 defineAssoc(Rule, "idMap", "meta.idMap");
2272 Rule.endpoint = "workflowRules";
2273
2274 //Move project into silo metadata
2275 //move autotest into silo metadata
2276 //
2277
2278 class SupplyChain {
2279 constructor(startingRule, stopRule) {
2280 if (startingRule) {
2281 this.startingRule = startingRule;
2282 this.stopRule = stopRule;
2283 this.remote = startingRule.remote;
2284 }
2285 }
2286
2287 async downloadPresetCode(objs = this.allPresets) {
2288 log("Downloading code... ");
2289 await lib.keepalive(objs.arr.map(x => () => x.downloadCode()));
2290 }
2291
2292 async calculate() {
2293 log("Getting rules... ");
2294 this.allRules = await Rule.getAll(this.remote);
2295 log(this.allRules.length);
2296 log("Getting presets... ");
2297 this.allPresets = await Preset.getAll(this.remote);
2298 log(this.allPresets.length);
2299 log("Getting providers... ");
2300 this.allProviders = await Provider.getAll(this.remote);
2301 log(this.allProviders.length);
2302 log("Getting notifications... ");
2303 this.allNotifications = await Notification.getAll(this.remote);
2304 log(this.allNotifications.length);
2305
2306 if (!this.startingRule) {
2307 this.rules = this.allRules;
2308 this.presets = this.allPresets;
2309 this.notifications = new Collection([]);
2310 await this.downloadPresetCode();
2311 return;
2312 } else {
2313 await this.downloadPresetCode();
2314 }
2315
2316 log("Done!"); //Now we have everything we need to find a whole supply chain
2317
2318 write("Calculating Supply chain... ");
2319 log(this.startingRule.chalkPrint());
2320 let allRuleNames = this.allRules.arr.map(x => x.name).filter(x => x.length >= 4);
2321 let allPresetNames = this.allPresets.arr.map(x => x.name).filter(x => x.length >= 4);
2322 let allNotifNames = this.allNotifications.arr.map(x => x.name).filter(x => x.length >= 4);
2323 let requiredNotifications = new Set();
2324 let ruleQueue = [this.startingRule];
2325 let presetQueue = [];
2326
2327 for (let currentRule of ruleQueue) {
2328 if (currentRule === this.stopRule) continue;
2329 let {
2330 eNext,
2331 pNext,
2332 preset,
2333 passNotif,
2334 errorNotif,
2335 enterNotif
2336 } = await currentRule.resolve();
2337 passNotif.forEach(n => requiredNotifications.add(n));
2338 enterNotif.forEach(n => requiredNotifications.add(n));
2339 errorNotif.forEach(n => requiredNotifications.add(n));
2340 if (eNext && !ruleQueue.includes(eNext)) ruleQueue.push(eNext);
2341 if (pNext && !ruleQueue.includes(eNext)) ruleQueue.push(pNext);
2342 let neededPresets = preset.findStringsInCode(allPresetNames);
2343 neededPresets = neededPresets.map(x => this.allPresets.findByName(x));
2344 let neededRules = preset.findStringsInCode(allRuleNames);
2345 neededRules = neededRules.map(x => this.allRules.findByName(x));
2346 preset.findStringsInCode(allNotifNames).map(str => this.allNotifications.findByName(str)).forEach(notif => requiredNotifications.add(notif));
2347 neededPresets.push(preset);
2348
2349 for (let p of neededPresets) if (!presetQueue.includes(p)) presetQueue.push(p);
2350
2351 for (let p of neededRules) if (!ruleQueue.includes(p)) ruleQueue.push(p);
2352
2353 if (exports.configObject.verbose) {
2354 write(currentRule.chalkPrint(false));
2355 log(":");
2356 write(" ");
2357 write(preset.chalkPrint(false));
2358 log(":");
2359 write(" Pass Next: ");
2360 if (pNext) write(pNext.chalkPrint(false));else write("None");
2361 log("");
2362 write(" Err Next: ");
2363 if (eNext) write(eNext.chalkPrint(false));else write("None");
2364 log("");
2365 log(" Rules:");
2366
2367 for (let p of neededRules) log(" " + p.chalkPrint(true));
2368
2369 log(" Presets:");
2370
2371 for (let p of neededPresets) log(" " + p.chalkPrint(true));
2372
2373 log("\n");
2374 }
2375 }
2376
2377 log("Done!");
2378 this.rules = new Collection(ruleQueue);
2379 this.presets = new Collection(presetQueue);
2380 requiredNotifications.delete(undefined);
2381 this.notifications = new Collection([...requiredNotifications]);
2382 }
2383
2384 async log() {
2385 if (this.notifications.arr.length > 0) {
2386 log("Required notifications: ");
2387 this.notifications.log();
2388 }
2389
2390 if (this.rules.arr.length > 0) {
2391 write("Required rules: ");
2392 log(this.rules.arr.length);
2393 this.rules.log();
2394 }
2395
2396 if (this.presets.arr.length > 0) {
2397 write("Required presets: ");
2398 log(this.presets.arr.length);
2399 this.presets.log();
2400 }
2401
2402 if (exports.configObject.rawOutput) {
2403 return {
2404 presets: this.presets.arr,
2405 rules: this.rules.arr,
2406 notifications: this.notifications.arr
2407 };
2408 }
2409 }
2410
2411 async deleteTo(env) {
2412 for (let preset of this.presets) {
2413 try {
2414 await preset.deleteRemoteVersion(env);
2415 } catch (e) {
2416 log(e);
2417 }
2418 }
2419 }
2420
2421 async syncTo(env) {
2422 for (let preset of this.presets) {
2423 try {
2424 await preset.save(env);
2425 } catch (e) {
2426 log(e);
2427 }
2428 }
2429
2430 if (this.rules.arr[0]) {
2431 log("Starting create phase for rules");
2432
2433 for (let rule of this.rules) {
2434 try {
2435 await rule.saveA(env);
2436 } catch (e) {
2437 log(e);
2438 }
2439 }
2440
2441 log("OK");
2442 log("Starting link phase for rules");
2443 Rule.removeCache(env);
2444
2445 for (let rule of this.rules) {
2446 try {
2447 await rule.saveB(env);
2448 } catch (e) {
2449 log(e);
2450 }
2451 }
2452 }
2453 }
2454
2455 }
2456
2457 class User extends RallyBase {
2458 constructor({
2459 data,
2460 remote
2461 }) {
2462 super();
2463 this.data = data;
2464 this.meta = {};
2465 this.remote = remote;
2466 }
2467
2468 chalkPrint(pad = false) {
2469 let id = String("U-" + this.id);
2470 if (pad) id = id.padStart(7);
2471 return chalk`{green ${id}}: {blue ${this.name}}`;
2472 }
2473
2474 }
2475
2476 defineAssoc(User, "id", "data.id");
2477 defineAssoc(User, "name", "data.attributes.name");
2478 defineAssoc(User, "email", "data.attributes.email");
2479 defineAssoc(User, "remote", "meta.remote");
2480 User.endpoint = "users";
2481
2482 class Tag extends RallyBase {
2483 constructor({
2484 data,
2485 remote
2486 } = {}) {
2487 super();
2488 this.meta = {};
2489 this.remote = remote;
2490 this.data = data; //this.data.attributes.rallyConfiguration = undefined;
2491 //this.data.attributes.systemManaged = undefined;
2492 }
2493
2494 chalkPrint(pad = true) {
2495 let id = String("T-" + this.remote + "-" + this.id);
2496 if (pad) id = id.padStart(10);
2497 let prefix = this.curated ? "blue +" : "red -";
2498 return chalk`{green ${id}}: {${prefix}${this.name}}`;
2499 }
2500
2501 static async create(env, name, {
2502 notCurated
2503 } = {}) {
2504 return new Tag({
2505 data: await lib.makeAPIRequest({
2506 env,
2507 path: `/${this.endpoint}`,
2508 method: "POST",
2509 payload: {
2510 data: {
2511 attributes: {
2512 name,
2513 curated: notCurated ? false : true
2514 },
2515 type: "tagNames"
2516 }
2517 }
2518 }),
2519 remote: env
2520 });
2521 }
2522
2523 }
2524
2525 defineAssoc(Tag, "id", "data.id");
2526 defineAssoc(Tag, "attributes", "data.attributes");
2527 defineAssoc(Tag, "relationships", "data.relationships");
2528 defineAssoc(Tag, "name", "data.attributes.name");
2529 defineAssoc(Tag, "curated", "data.attributes.curated");
2530 defineAssoc(Tag, "remote", "meta.remote");
2531 Tag.endpoint = "tagNames";
2532
2533 async function findLineInFile(renderedPreset, lineNumber) {
2534 let trueFileLine = lineNumber;
2535 let linedRenderedPreset = renderedPreset.split("\n").slice(2, -2);
2536 renderedPreset = renderedPreset.split("\n").slice(2, -2).join("\n");
2537 let includeLocation = renderedPreset.split("\n").filter(x => x.includes("@include"));
2538 let endIncludeNumber = -1,
2539 addTabDepth = 2;
2540 let lineBeforeIncludeStatement = '';
2541 let withinInclude = true;
2542
2543 if (lineNumber > linedRenderedPreset.indexOf(includeLocation[includeLocation.length - 1])) {
2544 addTabDepth = 0;
2545 withinInclude = false;
2546 }
2547
2548 for (let index = includeLocation.length - 1; index >= 0; index--) {
2549 let currIncludeIndex = linedRenderedPreset.indexOf(includeLocation[index]);
2550 let tabDepth = includeLocation[index].split(" ").length;
2551
2552 if (lineNumber > currIncludeIndex) {
2553 if (includeLocation[index].split(" ").filter(Boolean)[1] != "ERROR:") {
2554 if (lineBeforeIncludeStatement.split(" ").length == tabDepth && withinInclude) {
2555 trueFileLine = trueFileLine - currIncludeIndex;
2556 break;
2557 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth && endIncludeNumber == -1) {
2558 endIncludeNumber = currIncludeIndex;
2559 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth) {
2560 trueFileLine = trueFileLine - (endIncludeNumber - currIncludeIndex);
2561 endIncludeNumber = -1;
2562 }
2563 }
2564 } else {
2565 lineBeforeIncludeStatement = includeLocation[index];
2566 }
2567 }
2568
2569 let funcLine = "";
2570
2571 for (let line of linedRenderedPreset.slice(0, lineNumber).reverse()) {
2572 let match = /def (\w+)/.exec(line);
2573
2574 if (match) {
2575 funcLine = match[1];
2576 break;
2577 }
2578 }
2579
2580 let includeFilename;
2581
2582 if (lineBeforeIncludeStatement != "") {
2583 includeFilename = lineBeforeIncludeStatement.slice(1).trim().slice(14, -1);
2584 } else {
2585 includeFilename = null;
2586 }
2587
2588 if (includeLocation.length !== 0) {
2589 trueFileLine -= 1;
2590 lineNumber -= 1;
2591 }
2592
2593 return {
2594 lineNumber: trueFileLine,
2595 includeFilename,
2596 line: linedRenderedPreset[lineNumber],
2597 funcLine
2598 };
2599 }
2600 function printOutLine(eLine) {
2601 return log(chalk`{blue ${eLine.includeFilename || "Main"}}:{green ${eLine.lineNumber}} in ${eLine.funcLine}
2602${eLine.line}`);
2603 }
2604 async function getInfo(env, jobid) {
2605 log(env, jobid);
2606 let trace = lib.makeAPIRequest({
2607 env,
2608 path: `/jobs/${jobid}/artifacts/trace`
2609 }).catch(x => null);
2610 let renderedPreset = lib.makeAPIRequest({
2611 env,
2612 path: `/jobs/${jobid}/artifacts/preset`
2613 }).catch(x => null);
2614 let result = lib.makeAPIRequest({
2615 env,
2616 path: `/jobs/${jobid}/artifacts/result`
2617 }).catch(x => null);
2618 let error = lib.makeAPIRequest({
2619 env,
2620 path: `/jobs/${jobid}/artifacts/error`
2621 }).catch(x => null);
2622 let output = lib.makeAPIRequest({
2623 env,
2624 path: `/jobs/${jobid}/artifacts/output`
2625 }).catch(x => null);
2626 [trace, renderedPreset, result, output, error] = await Promise.all([trace, renderedPreset, result, output, error]);
2627 return {
2628 trace,
2629 renderedPreset,
2630 result,
2631 output,
2632 error
2633 };
2634 }
2635 async function parseTrace(env, jobid) {
2636 let {
2637 trace,
2638 renderedPreset
2639 } = await getInfo(env, jobid);
2640 let lineNumber = -1;
2641 let errorLines = [];
2642 let shouldBreak = 0;
2643
2644 for (let tr of trace.split("\n\n").reverse()) {
2645 errorLines.push(tr);
2646 shouldBreak--;
2647 if (tr.includes("Exception")) shouldBreak = 1;
2648 if (tr.includes("raised")) shouldBreak = 1;
2649 if (!shouldBreak) break;
2650 }
2651
2652 let errorList = [];
2653
2654 for (let errLine of errorLines) {
2655 lineNumber = /^[\w ]+:(\d+):/g.exec(errLine);
2656
2657 if (lineNumber && lineNumber[1]) {
2658 errorList.push((await findLineInFile(renderedPreset, lineNumber[1])));
2659 } else {
2660 errorList.push(errLine);
2661 }
2662 }
2663
2664 return errorList;
2665 }
2666 const Trace = {
2667 parseTrace,
2668 printOutLine,
2669 getInfo,
2670 findLineInFile
2671 };
2672
2673 require("source-map-support").install();
2674 const rallyFunctions = {
2675 async bestPagintation() {
2676 global.silentAPI = true;
2677
2678 for (let i = 10; i <= 30; i += 5) {
2679 console.time("test with " + i);
2680 let dl = await lib.indexPathFast("DEV", `/workflowRules?page=1p${i}`);
2681 console.timeEnd("test with " + i);
2682 }
2683 },
2684
2685 async uploadPresets(env, presets, createFunc = () => false) {
2686 for (let preset of presets) {
2687 await preset.uploadCodeToEnv(env, createFunc);
2688 }
2689 },
2690
2691 //Dummy test access
2692 async testAccess(env) {
2693 if (lib.isLocalEnv(env)) {
2694 //TODO
2695 return true;
2696 }
2697
2698 let result = await lib.makeAPIRequest({
2699 env,
2700 path: "/providers?page=1p1",
2701 fullResponse: true,
2702 timeout: 1000
2703 });
2704 return result.statusCode;
2705 }
2706
2707 };
2708
2709 exports.APIError = APIError;
2710 exports.AbortError = AbortError;
2711 exports.Asset = Asset;
2712 exports.Collection = Collection;
2713 exports.FileTooLargeError = FileTooLargeError;
2714 exports.Notification = Notification;
2715 exports.Preset = Preset;
2716 exports.ProtectedEnvError = ProtectedEnvError;
2717 exports.Provider = Provider;
2718 exports.RallyBase = RallyBase;
2719 exports.Rule = Rule;
2720 exports.SupplyChain = SupplyChain;
2721 exports.Tag = Tag;
2722 exports.Trace = Trace;
2723 exports.UnconfiguredEnvError = UnconfiguredEnvError;
2724 exports.User = User;
2725 exports.lib = lib;
2726 exports.loadConfig = loadConfig;
2727 exports.loadConfigFromArgs = loadConfigFromArgs;
2728 exports.rallyFunctions = rallyFunctions;
2729 exports.setConfig = setConfig;
2730 exports.sleep = sleep;
2731
2732 Object.defineProperty(exports, '__esModule', { value: true });
2733
2734})));
2735//# sourceMappingURL=web.js.map