UNPKG

dt-app

Version:

The Dynatrace App Toolkit is a tool you can use from your command line to create, develop, and deploy apps on your Dynatrace environment.

576 lines (562 loc) 16.8 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/migrations/1.3.0/index.ts var import_fs2 = require("fs"); var import_devkit = require("@dynatrace/devkit"); var import_path2 = require("path"); // src/utils/logger.ts var import_chalk = require("chalk"); var import_lodash = require("lodash"); // src/utils/logfile-stream.ts var import_fs = require("fs"); var import_promises = require("fs/promises"); var import_path = require("path"); var fileNamePattern = new RegExp(/[0-9]{4}-[0-9]{2}-[0-9]{2}_log.txt/); var pad = (num) => (num > 9 ? "" : "0") + num; var fileNameGenerator = (time) => { return `${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad( time.getDate() )}_log.txt`; }; var extractDateFromFileName = (filename) => { try { const parts = filename.split("-"); return new Date( Number(parts[0]), Number(parts[1]), Number(parts[2].split("_")[0]) ); } catch (_err) { return void 0; } }; var LogFileStreamStub = class { /** Stub for setup function */ async setup(_root) { return Promise.resolve(); } /** Stub that does not write to file */ write(_text) { } // eslint-disable-line @typescript-eslint/no-empty-function /** Stub that returns empty string */ getLogFile() { return ""; } }; var LogFileStreamImpl = class { /** Location where log files should be stored. */ logFolder; /** Log file path used by the current process. */ logFile; /** Actual stream that is used to write the logs into the file. */ stream; /** Initialize file stream for logging. */ async setup(root) { this.logFolder = (0, import_path.join)(root, ".dt-app/logs"); if (!(0, import_fs.existsSync)(this.logFolder)) { (0, import_fs.mkdirSync)(this.logFolder, { recursive: true }); } await this.rotate(); } /** Rotate file. */ async rotate() { this.logFile = (0, import_path.join)(this.logFolder, fileNameGenerator(/* @__PURE__ */ new Date())); if (!(0, import_fs.existsSync)(this.logFile)) { const files = (0, import_fs.readdirSync)(this.logFolder).map((f) => f.trim()).filter((f) => fileNamePattern.test(f)); if (files.length >= 10) { const oldestFile = files.map((f) => { return { path: f, date: extractDateFromFileName(f) }; }).filter((f) => f.date).sort((a, b) => a.date.getTime() - b.date.getTime())[0].path; (0, import_promises.unlink)((0, import_path.join)(this.logFolder, oldestFile)); } (0, import_fs.closeSync)((0, import_fs.openSync)(this.logFile, "w")); } await new Promise((resolve) => { this.stream = (0, import_fs.createWriteStream)(this.logFile, { encoding: "utf-8", flags: "a" // Append to file }); this.stream.on("open", () => resolve()); }); } /** Write log to file. */ write(text) { if (this.stream) { this.stream.write(text); } } /** Returns the path to the current log file. */ getLogFile() { return this.logFile; } }; async function initLogFileStream(root) { logFileStream = new LogFileStreamImpl(); return logFileStream.setup(root); } var logFileStream = new LogFileStreamStub(); // src/utils/logger.ts var import_os = require("os"); // src/utils/spinner-service.ts var import_ora = __toESM(require("ora")); // src/utils/terminal-queue.ts var queue = []; var isProcessing = false; function enqueue(func) { return new Promise((resolve, reject) => { queue.push(async () => { try { const result = await func(); resolve(result); } catch (error) { reject(error); } }); processQueue(); }); } async function processQueue() { if (isProcessing) { return; } isProcessing = true; while (queue.length > 0) { const queueItem = queue.shift(); await queueItem(); } isProcessing = false; } async function terminalAction(promptFunction) { return enqueue(promptFunction); } // src/utils/spinner-service.ts var verbose = process.env.DT_APP_DEACTIVATE_SPINNER !== "true"; var spinnerDefaultConfig = { // Frames: symbols, interval: 80 }; var concurrentSpinners = []; var spinnerState = "idle"; var multiSpinner = (0, import_ora.default)({ spinner: { frames: [""], interval: spinnerDefaultConfig.interval }, isSilent: !verbose }); var getMultiSpinnerFrame = () => ` ${concurrentSpinners.map((s) => s.getFrame()).join("\n")}`; function updateMultiSpinner() { multiSpinner.text = getMultiSpinnerFrame(); } var multiSpinnerUpdateInterval = void 0; function startMultiSpinner() { if (verbose && spinnerState !== "running" && !isProcessing) { if (multiSpinnerUpdateInterval === void 0) { multiSpinnerUpdateInterval = setInterval( updateMultiSpinner, spinnerDefaultConfig.interval ); } multiSpinner.start(); spinnerState = "running"; } } function pauseMultiSpinner() { if (spinnerState === "running") { spinnerState = "paused"; multiSpinner.stop(); } } function resumeMultiSpinner() { if (spinnerState === "paused" && !isProcessing) { spinnerState = "running"; multiSpinner.start(); } } function stopMultiSpinner() { spinnerState = "idle"; multiSpinner.stop(); concurrentSpinners.forEach((s) => s.abort()); if (multiSpinnerUpdateInterval !== void 0) { clearInterval(multiSpinnerUpdateInterval); multiSpinnerUpdateInterval = void 0; } } var SpinnerImpl = class { spinner; /** * @param options {@link ora.Options} */ constructor(options) { this.spinner = (0, import_ora.default)({ ...options, isSilent: !verbose }); } // Documented in interface // eslint-disable-next-line require-jsdoc get text() { return this.spinner.text; } // Documented in interface // eslint-disable-next-line require-jsdoc set text(text) { this.spinner.text = text; } /** * Stops the spinner and removes it from the concurrent spinner Array */ cleanup() { const index = concurrentSpinners.indexOf(this); if (index > -1) { if (this.spinner.isSpinning) { this.spinner.stop(); } concurrentSpinners.splice(index, 1); } if (concurrentSpinners.length === 0) { stopMultiSpinner(); } } /** * @returns the next frame of the spinner */ getFrame() { return this.spinner.frame(); } // Documented in interface // eslint-disable-next-line require-jsdoc start(text) { if (text) { this.text = text; } if (!concurrentSpinners.includes(this)) { concurrentSpinners.push(this); } startMultiSpinner(); return this; } /** * Stops the spinner and executes cleanup of this instance. * @param text stop text for the spinner * @param stopType either {@link SpinnerStopType} or {@link ora.PersistOptions} * @returns The spinner instance. */ _stop(text, stopType = "abort") { this.text = text ?? this.text; pauseMultiSpinner(); switch (stopType) { case "success": this.spinner.succeed(this.text); break; case "info": this.spinner.info(this.text); break; case "fail": this.spinner.fail(this.text); break; case "warn": this.spinner.warn(this.text); break; case "abort": this.spinner.stop(); break; default: this.spinner.stopAndPersist(stopType); } this.cleanup(); resumeMultiSpinner(); return this; } // Documented in interface // eslint-disable-next-line require-jsdoc stop(text, stopOptions = {}) { return this._stop(text, stopOptions); } // Documented in interface // eslint-disable-next-line require-jsdoc abort() { return this._stop(void 0, "abort"); } // Documented in interface // eslint-disable-next-line require-jsdoc info(text) { return this._stop(text, "info"); } // Documented in interface // eslint-disable-next-line require-jsdoc succeed(text) { return this._stop(text, "success"); } // Documented in interface // eslint-disable-next-line require-jsdoc fail(text) { return this._stop(text, "fail"); } // Documented in interface // eslint-disable-next-line require-jsdoc warn(text) { return this._stop(text, "warn"); } }; var SpinnerService = { /** * Creates an inactive spinner. * @param options - Optional {@link Options} for the spinner. * @returns A new Spinner instance. */ create: (options = {}) => new SpinnerImpl({ ...options, interval: spinnerDefaultConfig.interval }), /** * Creates and starts a new spinner. * @param text - Optional text to be displayed. * @param options - Optional {@link Options} for the spinner. * @returns A new Spinner instance. */ start: (text, options = {}) => new SpinnerImpl({ ...options, interval: spinnerDefaultConfig.interval }).start(text), /** * Pauses all running spinners. */ pause: pauseMultiSpinner, /** * Resumes all paused spinners. */ resume: resumeMultiSpinner, /** * Resets all running or idle spinners. */ reset: stopMultiSpinner, /** * Marks all spinners as succeeded with optional text. * @param text - Optional text to be displayed. */ successAll: (text) => concurrentSpinners.forEach((s) => s.succeed(text)), /** * Marks all spinners as failed with optional text. * @param text - Optional text to be displayed. */ failAll: (text) => concurrentSpinners.forEach((s) => s.fail(text)), disable: () => { verbose = false; stopMultiSpinner(); }, enable: () => { verbose = true; }, getInterval: () => spinnerDefaultConfig.interval, _getMockedSpinner: function(text) { const mockSpinner = { abort: (..._) => mockSpinner, fail: (..._) => mockSpinner, info: (..._) => mockSpinner, start: (..._) => mockSpinner, stop: (..._) => mockSpinner, succeed: (..._) => mockSpinner, text: text ?? "", warn: (..._) => mockSpinner }; return mockSpinner; } }; // src/utils/logger.ts var WaveLogger = class { boxSize = 80; warningBoxSize = 90; /** Disables logging */ disabled = false; /** Loading spinner instance */ spinner = { create: () => SpinnerService.create(), disable: SpinnerService.disable, start: (text) => SpinnerService.start(text), pause: SpinnerService.pause, resume: SpinnerService.resume, abort: SpinnerService.reset }; /** Toggle if the logger should log */ disable() { this.disabled = true; SpinnerService.disable(); } /** Log something to the cli */ log(text) { this._log(`${this._padString(text, 1)}`, "INFO"); } /** Prints a debug message */ debug(text, context) { if (global.VERBOSE_MODE && context.startsWith(global.VERBOSE_MODE.replace("%", ""))) { this.spinner.pause(); console.log( `${(0, import_chalk.yellow)("DEBUG")}${this._padString(`[${context}] ${text}`, 1)}` ); this.debounceTimeout = setTimeout(() => { this.spinner.resume(); }, SpinnerService.getInterval()); } this._printToFile(`[${context}] ${text}`, "DEBUG"); } /** Log level information (verbose output) */ info(text, pad2) { this._log( pad2 === void 0 || pad2 ? this._padString(text, 0) : text, "INFO" ); } /** Log level for successful tasks */ success(text) { this._log((0, import_chalk.green)(this._padString(text, 0)), "INFO"); } /** Log level information (verbose output) */ error(text) { this._log((0, import_chalk.red)(this._padString(text, 0)), "ERROR"); } /** Log level for warnings */ warn(text) { const boxedText = ` ${(0, import_chalk.yellow)( "Warning " + (0, import_lodash.repeat)("─", this.warningBoxSize - 8) )} ${(0, import_chalk.yellow)(this._padString(text))} ${(0, import_chalk.yellow)( (0, import_lodash.repeat)("─", this.warningBoxSize) )} `; this._log(boxedText, "WARNING"); } /** Logs the text in a box */ logBox(text) { if (this.disabled) { return; } const boxedText = ` ${(0, import_chalk.magenta)((0, import_lodash.repeat)("─", this.boxSize))} ${(0, import_chalk.magenta)( this._padString(text) )} ${(0, import_chalk.magenta)((0, import_lodash.repeat)("─", this.boxSize))} `; this._log(boxedText, "INFO"); } /** Cleanup logger. Used before system.exit() */ clean() { this.spinner.abort(); } /** Initialize log file stream */ async initLogFile(root) { if (!this.disabled) { await initLogFileStream(root); } } debounceTimeout; /** Logs the text and pauses a loading spinner in between if there is one */ _log(text, scope) { if (this.disabled) { return; } if (this.debounceTimeout) { clearTimeout(this.debounceTimeout); } this.spinner.pause(); if (scope === "ERROR") { console.log(text); } else { terminalAction(() => Promise.resolve(console.log(text))); } this.debounceTimeout = setTimeout(() => { this.spinner.resume(); }, SpinnerService.getInterval()); this._printToFile(text, scope); } /** Logs the text with timestamp prefix to file */ _printToFile(text, scope) { logFileStream.write( `${getOffsetTime().toISOString()} ${scope} ${stripAnsi(text)} ` ); } /** Cleans a string and adds the necessary space */ // eslint-disable-next-line require-jsdoc, @typescript-eslint/typedef _padString(text, start = 2) { return `${text}`.split(/[\n\r]/).map((line) => `${(0, import_lodash.repeat)(" ", start)}${line}`).join(import_os.EOL); } }; function stripAnsi(text) { const pattern = [ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))" ].join("|"); return text.replace(new RegExp(pattern, "g"), ""); } function getOffsetTime() { const now = /* @__PURE__ */ new Date(); const timezoneOffset = now.getTimezoneOffset() * 6e4; now.setTime(now.getTime() - timezoneOffset); return now; } var logger = new WaveLogger(); // src/migrations/1.3.0/index.ts var migration = async (fileMap, options) => { const appConfigPath = (0, import_path2.normalize)((0, import_path2.join)(options.root, "app.config.json")); if (!(0, import_fs2.existsSync)(appConfigPath)) { return fileMap; } const appConfigContent = (0, import_fs2.readFileSync)(appConfigPath, "utf-8"); let appConfig; try { appConfig = JSON.parse(appConfigContent); } catch (error) { return fileMap; } if (!appConfig.$schema) { const updatedConfig = { $schema: "./.dt-app/app.config.schema.json", ...appConfig }; fileMap["app.config.json"].mode = import_devkit.FileMode.updated; fileMap["app.config.json"].content = Buffer.from( JSON.stringify(updatedConfig, null, 2) ); } logger.log("Please enable autocompletion by running any dt-app command."); return fileMap; }; module.exports = migration; /** * @license * Copyright 2022 Dynatrace LLC * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //# sourceMappingURL=index.js.map