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.

1,621 lines (1,557 loc) 76.7 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/0.119.0/index.ts var import_fs13 = require("fs"); // src/utils/config/get-dt-app-file-config.ts var import_path12 = require("path"); // src/utils/config/extract-dt-app-config-from-ts.ts var import_path10 = 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((resolve2) => { this.stream = (0, import_fs.createWriteStream)(this.logFile, { encoding: "utf-8", flags: "a" // Append to file }); this.stream.on("open", () => resolve2()); }); } /** 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((resolve2, reject) => { queue.push(async () => { try { const result = await func(); resolve2(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/utils/node-resolve-package.ts function nodeResolvePackage(pck, cwd) { return require.resolve(pck, { paths: [cwd] }); } // src/utils/errors.ts var import_path2 = require("path"); var import_chalk2 = require("chalk"); var CREDENTIALS_ERROR = `Authentication failed! Please make sure that: * The provided client ID and the secret are correct. * The provided client ID has needed scopes. Deploy scopes: * app-engine:apps:run * app-engine:apps:install * app-engine:apps:delete Uninstall scopes: * app-engine:apps:delete Telemetry scopes: * app-engine:apps:run Development server needs all the scopes specified in the app.config. For more details on how you can configure authentication, see ${"https://dt-url.net/qw024j8" /* APP_TOOLKIT_CONFIGURATION */} `; function sanitizeErrorMessage(rawErrorMessage) { if (!rawErrorMessage) { return ""; } return rawErrorMessage.replace(/\(|\)/g, "").split(" ").filter(Boolean).map((element, i) => { if (i === 0 && element.toLocaleLowerCase() === "error:") { return ""; } return element; }).map((element) => { if (element.includes("http") || element.includes("://")) { return ""; } return element; }).map((element) => { if (element.includes(import_path2.sep)) { return `${element.replace(/["']/g, "").split(import_path2.sep).pop()}`; } return element; }).join(" ").substring(0, 200).trim(); } function toError(e) { return e instanceof Error ? e : new Error(String(e)); } function throwWithDynatraceErrorCode(error, errorCode) { throw new Error(`ERROR [${(0, import_chalk2.green)(errorCode)}]: ${error.message}`); } // src/plugin/require-plugins.ts var DEBUG_CONTEXT = "PLUGIN"; function requirePlugins(options) { return options.plugins.map((p) => requirePlugin(p, options.root)).filter((p) => !!p); } function requirePlugin(plugin, root) { try { return require(nodeResolvePackage(plugin, root)).default; } catch (e) { logger.warn(`Could not require plugin ${plugin}. It will be ignored.`); logger.debug(toError(e).toString(), DEBUG_CONTEXT); } return void 0; } // src/build/diagnostic.ts var import_path3 = require("path"); // src/utils/import-typescript.ts var typescript; function resolveTypescript(root) { if (!typescript) { typescript = require(nodeResolvePackage("typescript", root)); } return typescript; } // src/build/diagnostic.ts function fromEsbuildMessage(message, category) { return { category, text: message.text, filePath: message.location?.file ? (0, import_path3.resolve)(message.location.file) : void 0, line: message.location?.line ?? 1, column: (message.location?.column ?? 0) + 1 }; } function fromTypeScriptDiagnostic(tsDiagnostic, root) { const ts = resolveTypescript(root); const code = `TS${tsDiagnostic.code}`; const text = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, "\n"); const category = tsDiagnostic.category === ts.DiagnosticCategory.Error ? "Error" /* error */ : "Warning" /* warning */; if (tsDiagnostic.file) { const { line, character } = ts.getLineAndCharacterOfPosition( tsDiagnostic.file, tsDiagnostic.start ); return { code, category, text, filePath: tsDiagnostic.file.fileName, column: character + 1, line: line + 1 }; } return { code, category, text, filePath: void 0, line: 1, column: 1 }; } // src/build/compile.ts var import_path8 = require("path"); // src/utils/run-esbuild.ts var import_path7 = require("path"); // src/utils/exclude-vendor-from-source-map.ts var import_fs2 = require("fs"); var EMPTY_SOURCEMAP = "//# sourceMappingURL=data:application/json;base64,ewogICJtYXBwaW5ncyI6ICJBQUFBQSIsCiAgInNvdXJjZXMiOiBbIiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiIl0sCiAgIm5hbWVzIjogWyIiXSwKICAidmVyc2lvbiI6IDMsCiAgImZpbGUiOiAiIgp9"; var excludeVendorFromSourceMapPlugin = () => ({ name: "excludeVendorFromSourceMap", setup(build) { build.onLoad({ filter: /node_modules/ }, (args) => { if (args.path.endsWith(".js")) { return { contents: `${(0, import_fs2.readFileSync)(args.path, "utf8")} ${EMPTY_SOURCEMAP}`, loader: "default" }; } }); } }); // src/build/utils.ts var import_fast_glob2 = __toESM(require("fast-glob")); // src/utils/unix-join.ts function unixJoin(parts, options) { const arrayParts = Array.isArray(parts) ? parts : parts.split(/[\/\\]/g); const result = arrayParts.map((part) => part ? part.replace(/\\/g, "/") : "").join("/").replace(/\/\.\//g, "/").replace(/(?<!:)\/{2,}/g, "/").replace(/^.\//, ""); if (options?.keepFullPath) { return result; } else { return result.replace(/^([a-zA-Z]):/, ""); } } // src/utils/request.ts var import_undici = require("undici"); async function dtFetch(url, options) { return (0, import_undici.fetch)(url, options); } // src/dev/server.ts var import_fastify = __toESM(require("fastify")); var import_ws = __toESM(require("ws")); // src/utils/clouddev/codespaces.ts var codespacesEnv = process.env.CODESPACE_NAME && process.env.CODESPACE_NAME.trim() !== "" ? process.env.CODESPACE_NAME : void 0; // src/utils/clouddev/gitpod.ts var gitpodEnv = process.env.GITPOD_WORKSPACE_URL && process.env.GITPOD_WORKSPACE_URL.trim() !== "" ? process.env.GITPOD_WORKSPACE_URL : void 0; // src/utils/clouddev/cloud-dev.ts var customEnv = process.env.DT_APP_DEV_ENVIRONMENT_URL && process.env.DT_APP_DEV_ENVIRONMENT_URL.trim() !== "" ? process.env.DT_APP_DEV_ENVIRONMENT_URL : void 0; // src/utils/generator/generator.ts var selfmonScriptContentCache; async function selfmonAgentScript(url) { if (selfmonScriptContentCache) { return selfmonScriptContentCache; } const response = await dtFetch(url); selfmonScriptContentCache = await response.text(); return selfmonScriptContentCache; } // src/utils/generator/utility.ts var import_chalk3 = __toESM(require("chalk")); async function isUrlReachable(url) { try { if (!url.includes("http")) { return 3 /* MisconfiguredURL */; } const response = await dtFetch(url); const responseCode = response.status; const contentType = response.headers.get("content-type"); const isJavascript = contentType?.includes("javascript"); const content = await response.text(); const includesCUC = content.includes("cuc="); if (responseCode === 404) { return 2 /* NotFound */; } if (!isJavascript) { return 2 /* NotFound */; } if (!includesCUC) { return 2 /* NotFound */; } await selfmonAgentScript(url); return 0 /* Reachable */; } catch (e) { return 1 /* Unreachable */; } } // src/utils/layout.ts var import_chalk4 = __toESM(require("chalk")); // src/utils/file-map/file-map.ts var import_chalk5 = __toESM(require("chalk")); var import_path6 = require("path"); var import_promises2 = require("fs/promises"); // src/utils/file-map/utils.ts function normalizeUnixFilePath(path) { if (path.startsWith("../")) { return path; } if (path.startsWith("./")) { return path.slice(1); } return path.startsWith("/") ? path : "/" + path; } // src/utils/reporting/reporting.ts var import_openkit_js = require("@dynatrace/openkit-js"); var import_lodash2 = require("lodash"); var import_os3 = require("os"); var import_path5 = require("path"); // src/utils/execute-command.ts var import_commander = require("commander"); // src/dev/csp/csp.ts function getTenantAndHostNameFromEnvUrl(environmentUrl) { const parsed = new URL(environmentUrl); const [tenant, ...hostnameParts] = parsed.hostname.split("."); if (!hostnameParts || hostnameParts.length < 1) { throw new Error(`Invalid environment URL: "${environmentUrl}"`, { cause: 258 /* USER */ }); } return { tenant, hostname: hostnameParts.join(".") }; } // src/utils/read-json.ts var import_fs3 = require("fs"); function readJson(file) { if (!(0, import_fs3.existsSync)(file) && !(0, import_fs3.lstatSync)(file, { throwIfNoEntry: false })?.isFile()) { throw new Error(`[read-json.ts] File does not exist: ${file}`, { cause: 257 /* INTERNAL */ }); } const content = (0, import_fs3.readFileSync)(file, "utf-8"); return JSON.parse(content); } // src/utils/reporting/reporting.ts var import_jsonwebtoken = require("jsonwebtoken"); // src/utils/reporting/reporting-utils.ts var import_path4 = require("path"); var import_os2 = require("os"); var SESSION_FILE = (0, import_path4.join)((0, import_os2.homedir)(), ".dt-app", ".session"); function transformToObjectIfError(obj) { if (obj instanceof Error) { return { error: { name: obj.name, message: obj.message, stack: obj.stack, cause: obj.cause } }; } else { return obj; } } function deepCleanTelemetryPayload(obj) { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [ key, cleanValueOfTelemetryPayloadObject(value) ]) ); } function cleanValueOfTelemetryPayloadObject(value) { if (value === null || value === void 0) { return value; } if (Array.isArray(value)) { return value.map((item) => cleanValueOfTelemetryPayloadObject(item)); } if (typeof value === "object") { return deepCleanTelemetryPayload(value); } if (typeof value === "string") { return sanitizeErrorMessage(value); } return value; } function extractSessionTypeFromEnvUrl(url) { if (url.includes("dev")) { return "dev"; } else if (url.includes("hardening")) { return "hardening"; } else { return "prod"; } } // src/utils/reporting/reporting.ts var UUID_FILE = (0, import_path5.join)((0, import_os3.homedir)(), ".dt-app", ".uuid"); var session; var sessionId; var userId; var isInitialized = false; var cachedTelemetryConfig; function sendTelemetryBizEvent(bizEvent, options) { if (!isInitialized || !session || !sessionId) { return; } const processedPayload = deepCleanTelemetryPayload( transformToObjectIfError(bizEvent.payload || {}) ); const environmentUrl = options?.environmentUrl || cachedTelemetryConfig?.environmentUrl || ""; const appId = options?.app?.id || cachedTelemetryConfig.appId || ""; const appVersion = options?.app?.version || cachedTelemetryConfig.appVersion || ""; const nodejsVersion = process.version.charAt(0) === "v" ? process.version.substring(1) : process.version; const tenantInformation = environmentUrl ? { tenantId: getTenantAndHostNameFromEnvUrl(environmentUrl).tenant, sessionType: extractSessionTypeFromEnvUrl(environmentUrl) } : { tenantId: "", sessionType: "" }; const userInformation = { ...userId && { userId }, userType: cachedTelemetryConfig.userType || "" }; const enrichedBizEvent = { sessionId: sessionId.toString(), "event.type": `dt-app.${bizEvent["event.category"]}.${bizEvent.name}`, "event.category": bizEvent["event.category"], "event.provider": "dt-app", operatingSystemVersion: (0, import_os3.version)(), nodejsVersion, appId, appVersion, cliVersion: global.DT_APP_VERSION, ...tenantInformation, ...userInformation, ...processedPayload }; session.sendBizEvent(enrichedBizEvent["event.type"], enrichedBizEvent); } // src/utils/file-map/file-map.ts var import_fast_glob = __toESM(require("fast-glob")); var import_fs4 = require("fs"); var renderFileModeLabel = { created: import_chalk5.default.green`CREATED:`, updated: "UPDATED:", deleted: import_chalk5.default.red`DELETED:`, copied: "COPIED:" }; function createFileManager(initFileMap) { let fileMap = {}; const fileManager = { fileExists, throwIfFileDoesNotExist, getFile, getFilePathsByRegex, getFileMap, getFileName, setFile, setFiles, mapEsbuildOutputFiles, deleteFile, writeFileToDisc, writeFileMapToDisc, addAssetFiles, resetFileMap }; if (initFileMap) { setFiles(initFileMap); } return fileManager; function fileExists(filepath) { const path = normalizeUnixFilePath(filepath); const file = fileMap[path]; return !!file; } function throwIfFileDoesNotExist(filepath) { const path = normalizeUnixFilePath(filepath); if (!fileExists(filepath)) { sendTelemetryBizEvent({ "event.category": "crash" /* CRASH */, name: "fileManager.setFile", payload: { error: `Failed to set file ${filepath} with fileManager.` } }); throw new Error(`File ${path} does not exist in fileMap!`); } } function getFile(filepath) { filepath = normalizeUnixFilePath(unixJoin(filepath)); throwIfFileDoesNotExist(filepath); return fileMap[filepath]; } function getFilePathsByRegex(regex) { return Object.keys(fileMap).filter((filePath) => filePath.match(regex)); } function getFileMap() { return fileMap; } function getFileName(filepath) { filepath = normalizeUnixFilePath(unixJoin(filepath)); throwIfFileDoesNotExist(filepath); return (0, import_path6.basename)(filepath); } function setFile(filepath, content) { filepath = normalizeUnixFilePath(unixJoin(filepath)); fileMap[filepath] = { content }; } function setFiles(files) { for (const path in files) { setFile(path, files[path].content); } } function mapEsbuildOutputFiles(outputFiles, root, outdir = "dist") { const tmpOutDir = unixJoin([root, "dist"]); (outputFiles || []).forEach((file) => { const unixFilePath = unixJoin(file.path); const filepath = unixJoin([outdir, unixFilePath.replace(tmpOutDir, "")]); const path = normalizeUnixFilePath(filepath); fileMap[path] = { content: Buffer.from(file.contents) }; }); return fileManager; } function deleteFile(filepath) { filepath = normalizeUnixFilePath(unixJoin(filepath)); delete fileMap[filepath]; } async function writeFileToDisc(filepath) { filepath = normalizeUnixFilePath(unixJoin(filepath)); throwIfFileDoesNotExist(filepath); await (0, import_promises2.writeFile)((0, import_path6.join)(filepath), fileMap[filepath].content); } async function writeFileMapToDisc(discDestinationDir) { await Promise.all( Object.entries(fileMap).map(async ([filepath, { content, mode }]) => { const fileSize = (Buffer.byteLength(content, "utf8") / Math.pow(2, 10)).toFixed(2); const fileSystemFullPath = (0, import_path6.join)(discDestinationDir, filepath); logger.debug( `${mode !== void 0 ? renderFileModeLabel[mode] : renderFileModeLabel.created} ${fileSystemFullPath} ${`(${fileSize} KB)`}`, "FILES" ); await (0, import_promises2.mkdir)((0, import_path6.dirname)(fileSystemFullPath), { recursive: true }); return (0, import_promises2.writeFile)(fileSystemFullPath, content); }) ); } async function addAssetFiles(options) { for (const asset of options.assetConfigs) { const currDir = (0, import_path6.isAbsolute)(asset.input) ? asset.input : (0, import_path6.join)(options.root, asset.input); const files = await (0, import_fast_glob.default)(asset.glob, { cwd: currDir, dot: true, ignore: asset.ignore }); for (const file of files) { const filePath = unixJoin([options.distDir, asset.output, file]); setFile(filePath, (0, import_fs4.readFileSync)((0, import_path6.join)(currDir, file))); } } } function resetFileMap() { fileMap = {}; } } var fileMapManagerSingleton = createFileManager(); // src/build/build-watchers/utils.ts function getDefaultEsbuildOptions() { return { sourcemap: true, color: true, loader: { // Esbuild should not bundle .node files but also not throw an error if .node files are required ".node": "empty" }, bundle: true, logLevel: "silent", charset: "utf8", write: false }; } // src/utils/run-esbuild.ts async function runEsbuild(outdir, options, abortSignal) { const esbuild = require(nodeResolvePackage( "esbuild", options.cwd )); if (!esbuild.context) { throw new Error( `esbuild context function not found. This is likely happening because you may have dependencies in your project that are installing an older version of esbuild. Please fix your dependencies and try again. ${esbuild.version ? `Your current esbuild version being used is ${esbuild.version}.` : ""}`, { cause: 258 /* USER */ } ); } const { context } = esbuild; const sourcemap = options.sourcemapOptions?.sourcemap ?? false; if (sourcemap && !options.sourcemapOptions?.includeVendorSourceMaps) { options.plugins = [ ...options.plugins ?? [], excludeVendorFromSourceMapPlugin() ]; } const ctx = await context({ ...getDefaultEsbuildOptions(), entryPoints: options.entryPoints.map( (entryPoint) => (0, import_path7.isAbsolute)(entryPoint) ? entryPoint : (0, import_path7.join)(options.cwd, entryPoint) ), outbase: options.outbase, target: options.target, minify: options.minify ?? false, sourcemap, platform: options.platform || "browser", plugins: options.plugins || [], external: options.external || [], sourceRoot: options.sourceRoot, tsconfig: (0, import_path7.isAbsolute)(options.tsconfig) ? options.tsconfig : (0, import_path7.join)(options.cwd, options.tsconfig), outdir, format: options.format, globalName: options.globalName, metafile: options.metafile, write: false, // Necessary for type safety. define: options.define }); abortSignal?.addEventListener("abort", () => ctx.cancel()); const resultPromise = ctx.rebuild(); try { return await resultPromise; } finally { ctx.dispose(); } } // src/build/compile.ts async function compile(options, abortSignal) { const timerStart = Date.now(); try { const tmpOutDir = (0, import_path8.join)(options.cwd, "dist"); const buildResult = await runEsbuild(tmpOutDir, options, abortSignal); const timerEnd = Date.now(); const diagnostics = buildResult.warnings.map((message) => fromEsbuildMessage(message, "Warning" /* warning */)).filter((diagnostic) => !diagnostic.filePath?.includes("node_modules/")); const outDir = options.outDir ?? "dist"; const fileMap = buildResult.outputFiles.reduce((prev, file) => { const filepath = `${import_path8.sep}${(0, import_path8.join)( outDir, file.path.replace(tmpOutDir, "") )}`; return { ...prev, [filepath]: { content: Buffer.from(file.contents) } }; }, {}); return { fileMap, diagnostics, duration: timerEnd - timerStart, metafile: buildResult.metafile }; } catch (e) { const timerEnd = Date.now(); if (isBuildFailure(e)) { const diagnostics = [ // Convert esbuild errors ...e.errors.map( (message) => fromEsbuildMessage(message, "Error" /* error */) ), // Convert esbuild warnings, but ignore warnings in node_modules ...e.warnings.map( (message) => fromEsbuildMessage(message, "Warning" /* warning */) ).filter( (diagnostic) => !diagnostic.filePath?.includes("node_modules/") ) ]; return { fileMap: {}, diagnostics, duration: timerEnd - timerStart }; } const error = e instanceof Error ? e : new Error(String(e), { cause: 261 /* COMPILATION */ }); throw error; } } function isBuildFailure(error) { return typeof error === "object" && typeof error?.errors !== "undefined"; } // src/dev/pretty-error.ts var import_code_frame = require("@babel/code-frame"); var import_chalk6 = require("chalk"); var import_fs5 = require("fs"); var import_ansi_to_html = __toESM(require("ansi-to-html")); var ansiToHtmlConverter = new import_ansi_to_html.default({}); function logDiagnostics(diagnostics) { for (const diagnostic of diagnostics) { logger.info(prettyConsoleError(diagnostic)); } } var prettyConsoleError = (diagnostic) => { const file = diagnostic.filePath ? `${(0, import_chalk6.cyan)(diagnostic.filePath)}:${(0, import_chalk6.yellow)(diagnostic.line)}:${(0, import_chalk6.yellow)( diagnostic.column )}` : "<no file>"; const category = diagnostic.category === "Error" /* error */ ? (0, import_chalk6.red)("Error:") : (0, import_chalk6.yellow)("Warning:"); const code = diagnostic.code ? (0, import_chalk6.gray)(` ${diagnostic.code}`) : ""; const header = `> ${file} - ${category}${code} ${diagnostic.text}`; const codeFrame = (0, import_chalk6.bold)(`${createCodeFrame(diagnostic)}`); return `${header} ${codeFrame}`; }; var createCodeFrame = (error) => { try { if (!error.filePath) { return ""; } const fileString = (0, import_fs5.readFileSync)(error.filePath, "utf-8"); const isFileEmpty = fileString === ""; const location = { start: { line: error.line, column: isFileEmpty ? 1 : error.column } }; return (0, import_code_frame.codeFrameColumns)(isFileEmpty ? " " : fileString, location, { forceColor: true }); } catch (error2) { console.error(error2); } return ""; }; // src/utils/config/extract-dt-app-config-from-ts.ts var import_fs7 = require("fs"); // src/utils/config/native-node-modules-plugin.ts var nativeNodeModulesPlugin = { name: "native-node-modules", setup(build) { build.onResolve({ filter: /\.node$/, namespace: "file" }, (args) => ({ path: require.resolve(args.path, { paths: [args.resolveDir] }), namespace: "node-file" })); build.onLoad({ filter: /.*/, namespace: "node-file" }, (args) => ({ contents: ` import path from ${JSON.stringify(args.path)} try { module.exports = require(path) } catch {} ` })); build.onResolve({ filter: /\.node$/, namespace: "node-file" }, (args) => ({ path: args.path, namespace: "file" })); const opts = build.initialOptions; opts.loader = opts.loader || {}; opts.loader[".node"] = "file"; } }; // src/utils/config/extract-dt-app-config-from-ts.ts var import_crypto = __toESM(require("crypto")); // src/build/type-checker.ts var import_lodash3 = require("lodash"); var import_micromatch = require("micromatch"); var import_fs6 = require("fs"); var import_path9 = require("path"); // src/build/fixed-build-type-options.ts var fixedGeneralOptions = { noEmit: true, skipLibCheck: true }; function getFixedBuildTypeOptions(root) { const { ScriptTarget } = resolveTypescript(root); return { ["ui" /* UI */]: { target: ScriptTarget.ES2021, ...fixedGeneralOptions }, ["functions" /* FUNCTIONS */]: { target: ScriptTarget.ESNext, ...fixedGeneralOptions }, ["actions" /* ACTIONS */]: { target: ScriptTarget.ESNext, ...fixedGeneralOptions }, ["widgets" /* WIDGETS */]: { target: ScriptTarget.ESNext, ...fixedGeneralOptions } }; } // src/build/type-checker.ts function check(options) { const { createCompilerHost, createProgram } = resolveTypescript( options.srcRoot ); const { tsConfigContents, tsConfigFileDiagnostics: fileDiagnostics } = getTsConfig(options.srcRoot, options.tsConfigFullPath); const isSolutionConfig = tsConfigContents.projectReferences && tsConfigContents.projectReferences.length > 0 && tsConfigContents.fileNames.length === 0; if (isSolutionConfig) { return handleSolutionConfigWithBuilder(options); } const compilerOptions = { ...tsConfigContents.options, ...getFixedBuildTypeOptions(options.srcRoot)[options.buildType], incremental: false }; const host = createCompilerHost(tsConfigContents.options); let entryFiles = []; const purgedEntryFiles = (0, import_micromatch.not)( options.entrypoints, tsConfigContents.raw.exclude ).map( (filepath) => (0, import_path9.isAbsolute)(filepath) ? filepath : (0, import_path9.join)(options.srcRoot, filepath) ); if (tsConfigContents.options.composite) { entryFiles = [ .../* @__PURE__ */ new Set([...purgedEntryFiles, ...tsConfigContents.fileNames]) ]; } else { const typeDefinitions = tsConfigContents.fileNames.filter( (fileName) => fileName.endsWith(".d.ts") ); const purgedTypeDefs = (0, import_micromatch.not)( typeDefinitions, tsConfigContents.raw.exclude ).map( (filepath) => (0, import_path9.isAbsolute)(filepath) ? filepath : (0, import_path9.join)(options.srcRoot, filepath) ); entryFiles = [.../* @__PURE__ */ new Set([...purgedEntryFiles, ...purgedTypeDefs])]; } const program2 = createProgram({ rootNames: entryFiles, options: compilerOptions, host, configFileParsingDiagnostics: fileDiagnostics, projectReferences: tsConfigContents.projectReferences }); const results = program2.emit(); const allDiagnostics = [ ...program2.getSyntacticDiagnostics(), ...program2.getSemanticDiagnostics(), ...program2.getConfigFileParsingDiagnostics(), ...program2.getGlobalDiagnostics(), ...program2.getDeclarationDiagnostics(), ...results.diagnostics ]; return allDiagnostics.map( (d) => fromTypeScriptDiagnostic(d, options.appRoot) ); } function getTsConfig(cwd, tsConfigPath) { const { sys, readConfigFile, parseJsonConfigFileContent } = resolveTypescript(cwd); const configFile = readConfigFile( tsConfigPath, (path) => (0, import_fs6.readFileSync)(path).toString() ); let tsConfigFileDiagnostics = []; if (configFile.error && (0, import_lodash3.has)(configFile.error.file, "parseDiagnostics")) { tsConfigFileDiagnostics = (0, import_lodash3.get)( configFile.error?.file, "parseDiagnostics" ); } const tsConfigContents = parseJsonConfigFileContent( configFile.config, sys, (0, import_path9.dirname)(tsConfigPath) ); tsConfigFileDiagnostics.push(...tsConfigContents.errors); return { tsConfigContents, tsConfigFileDiagnostics }; } function handleSolutionConfigWithBuilder(options) { const { sys, createSolutionBuilderHost, createSolutionBuilder } = resolveTypescript(options.srcRoot); const allDiagnostics = []; const host = createSolutionBuilderHost( sys, void 0, (diagnostic) => { allDiagnostics.push(diagnostic); }, (diagnostic) => { allDiagnostics.push(diagnostic); } ); const solutionBuilder = createSolutionBuilder( host, [options.tsConfigFullPath], // Root config files { dry: false, force: false, verbose: false } ); solutionBuilder.build(); return allDiagnostics.map( (d) => fromTypeScriptDiagnostic(d, options.appRoot) ); } // src/utils/config/extract-dt-app-config-from-ts.ts async function extractDtAppConfigFromTs(root, configFilePath, tsconfigPath, skipTypeCheck, format = "cjs") { logger.debug( "Perform build and type-check on configuration file", DEBUG_CONTEXT ); const externalPackages = getProjectDependencies(format, root); const buildPromise = compile({ cwd: (0, import_path10.dirname)(configFilePath), tsconfig: tsconfigPath, entryPoints: [configFilePath], external: ["esbuild", ...externalPackages], platform: "node", target: "node16", format, outDir: "dist", plugins: [nativeNodeModulesPlugin], define: {} // Overwrite define to not break esbuild plugins defined in app.config }); let typeCheckDiagnostics = []; if (!skipTypeCheck) { typeCheckDiagnostics = check({ appRoot: root, srcRoot: root, entrypoints: [configFilePath], tsConfigFullPath: tsconfigPath, buildType: "ui" /* UI */ }); } const buildResult = await buildPromise; const diagnostics = [...buildResult.diagnostics, ...typeCheckDiagnostics]; logDiagnostics(diagnostics); if (diagnostics.some( (diagnostic) => diagnostic.category === "Error" /* error */ )) { throw new Error("Reading your configuration file failed.", { cause: 258 /* USER */ }); } logger.debug("Successfully prepared configuration to be read", DEBUG_CONTEXT); const randomTmpDir = generateRandomDirectoryName(configFilePath, ".dt-app"); const dtAppConfigJsPath = (0, import_path10.join)( randomTmpDir, format === "cjs" ? "app.config.js" : "app.config.mjs" ); if (!(0, import_fs7.existsSync)(randomTmpDir)) { (0, import_fs7.mkdirSync)(randomTmpDir, { recursive: true }); } (0, import_fs7.writeFileSync)( dtAppConfigJsPath, buildResult.fileMap[(0, import_path10.join)(import_path10.sep, "dist", "app.config.js")].content.toString( "utf-8" ) ); try { const result = format === "cjs" ? await require(dtAppConfigJsPath) : (await import(dtAppConfigJsPath)).default; return result; } catch (error) { throw new Error(`Error at resolving of app.config: ${error}`, { cause: 258 /* USER */ }); } finally { try { (0, import_fs7.rmSync)(randomTmpDir, { recursive: true }); } catch (e) { logger.debug( `A temporary file can't be cleaned up: ${dtAppConfigJsPath}. Error: ${toError(e).message}`, DEBUG_CONTEXT ); } } } function getProjectDependencies(format, root) { if (format !== "esm") { return []; } const externalPackages = []; try { const packageJsonPath = (0, import_path10.join)(root, "package.json"); const packageJson = JSON.parse((0, import_fs7.readFileSync)(packageJsonPath, "utf-8")); const dependencies = Object.keys(packageJson?.dependencies || {}); const devDependencies = Object.keys(packageJson?.devDependencies || {}); externalPackages.push(...dependencies, ...devDependencies); } catch (error) { logger.debug( "Failed to read package.json for external packages", DEBUG_CONTEXT ); } return externalPackages; } function generateRandomDirectoryName(filePath, dir) { const randomString = import_crypto.default.randomBytes(4).toString("hex"); return (0, import_path10.join)((0, import_path10.dirname)(filePath), `${dir}/${randomString}`); } // src/utils/file-operations.ts var import_node_path = require("node:path"); var import_fs9 = require("fs"); var import_inquirer = require("inquirer"); // src/utils/file-utils.ts var import_path11 = require("path"); var import_fs8 = require("fs"); function mkdirp(path) { if (!(0, import_fs8.existsSync)(path)) { (0, import_fs8.mkdirSync)(path); } } function nameToFileName({ name, suffix, allowNested = true }) { const { name: nameWithoutExtension, dir, base } = (0, import_path11.parse)(name); const parsedName = allowNested ? nameWithoutExtension : base; const fileName = `${parsedName}.${suffix}`; if (allowNested) { return (0, import_path11.join)(dir, fileName); } return fileName; } // src/utils/file-operations.ts function checkIfFileExists(fileSearch) { const { filePath } = getFileNameAndPath(fileSearch); return (0, import_fs9.existsSync)(filePath); } function getFileNameAndPath({ allowNested = true, ...options }) { let fileName = options.suffix ? nameToFileName({ name: options.name, suffix: options.suffix, allowNested }) : options.name; let filePath = ""; if (options.folder) { filePath = (0, import_node_path.join)(options.cwd, options.folder, fileName); } else { filePath = (0, import_node_path.join)(options.cwd, fileName); } if (!options.suffix) { fileName = (0, import_node_path.basename)(filePath); } return { fileName, filePath }; } // src/utils/config/get-dt-app-file-config.ts var import_fs10 = require("fs"); async function getDtAppFileConfig(root, skipTypeCheck = false) { logger.debug("Searching for configuration file", DEBUG_CONTEXT); const configFileName = await getConfigFileName(root); if (configFileName) { const fullPath = (0, import_path12.join)(root, configFileName); logger.debug(`Found ${fullPath}`, DEBUG_CONTEXT); const fileExtension = (0, import_path12.extname)(configFileName); switch (fileExtension) { case ".js": { return require(fullPath); } case ".json": { try { return readJson(fullPath); } catch (e) { if (e instanceof Error) { throw new Error(e.message, { cause: 258 /* USER */ }); } else { throw new Error(String(e), { cause: 258 /* USER */ }); } } } case ".ts": case ".cts": { const tsconfigPath = findTsConfigPath(root); return await extractDtAppConfigFromTs( root, fullPath, tsconfigPath, skipTypeCheck, fileExtension === ".ts" ? "esm" : "cjs" ); } } } throw Error( "The current directory does not appear to contain a Dynatrace App. Please verify that you're in the correct application directory and re-run this command inside your Dynatrace App folder." ); } function findTsConfigPath(root) { const isFilePresent = checkIfFileExists({ cwd: root, name: "tsconfig", suffix: "json" }); if (isFilePresent) { return (0, import_path12.join)(root, "tsconfig.json"); } mkdirp((0, import_path12.join)(root, ".dt-app")); const tempTsConfigPath = (0, import_path12.join)(root, ".dt-app", "tmp-app-tsconfig.json"); const tempTsConfig = { compilerOptions: { target: "ESNext", module: "ESNext", moduleResolution: "node", allowSyntheticDefaultImports: true, esModuleInterop: true, strict: true, skipLibCheck: true, resolveJsonModule: true, noImplicitAny: false, types: ["node"], typeRoots: ["../node_modules/@types", "../node_modules/@dynatrace"] }, include: ["../app.config.ts", "../app.config.cts"], exclude: ["../node_modules", "../dist"] }; try { (0, import_fs10.writeFileSync)(tempTsConfigPath, JSON.stringify(tempTsConfig, null, 2)); } catch (error) { throw new Error( `Could not write temporary tsconfig at ${tempTsConfigPath}`, { cause: 258 /* USER */ } ); } return tempTsConfigPath; } async function getConfigFileName(cwd) { const configFileExtensionList = ["cts", "ts", "js", "json"]; const configFiles = configFileExtensionList.filter( (ext) => checkIfFileExists({ name: "app", suffix: `config.${ext}`, cwd }) ).map((ext) => `app.config.${ext}`); if (configFiles.length > 1) { logger.warn(`Multiple app.config files have been detected. (${configFiles.join( ", " )}) ${configFiles[0]} w