UNPKG

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