UNPKG

rally-tools

Version:
1,927 lines (1,635 loc) 654 kB
#!/usr/bin/env node /*---------------------------------------------- * Generated by rollup. Written by John Schmidt. * Rally Tools CLI v8.6.3 *--------------------------------------------*/ const importLazy = require("import-lazy")(require); 'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var os = require('os'); var fs = require('fs'); var fs__default = _interopDefault(fs); var child_process = require('child_process'); var perf_hooks = require('perf_hooks'); var chalk$1 = _interopDefault(require('chalk')); var rp = _interopDefault(require('request-promise')); var path = require('path'); var path__default = _interopDefault(path); var moment = _interopDefault(require('moment')); var fetch = _interopDefault(require('node-fetch')); var argparse = _interopDefault(require('minimist')); var redis = require('redis'); function _asyncIterator(iterable) { var method; if (typeof Symbol === "function") { if (Symbol.asyncIterator) { method = iterable[Symbol.asyncIterator]; if (method != null) return method.call(iterable); } if (Symbol.iterator) { method = iterable[Symbol.iterator]; if (method != null) return method.call(iterable); } } throw new TypeError("Object is not async iterable"); } function _AwaitValue(value) { this.wrapped = value; } function _AsyncGenerator(gen) { var front, back; function send(key, arg) { return new Promise(function (resolve, reject) { var request = { key: key, arg: arg, resolve: resolve, reject: reject, next: null }; if (back) { back = back.next = request; } else { front = back = request; resume(key, arg); } }); } function resume(key, arg) { try { var result = gen[key](arg); var value = result.value; var wrappedAwait = value instanceof _AwaitValue; Promise.resolve(wrappedAwait ? value.wrapped : value).then(function (arg) { if (wrappedAwait) { resume("next", arg); return; } settle(result.done ? "return" : "normal", arg); }, function (err) { resume("throw", err); }); } catch (err) { settle("throw", err); } } function settle(type, value) { switch (type) { case "return": front.resolve({ value: value, done: true }); break; case "throw": front.reject(value); break; default: front.resolve({ value: value, done: false }); break; } front = front.next; if (front) { resume(front.key, front.arg); } else { back = null; } } this._invoke = send; if (typeof gen.return !== "function") { this.return = undefined; } } if (typeof Symbol === "function" && Symbol.asyncIterator) { _AsyncGenerator.prototype[Symbol.asyncIterator] = function () { return this; }; } _AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); }; _AsyncGenerator.prototype.throw = function (arg) { return this._invoke("throw", arg); }; _AsyncGenerator.prototype.return = function (arg) { return this._invoke("return", arg); }; function _wrapAsyncGenerator(fn) { return function () { return new _AsyncGenerator(fn.apply(this, arguments)); }; } function _awaitAsyncGenerator(value) { return new _AwaitValue(value); } function _asyncGeneratorDelegate(inner, awaitWrap) { var iter = {}, waiting = false; function pump(key, value) { waiting = true; value = new Promise(function (resolve) { resolve(inner[key](value)); }); return { done: false, value: awaitWrap(value) }; } if (typeof Symbol === "function" && Symbol.iterator) { iter[Symbol.iterator] = function () { return this; }; } iter.next = function (value) { if (waiting) { waiting = false; return value; } return pump("next", value); }; if (typeof inner.throw === "function") { iter.throw = function (value) { if (waiting) { waiting = false; throw value; } return pump("throw", value); }; } if (typeof inner.return === "function") { iter.return = function (value) { return pump("return", value); }; } return iter; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; } let configFile = null; if (os.homedir) { configFile = os.homedir() + "/.rallyconfig"; } let configObject; function loadConfig(file) { if (file) configFile = file; if (!configFile) return; configObject = { hasConfig: true }; let json; try { json = fs.readFileSync(configFile); configObject = JSON.parse(json); configObject.hasConfig = true; } catch (e) { if (e.code == "ENOENT") { configObject.hasConfig = false; //ok, they should probably make a config } else if (e instanceof SyntaxError) { configObject.hasConfig = false; log(chalk`{red Error}: Syntax Error when loading {blue ${configFile}}`); log(chalk`{red ${e.message}}`); let charPos = /at position (\d+)/g.exec(e.message); if (charPos) { let lineNum = 1; let charsLeft = Number(charPos[1]) + 1; for (let line of json.toString("utf8").split("\n")) { if (line.length + 1 > charsLeft) { break; } charsLeft -= line.length + 1; //+1 for newline lineNum++; } log(chalk`Approximate error loc: {green ${lineNum}:${charsLeft}}`); } } else { throw e; } } } function loadConfigFromArgs(args) { let tempConfig = { hasConfig: true, ...args.config }; configObject = tempConfig; } function setConfig(obj) { configObject = obj; } let helpEntries = {}; let helpEntry = name => helpEntries[name] ? helpEntries[name] : helpEntries[name] = { name }; //short description function helpText(text) { return function (func, name) { helpEntry(name).text = text; return func; }; } //flag type argument like -f or --file function arg(long, short, desc) { return function (func, name) { let args = helpEntry(name).args = helpEntry(name).args || []; args.unshift({ long, short, desc }); return func; }; } //normal argument function param(param, desc) { return function (func, name) { let params = helpEntry(name).params = helpEntry(name).params || []; params.unshift({ param, desc }); return func; }; } //usage string function usage(usage) { return function (func, name) { usage = usage.replace(/[\[<](\w+)[\]>]/g, chalk`[{blue $1}]`); helpEntry(name).usage = usage; return func; }; } //function retuns obj.a.b.c function deepAccess(obj, path$$1) { let o = obj; for (let key of path$$1) { if (!o) return []; o = o[key]; } return o; } //This takes a class as the first argument, then adds a getter/setter pair that //corresponds to an object in this.data function defineAssoc(classname, shortname, path$$1) { path$$1 = path$$1.split("."); let lastKey = path$$1.pop(); Object.defineProperty(classname.prototype, shortname, { get() { return deepAccess(this, path$$1)[lastKey]; }, set(val) { deepAccess(this, path$$1)[lastKey] = val; } }); } function runCommand(command) { return new Promise(function (resolve, reject) { child_process.exec(command, { maxBuffer: Infinity }, async function (err, stdout, stderr) { resolve(stdout); }); }); } function spawn(options, ...args) { if (typeof options !== "object") { args.unshift(options); options = {}; } //todo options return new Promise((resolve, reject) => { let start = perf_hooks.performance.now(); let stdout = ""; let stderr = ""; let cp = child_process.spawn(...args); let write = global.write; if (options.noecho) { write = () => {}; } if (cp.stdout) cp.stdout.on("data", chunk => { stdout += chunk; write(chunk); }); if (cp.stderr) cp.stderr.on("data", chunk => { stderr += chunk; write(chunk); }); if (options.stdin) { options.stdin(cp.stdin); } cp.on("error", reject); cp.on("close", code => { let end = perf_hooks.performance.now(); let time = end - start; let timestr = time > 1000 ? (time / 100 | 0) / 10 + "s" : (time | 0) + "ms"; resolve({ stdout, stderr, exitCode: code, time, timestr }); }); }); } async function runGit(oks, ...args) { if (typeof oks === "number") { oks = [oks]; } else if (typeof oks === "undefined") { oks = [0]; } if (configObject.verbose) write(`git ${args.join(" ")}`); let g = await spawn({ noecho: true }, "git", args); if (!oks.includes(g.exitCode)) { if (configObject.verbose) log(chalk`{red ${g.exitCode}}`); log(g.stderr); log(g.stdout); throw new AbortError(chalk`Failed to run git ${args} {red ${g.exitCode}}`); } else if (configObject.verbose) { log(chalk`{green ${g.exitCode}}`); } return [g.stdout, g.stderr]; } global.chalk = chalk$1; global.log = (...text) => console.log(...text); global.write = (...text) => process.stdout.write(...text); global.elog = (...text) => console.error(...text); global.ewrite = (...text) => process.stderr.write(...text); global.errorLog = (...text) => log(...text.map(chalk$1.red)); const logging = { log, write, elog, ewrite, errorLog, chalk: chalk$1 }; class lib { //This function takes 2 required arguemnts: // env: the enviornment you wish to use // and either: // 'path', the short path to the resource. ex '/presets/' // 'path_full', the full path to the resource like 'https://discovery-dev.sdvi.com/presets' // // If the method is anything but GET, either payload or body should be set. // payload should be a javascript object to be turned into json as the request body // body should be a string that is passed as the body. for example: the python code of a preset. // // qs are the querystring parameters, in a key: value object. // {filter: "name=test name"} becomes something like 'filter=name=test+name' // // headers are the headers of the request. "Content-Type" is already set if // payload is given as a parameter // // fullResponse should be true if you want to receive the request object, // not just the returned data. static async makeAPIRequest({ env, path: path$$1, path_full, fullPath, payload, body, method = "GET", qs, headers = {}, fullResponse = false, timeout = configObject.timeout || 20000 }) { var _configObject$api; //backwards compatability from ruby script if (fullPath) path_full = fullPath; //Keys are defined in enviornment variables let config = configObject === null || configObject === void 0 ? void 0 : (_configObject$api = configObject.api) === null || _configObject$api === void 0 ? void 0 : _configObject$api[env]; if (!config) { throw new UnconfiguredEnvError(env); } if (method !== "GET" && !configObject.dangerModify) { if (env === "UAT" && configObject.restrictUAT || env === "PROD") { throw new ProtectedEnvError(env); } } let rally_api_key = config.key; let rally_api = config.url; if (path$$1 && path$$1.startsWith("/v1.0/")) { rally_api = rally_api.replace("/api/v2", "/api"); } path$$1 = path_full || rally_api + path$$1; if (payload) { body = JSON.stringify(payload, null, 4); } if (payload) { headers["Content-Type"] = "application/vnd.api+json"; } let fullHeaders = { //SDVI ignores this header sometimes. Accept: "application/vnd.api+json", "X-SDVI-Client-Application": "Discovery-rtlib-" + (configObject.appName || "commandline"), ...headers }; if (configObject.vvverbose) { log(`${method} @ ${path$$1}`); log(JSON.stringify(fullHeaders, null, 4)); if (body) { log(body); } else { log("(No body"); } } if (configObject.dryRun && method !== "GET") { log(chalk$1`{red Skipping ${method} request for dry run}`); return null; } let requestOptions = { method, body, qs, uri: path$$1, timeout, auth: { bearer: rally_api_key }, headers: fullHeaders, simple: false, resolveWithFullResponse: true }; let response; try { response = await rp(requestOptions); if (configObject.vverbose || configObject.vvverbose) { log(chalk$1`${method} @ ${response.request.uri.href}`); } } catch (e) { if ((e === null || e === void 0 ? void 0 : e.cause.code) === "ESOCKETTIMEDOUT") { throw new APIError(response || {}, requestOptions, body); } else { throw e; } } //Throw an error for any 5xx or 4xx if (!fullResponse && ![200, 201, 202, 203, 204].includes(response.statusCode)) { throw new APIError(response, requestOptions, body); } let contentType = response.headers["content-type"]; let isJSONResponse = contentType === "application/vnd.api+json" || contentType === "application/json"; if (configObject.vvverbose) { log(response.body); } if (fullResponse) { return response; } else if (isJSONResponse) { var _response, _response$body; 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 {}; try { return JSON.parse(response.body); } catch (e) { log(response.body); throw new AbortError("Body is not valid json: "); } } else { return response.body; } } //Index a json endpoint that returns a {links} field. //This function returns the merged data objects as an array // //Additonal options (besides makeAPIRequest options): // - Observe: function to be called for each set of data from the api static async indexPath(env, path$$1) { let opts = typeof env === "string" ? { env, path: path$$1 } : env; opts.maxParallelRequests = 1; let index = new IndexObject(opts); return await index.fullResults(); } static clearProgress(size = 30) { if (!configObject.globalProgress) return; process.stderr.write(`\r${" ".repeat(size + 15)}\r`); } static async drawProgress(i, max, size = process.stdout.columns - 15 || 15) { if (!configObject.globalProgress) return; if (size > 45) size = 45; let pct = Number(i) / Number(max); //clamp between 0 and 1 pct = pct < 0 ? 0 : pct > 1 ? 1 : pct; let numFilled = Math.floor(pct * size); let numEmpty = size - numFilled; this.clearProgress(size); process.stderr.write(`[${"*".repeat(numFilled)}${" ".repeat(numEmpty)}] ${i} / ${max}`); } static async keepalive(funcs) { for (let f of funcs) { await f(); } } //Index a json endpoint that returns a {links} field. // //This function is faster than indexPath because it can guess the pages it //needs to retreive so that it can request all assets at once. // //This function assumes that the content from the inital request is the //first page, so starting on another page may cause issues. Consider //indexPath for that. // //Additional opts, besides default indexPath opts: // - chunksize[10]: How often to break apart concurrent requests static async indexPathFast(env, path$$1) { let opts = typeof env === "string" ? { env, path: path$$1 } : env; let index = new IndexObject(opts); return await index.fullResults(); } static isLocalEnv(env) { return !env || env === "LOCAL" || env === "LOC"; } static envName(env) { if (this.isLocalEnv(env)) return "LOCAL"; return env; } } class AbortError extends Error { constructor(message) { super(message); Error.captureStackTrace(this, this.constructor); this.name = "AbortError"; } } class APIError extends Error { constructor(response, opts, body) { super(chalk$1` {reset Request returned} {yellow ${response === null || response === void 0 ? void 0 : response.statusCode}}{ {green ${JSON.stringify(opts, null, 4)}} {green ${body}} {reset ${response.body}} =============================== {red ${!response.body ? "Request timed out" : "Bad response from API"}} =============================== `); this.response = response; this.opts = opts; this.body = body; //Error.captureStackTrace(this, this.constructor); this.name = "ApiError"; } } class UnconfiguredEnvError extends AbortError { constructor(env) { super("Unconfigured enviornment: " + env); this.name = "Unconfigured Env Error"; } } class ProtectedEnvError extends AbortError { constructor(env) { super("Protected enviornment: " + env); this.name = "Protected Env Error"; } } class FileTooLargeError extends Error { constructor(file) { super(`File ${file.parentAsset ? file.parentAsset.name : "(unknown)"}/${file.name} size is: ${file.sizeGB}g (> ~.2G)`); this.name = "File too large error"; } } class ResoultionError extends Error { constructor(name, env) { super(chalk$1`Error during name resolution: '{blue ${name}}' is not mapped on {green ${env}}`); this.name = "Name Resoultion Error"; } } class Collection { constructor(arr) { this.arr = arr; } [Symbol.iterator]() { return this.arr[Symbol.iterator](); } findById(id) { return this.arr.find(x => x.id == id); } findByName(name) { return this.arr.find(x => x.name == name); } findByNameContains(name) { return this.arr.find(x => x.name.includes(name)); } log() { for (let d of this) { if (d) { log(d.chalkPrint(true)); } else { log(chalk$1`{red (None)}`); } } } get length() { return this.arr.length; } } class RallyBase { static handleCaching() { if (!this.cache) this.cache = []; } static isLoaded(env) { if (!this.hasLoadedAll) return; return this.hasLoadedAll[env]; } static async getById(env, id, qs) { this.handleCaching(); for (let item of this.cache) { if (item.id == id && item.remote === env || `${env}-${id}` === item.metastring) return item; } let data = await lib.makeAPIRequest({ env, path: `/${this.endpoint}/${id}`, qs }); if (data.data) { let o = new this({ data: data.data, remote: env, included: data.included }); this.cache.push(o); return o; } } static async getByName(env, name, qs) { this.handleCaching(); for (let item of this.cache) { if (item.name === name && item.remote === env) { return item; } } let data = await lib.makeAPIRequest({ env, path: `/${this.endpoint}`, qs: { ...qs, filter: `name=${name}` + (qs && qs.filter ? qs.filter : "") } }); //TODO included might not wokr correctly here if (data.data[0]) { let o = new this({ data: data.data[0], remote: env, included: data.included }); this.cache.push(o); return o; } } static async getAllPreCollect(d) { return d; } static async getAll(env) { this.handleCaching(); let datas = await lib.indexPathFast({ env, path: `/${this.endpoint}`, pageSize: "50", qs: { sort: "id" } }); datas = await this.getAllPreCollect(datas); let all = new Collection(datas.map(data => new this({ data, remote: env }))); this.cache = [...this.cache, ...all.arr]; return all; } static async removeCache(env) { this.handleCaching(); this.cache = this.cache.filter(x => x.remote !== env); } //Specific turns name into id based on env //Generic turns ids into names async resolveApply(type, dataObj, direction) { let obj; if (direction == "generic") { obj = await type.getById(this.remote, dataObj.id); if (obj) { dataObj.name = obj.name; } else { throw new ResoultionError(`(id = ${dataObj.id})`, this.remote); } } else if (direction == "specific") { obj = await type.getByName(this.remote, dataObj.name); if (obj) { dataObj.id = obj.id; } else { throw new ResoultionError(dataObj.name, this.remote); } } return obj; } //Type is the baseclass you are looking for (should extend RallyBase) //name is the name of the field //isArray is true if it has multiple cardinailty, false if it is single //direction gets passed directly to resolveApply async resolveField(type, name, isArray = false, direction = "generic") { // ignore empty fields let field = this.relationships[name]; if (!(field === null || field === void 0 ? void 0 : field.data)) return; if (isArray) { return await Promise.all(field.data.map(o => this.resolveApply(type, o, direction))); } else { return await this.resolveApply(type, field.data, direction); } } cleanup() { for (let [key, val] of Object.entries(this.relationships)) { //Remove ids from data if (val.data) { if (val.data.id) { delete val.data.id; } else if (val.data[0]) { for (let x of val.data) delete x.id; } } delete val.links; } // organization is unused (?) delete this.relationships.organization; // id is specific to envs // but save source inside meta string in case we need it this.metastring = this.remote + "-" + this.data.id; delete this.data.id; // links too delete this.data.links; } } function sleep(time = 1000) { return new Promise(resolve => setTimeout(resolve, time)); } function* zip(...items) { let iters = items.map(x => x[Symbol.iterator]()); for (;;) { let r = []; for (let i of iters) { let next = i.next(); if (next.done) return; r.push(next.value); } yield r; } } function unordered(_x) { return _unordered.apply(this, arguments); } function _unordered() { _unordered = _wrapAsyncGenerator(function* (proms) { let encapsulatedPromises = proms.map(async (x, i) => [i, await x]); while (encapsulatedPromises.length > 0) { let [ind, result] = yield _awaitAsyncGenerator(Promise.race(encapsulatedPromises.filter(x => x))); yield result; encapsulatedPromises[ind] = undefined; } }); return _unordered.apply(this, arguments); } function* range(start, end) { if (end === undefined) { end = start; start = 0; } while (start < end) yield start++; } class IndexObject { //normal opts from any makeAPIRequest //Note that full_response and pages won't work. // //if you want to start from another page, use `opts.start` //opts.observe: async function(jsonData) => jsonData. Transform the data from the api //opts.maxParallelRequests: number of max api requests to do at once //opts.noCollect: return [] instead of the full data constructor(opts) { this.opts = opts; } linkToPage(page) { return this.baselink.replace(`page=1p`, `page=${page}p`); } async initializeFirstRequest() { //Create a copy of the options in case we need to have a special first request this.start = this.opts.start || 1; let initOpts = { ...this.opts }; if (this.opts.pageSize) { initOpts.qs = { ...this.opts.qs }; initOpts.qs.page = `${this.start}p${this.opts.pageSize}`; } this.allResults = []; //we make 1 non-parallel request to the first page so we know how to //format the next requests let json = await lib.makeAPIRequest(initOpts); if (this.opts.observe) json = await this.opts.observe(json); if (!this.opts.noCollect) this.allResults.push(json); this.baselink = json.links.first; this.currentPageRequest = this.start; this.hasHit404 = false; } getNextRequestLink() { this.currentPageRequest++; return [this.currentPageRequest, this.linkToPage(this.currentPageRequest)]; } ///promiseID is the id in `currentPromises`, so that it can be marked as ///done inside the promise array. promiseID is a number from 0 to ///maxparallel-1 async getNextRequestPromise(promiseID) { let [page, path_full] = this.getNextRequestLink(); return [promiseID, page, await lib.makeAPIRequest({ ...this.opts, path_full, fullResponse: true })]; } cancel() { this.willCancel = true; } async fullResults() { await this.initializeFirstRequest(); let maxParallelRequests = this.opts.maxParallelRequests || this.opts.chunksize || 5; let currentPromises = []; //generate the first set of requests. Everything after this will re-use these i promiseIDs for (let i = 0; i < maxParallelRequests; i++) { currentPromises.push(this.getNextRequestPromise(currentPromises.length)); } for (;;) { let [promiseID, page, requestResult] = await Promise.race(currentPromises.filter(x => x)); if (this.willCancel) { return null; } if (requestResult.statusCode === 404) { this.hasHit404 = true; } else if (requestResult.statusCode === 200) { let json = JSON.parse(requestResult.body); if (this.opts.observe) json = await this.opts.observe(json); if (!this.opts.noCollect) this.allResults.push(json); if (json.data.length === 0) this.hasHit404 = true; } else { throw new APIError(requestResult, `(unknown args) page ${page}`, null); } if (this.hasHit404) { currentPromises[promiseID] = null; } else { currentPromises[promiseID] = this.getNextRequestPromise(promiseID); } if (currentPromises.filter(x => x).length === 0) break; } let all = []; for (let result of this.allResults) { for (let item of result.data) { all.push(item); } } return all; } } function orderedObjectKeys(obj) { let keys = Object.keys(obj).sort(); let newDict = {}; for (let key of keys) { if (Array.isArray(obj[key])) { newDict[key] = obj[key].map(x => orderedObjectKeys(x)); } else if (typeof obj[key] === "object" && obj[key]) { newDict[key] = orderedObjectKeys(obj[key]); } else { newDict[key] = obj[key]; } } return newDict; } class Provider extends RallyBase { constructor({ data, remote }) { super(); this.data = data; this.meta = {}; this.remote = remote; } //cached async getEditorConfig() { if (this.editorConfig) return this.editorConfig; this.editorConfig = await lib.makeAPIRequest({ env: this.remote, path_full: this.data.links.editorConfig }); return this.editorConfig; } static async getAllPreCollect(providers) { return providers.sort((a, b) => { return a.attributes.category.localeCompare(b.attributes.category) || a.attributes.name.localeCompare(b.attributes.name); }); } async getFileExtension() { let map = { python: "py", text: "txt", getmap(key) { if (this.name === "Aurora") return "zip"; if (this.name === "Vantage") return "zip"; if (this.name === "ffmpeg") return "txt"; //if(String(this.name).toLowerCase().startsWith("msc")) return "json"; if (this[key]) return this[key]; return key; } }; let v = map.getmap(this.lang); //log(config) //log(this.name) //log(v) return v; } chalkPrint(pad = true) { let id = String(this.id); if (pad) id = id.padStart(4); return chalk`{green ${id}}: {blue ${this.category}} - {green ${this.name}}`; } } defineAssoc(Provider, "id", "data.id"); defineAssoc(Provider, "name", "data.attributes.name"); defineAssoc(Provider, "category", "data.attributes.category"); defineAssoc(Provider, "lang", "data.attributes.lang"); defineAssoc(Provider, "remote", "meta.remote"); defineAssoc(Provider, "editorConfig", "meta.editorConfig"); Provider.endpoint = "providerTypes"; class Notification extends RallyBase { constructor({ data, remote }) { super(); this.data = data; this.meta = {}; this.remote = remote; } static async getAllPreCollect(notifications) { return notifications.sort((a, b) => { return a.attributes.type.localeCompare(b.attributes.type) || a.attributes.name.localeCompare(b.attributes.name); }); } chalkPrint(pad = false) { let id = String("N-" + this.id); if (pad) id = id.padStart(4); return chalk`{green ${id}}: {blue ${this.type}} - {green ${this.name}}`; } } defineAssoc(Notification, "id", "data.id"); defineAssoc(Notification, "name", "data.attributes.name"); defineAssoc(Notification, "address", "data.attributes.address"); defineAssoc(Notification, "type", "data.attributes.type"); defineAssoc(Notification, "remote", "meta.remote"); Notification.endpoint = "notificationPresets"; class Tag extends RallyBase { constructor({ data, remote } = {}) { super(); this.meta = {}; this.remote = remote; this.data = data; //this.data.attributes.rallyConfiguration = undefined; //this.data.attributes.systemManaged = undefined; } chalkPrint(pad = true) { let id = String("T-" + this.remote + "-" + this.id); if (pad) id = id.padStart(10); let prefix = this.curated ? "blue +" : "red -"; return chalk`{green ${id}}: {${prefix}${this.name}}`; } static async create(env, name, { notCurated } = {}) { return new Tag({ data: await lib.makeAPIRequest({ env, path: `/${this.endpoint}`, method: "POST", payload: { data: { attributes: { name, curated: notCurated ? false : true }, type: "tagNames" } } }), remote: env }); } async curate() { this.curated = !this.curated; return await lib.makeAPIRequest({ env: this.remote, path: `/tagNames/${this.id}`, method: "PATCH", payload: { data: { attributes: { curated: this.curated }, type: "tagNames" } } }); } } defineAssoc(Tag, "id", "data.id"); defineAssoc(Tag, "attributes", "data.attributes"); defineAssoc(Tag, "relationships", "data.relationships"); defineAssoc(Tag, "name", "data.attributes.name"); defineAssoc(Tag, "curated", "data.attributes.curated"); defineAssoc(Tag, "remote", "meta.remote"); Tag.endpoint = "tagNames"; let home; if (os.homedir) { home = os.homedir(); } const colon = /:/g; const siloLike = /(silo\-\w+?)s?\/([^\/]+)\.([\w1234567890]+)$/g; function pathTransform(path$$1) { if (path$$1.includes(":")) { //Ignore the first colon in window-like filesystems path$$1 = path$$1.slice(0, 3) + path$$1.slice(3).replace(colon, "--"); } if (configObject.invertedPath) { path$$1 = path$$1.replace(siloLike, "$2-$1.$3"); } if (path$$1.includes("\\342\\200\\220")) { path$$1 = path$$1.replace("\\342\\200\\220", "‐"); } return path$$1; } function readFileSync(path$$1, options) { return fs__default.readFileSync(pathTransform(path$$1), options); } //Create writefilesync, with ability to create directory if it doesnt exist function writeFileSync(path$$1, data, options, dircreated = false) { path$$1 = pathTransform(path$$1); try { return fs__default.writeFileSync(path$$1, data, options); } catch (e) { if (dircreated) throw e; let directory = path.dirname(path$$1); try { fs__default.statSync(directory); throw e; } catch (nodir) { fs__default.mkdirSync(directory); return writeFileSync(path$$1, data, options, true); } } } class Rule extends RallyBase { constructor({ path: path$$1, data, remote, subProject } = {}) { super(); if (path$$1) { path$$1 = path.resolve(path$$1); try { let f = readFileSync(path$$1, "utf-8"); data = JSON.parse(readFileSync(path$$1, "utf-8")); } catch (e) { if (e.code === "ENOENT") { if (configObject.ignoreMissing) { this.missing = true; return undefined; } else { throw new AbortError("Could not load code of local file"); } } else { throw new AbortError(`Unreadable JSON in ${path$$1}. ${e}`); } } } this.meta = {}; this.subproject = subProject; if (!data) { data = Rule.newShell(); } this.data = data; this.remote = remote; delete this.data.relationships.transitions; delete this.data.meta; delete this.data.attributes.updatedAt; delete this.data.attributes.createdAt; this.isGeneric = !this.remote; } static newShell() { return { "attributes": { "description": "-", "priority": "PriorityNorm", "starred": false }, "relationships": {}, "type": "workflowRules" }; } async acclimatize(env) { this.remote = env; let preset = await this.resolveField(Preset, "preset", false, "specific"); let pNext = await this.resolveField(Rule, "passNext", false, "specific"); let eNext = await this.resolveField(Rule, "errorNext", false, "specific"); let proType = await this.resolveField(Provider, "providerType", false, "specific"); let proTag = await this.resolveField(Tag, "providerFilterTag", false, "specific"); if (proTag) { this.data.attributes.providerFilter = proTag.id; } else { this.data.attributes.providerFilter = null; } let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true, "specific"); let enterNotif = await this.resolveField(Notification, "enterNotifications", true, "specific"); let errorNotif = await this.resolveField(Notification, "errorNotifications", true, "specific"); let passNotif = await this.resolveField(Notification, "passNotifications", true, "specific"); } async saveA(env) { if (lib.isLocalEnv(env)) return; return await this.createOrUpdate(env); } async saveB(env) { if (!this.isGeneric) { await this.resolve(); } this.cleanup(); if (lib.isLocalEnv(env)) { log(chalk`Saving rule {green ${this.name}} to {blue ${lib.envName(env)}}.`); writeFileSync(this.localpath, JSON.stringify(orderedObjectKeys(this.data), null, 4)); } else { return await this.createOrUpdate(env); } } get immutable() { return false; } async createOrUpdate(env) { write(chalk`First pass rule {green ${this.name}} to {green ${env}}: `); await this.acclimatize(env); if (this.immutable) { log(chalk`{magenta IMMUTABLE}. Nothing to do.`); return; } //First query the api to see if this already exists. let remote = await Rule.getByName(env, this.name); this.idMap = this.idMap || {}; this.relationships.transitions = { data: await this.constructWorkflowTransitions() }; if (remote) { this.idMap[env] = remote.id; log(chalk`exists ${remote.chalkPrint(false)}`); if (configObject.skipStarred) { write("no starred, "); this.data.attributes.starred = undefined; } write("replace, "); let res = await lib.makeAPIRequest({ env, path: `/workflowRules/${this.idMap[env]}`, method: "PUT", payload: { data: this.data } }); } else { write("create, "); let res = await lib.makeAPIRequest({ env, path: `/workflowRules`, method: "POST", payload: { data: this.data } }); this.idMap[env] = res.data.id; } write("id "); log(this.idMap[env]); } async patchStrip() { delete this.data.attributes.createdAt; delete this.data.attributes.starred; delete this.data.attributes.updatedAt; this.nexts = this.data.relationships.dynamicNexts; delete this.data.relationships.dynamicNexts; // TEMP FIX FOR BUG IN SDVI if (this.relationships.passMetadata && this.relationships.passMetadata[0]) { log("HAS PASS"); log(this.name); log("HAS PASS"); } delete this.relationships.passMetadata; if (this.relationships.errorMetadata && this.relationships.errorMetadata[0]) { log("HAS PASS"); log(this.name); log("HAS PASS"); } delete this.relationships.errorMetadata; // This is commented out because it was fixed. //for(let key in this.relationships){ //let relationship = this.relationships[key]; //if(!relationship.data || relationship.data instanceof Array && !relationship.data[0]){ //delete this.relationships[key]; //} //} } async deleteRemoteVersion(env, id = null) { if (lib.isLocalEnv(env)) return false; if (!id) { let remote = await Rule.getByName(env, this.name); id = remote.id; } return await lib.makeAPIRequest({ env, path: `/workflowRules/${id}`, method: "DELETE" }); } async delete() { if (lib.isLocalEnv(this.remote)) return false; return await this.deleteRemoteVersion(this.remote, this.id); } get localpath() { return this._localpath || path.join(configObject.repodir, this.subproject || "", "silo-rules", this.name + ".json"); } async resolve() { let preset = await this.resolveField(Preset, "preset", false); //log(preset); let pNext = await this.resolveField(Rule, "passNext", false); let eNext = await this.resolveField(Rule, "errorNext", false); let proType = await this.resolveField(Provider, "providerType", false); let proTag = await this.resolveField(Tag, "providerFilterTag", false); if (proTag && this.data.attributes.providerFilter) { delete this.data.attributes.providerFilter; } //log("Dynamic nexts") let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true); //log(dynamicNexts); let enterNotif = await this.resolveField(Notification, "enterNotifications", true); let errorNotif = await this.resolveField(Notification, "errorNotifications", true); let passNotif = await this.resolveField(Notification, "passNotifications", true); //TODO Unsupported delete this.relationships["enterMetadata"]; delete this.relationships["errorMetadata"]; this.isGeneric = true; return { preset, proType, proTag, pNext, eNext, dynamicNexts, errorNotif, enterNotif, passNotif }; } chalkPrint(pad = true) { let id = String("R-" + (this.remote && this.remote + "-" + this.id || "LOCAL")); let sub = ""; if (this.subproject) { sub = chalk`{yellow ${this.subproject}}`; } if (pad) id = id.padStart(13); try { return chalk`{green ${id}}: ${sub}{blue ${this.name}}`; } catch (e) { return this.data; } } async constructWorkflowTransitions() { var _this$nexts; let transitions = []; let dynamicNexts = ((_this$nexts = this.nexts) === null || _this$nexts === void 0 ? void 0 : _this$nexts.data) || []; if (dynamicNexts.length == 0) return []; write(chalk`transition mapping: `); for (let transition of dynamicNexts) { write(chalk`{green ${transition.meta.transition}}:`); let filters = { toWorkflowRuleId: transition.id, name: transition.meta.transition, fromWorkflowRuleId: this.id }; let res = await lib.makeAPIRequest({ env: this.remote, path: `/workflowTransitions`, method: "GET", qs: { filter: JSON.stringify(filters) } }); let newTransitionId = 0; if (res.data.length > 0) { write(chalk`{blue found} `); let firstTransition = res.data[0]; newTransitionId = firstTransition.id; } else { write(chalk`{magenta create} `); let newTransitionPayload = { "attributes": { "name": filters.name }, "relationships": { "fromWorkflowRule": { "data": { "id": filters.fromWorkflowRuleId, "type": "workflowRules" } }, "toWorkflowRule": { "data": { "id": filters.toWorkflowRuleId, "type": "workflowRules" } } }, "type": "workflowTransitions" }; let newTransition = await lib.makeAPIRequest({ env: this.remote, path: `/workflowTransitions`, method: "POST", payload: { data: newTransitionPayload } }); newTransitionId = newTransition.data.id; } transitions.push({ "id": newTransitionId, "type": "workflowTransitions" }); write(chalk`{yellow ${newTransitionId}}, `); } write(chalk`t. done, `); return transitions; } } defineAssoc(Rule, "name", "data.attributes.name"); defineAssoc(Rule, "description", "data.attributes.description"); defineAssoc(Rule, "id", "data.id"); defineAssoc(Rule, "relationships", "data.relationships"); defineAssoc(Rule, "isGeneric", "meta.isGeneric"); defineAssoc(Rule, "remote", "meta.remote"); defineAssoc(Rule, "subproject", "meta.project"); defineAssoc(Rule, "idMap", "meta.idMap"); defineAssoc(Rule, "nexts", "meta.nexts"); Rule.endpoint = "workflowRules"; const inquirer = importLazy("inquirer"); const readdir = importLazy("recursive-readdir"); let hasAutoCompletePrompt = false; function addAutoCompletePrompt() { if (hasAutoCompletePrompt) return; hasAutoCompletePrompt = true; inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt")); } async function $api(propArray) { let q; q = await inquirer.prompt([{ type: "input", name: "company", message: `What is your company?`, default: `discovery` }]); let company = q.company; const defaults = { DEV: `https://${company}-dev.sdvi.com/api/v2`, UAT: `https://${company}-uat.sdvi.com/api/v2`, QA: `https://${company}-qa.sdvi.com/api/v2`, PROD: `https://${company}.sdvi.com/api/v2` }; if (propArray && propArray[1]) { q = { envs: [propArray[1]] }; } else { //Create a checkbox prompt to choose enviornments q = await inquirer.prompt([{ type: "checkbox", name: "envs", message: `What enviornments would you like to configure?`, choices: Object.keys(defaults).map(name => ({ name, checked: true })) }]); } //Each env should ask 2 for two things: The url and the key. let questions = q.envs.map(env => { let defaultKey = process.env[`rally_api_key_${env}`]; if (configObject && configObject.api && configObject.api[env]) { defaultKey = configObject.api[env].key; } return [{ type: "input", name: `api.${env}.url`, message: `What is the api endpoint for ${env}?`, default: defaults[env] }, { type: "input", name: `api.${env}.key`, message: `What is your api key for ${env}?`, default: defaultKey }]; }); //flatten and ask questions = [].concat(...questions); q = await inquirer.prompt(questions); if (propArray) { q.api = { ...configObject.api, ...q.api }; } return q; } async function $chalk(propArray) { return { chalk: await askQuestion("Would you like chalk enabled (Adds coloring)?") }; } async function $restrictUAT(propArray) { return { restrictUAT: await askQuestion("Would you like to protect UAT?") }; } async function $repodir(propArray) { return await inquirer.prompt([{ type: "input", name: `repodir`, message: `Where is your rally repository (empty for N/A)?`, default: process.env["rally_repo_path"] }]); } async function $appName(propArray) { let defaultAppName = "cmdline-" + (process.env.USERNAME || process.env.LOGNAME); let project = await askInput("Application name?", defaultAppName); if (project === "none" || project === "-" || project === "" || !project) { project = null; } return { appName: project }; } async function $project(propArray) { let project = await askInput("Subproject directory?"); if (project === "none" || project === "-" || project === "" || !project) { project = null; } return { project }; } async function $defaultEnv(propArray) { return await inquirer.prompt([{ type: "input", name: `defaultEnv`, message: `Default enviornment?`, default: "DEV" }]); } //Internal usage/testing async function selectProvider(providers, autoDefault = false) { addAutoCompletePrompt(); let defaultProvider = providers.findByName("SdviEvaluate"); if (autoDefault) { return defaultProvider; } else { let choices = providers.arr.map(x => ({ name: x.chalkPrint(true), value: x })); let q = await inquirer.prompt([{ type: "autocomplete", name: "provider", default: defaultProvider, source: async (sofar, input) => { return choices.filter(x => input ? x.value.name.toLowerCase().includes(input.toLowerCase()) : true); } }]); return q.provider; } } async function loadLocals(path$$1, Class) { let basePath = configObject.repodir; let objs = (await readdir(basePath)).filter(name => name.includes(path$$1)).filter(name => !path.basename(name).startsWith(".")).map(name => new Class({ path: name })); return objs; } async function selectLocal(path$$1, typeName, Class, canSelectNone = true) { addAutoCompletePrompt(); let objs = await loadLocals(path$$1, Class); let objsMap = objs.map(x => ({ name: x.chalkPrint(true), value: x })); return await selectLocalMenu(objsMap, typeName, canSelectNone); } async function selectLocalMenu(objs, typeName, canSelectNone = true) { let none = { name: chalk` {red None}: {red None}`, value: null }; if (canSelectNone) objs.unshift(none); let q = await inquirer.prompt([{ type: "autocomplete", name: "obj", message: `What ${typeName} do you want?`, source: async (sofar, input) => { return objs.filter(x => input ? x.name.toLowerCase().includes(input.toLowerCase()) : true); } }]); return q.obj; } async function selectPreset({ purpose = "preset", canSelectNone }) { return selectLocal("silo-presets", purpose, Preset, canSelectNone); } async function selectRule({ purpose = "rule", canSelectNone }) { return selectLocal("silo-rules", purpose, Rule, canSelectNone); } async function askInput(question, def) { return (await inquirer.prompt([{ type: "input", name: "ok", message: question, default: def }])).ok; } async function askQuestion(question) { return (await inquirer.prompt([{ type: "confirm", name: "ok", message: question }])).ok; } async function saveConfig(newConfigObject, { ask = true, print = true } = {}) { //Create readable json and make sure the user is ok with it let newConfig = JSON.stringify(newConfigObject, null, 4); if (print) log(newConfig); //-y or --set will make this not prompt if (ask && !(await askQuestion("Write config to disk?"))) return; fs.writeFileSync(configFile, newConfig, { mode: 0o600 }); log(chalk`Created file {green ${configFile}}.`); } var configHelpers = /*#__PURE__*/Object.freeze({ inquirer: inquirer, addAutoCompletePrompt: addAutoCompletePrompt, $api: $api, $chalk: $chalk, $restrictUAT: $restrictUAT, $repodir: $repodir, $appName: $appName, $project: $project, $defaultEnv: $defaultEnv, selectProvider: selectProvider, loadLocals: loadLocals, selectLocal: selectLocal, selectLocalMenu: selectLocalMenu, selectPreset: selectPreset, selectRule: selectRule, askInput: askInput, askQuestion: askQuestion, saveConfig: saveConfig }); class File extends RallyBase { constructor({ data, remote, included, parent }) { super(); this.data = data; this.meta = {}; this.remote = remote; this.parentAsset = parent; } chalkPrint(pad = false) { let id = String("F-" + (this.remote && this.remote + "-" + this.id || "LOCAL")); if (pad) id = id.padStart(15); return chalk`{green ${id}}: {blue ${this.data.attributes ? this.name : "(lite file)"}} {red ${this.sizeHR}}`; } canBeDownloaded() { return this.sizeGB <= .2; } async getContent(force = false, noRedirect = false, timeout = undefined) { if (!this.canBeDownloaded() && !force && !noRedirect) { throw new FileTooLargeError(this); } let d = lib.makeAPIRequest({ env: this.remote, fullPath: this.contentLink, qs: { "