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
JavaScript
;
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