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,638 lines (1,567 loc) 119 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.116.0/index.ts var import_fs14 = require("fs"); var import_devkit2 = require("@dynatrace/devkit"); var import_path18 = require("path"); // src/utils/config/get-dt-app-file-config.ts var import_path9 = require("path"); // src/utils/config/extract-dt-app-config-from-ts.ts var import_path7 = 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 ErrorCause = /* @__PURE__ */ ((ErrorCause2) => { ErrorCause2[ErrorCause2["POSIX_SIGNAL_BASE"] = 128] = "POSIX_SIGNAL_BASE"; ErrorCause2[ErrorCause2["UNCAUGHT"] = 256] = "UNCAUGHT"; ErrorCause2[ErrorCause2["INTERNAL"] = 257] = "INTERNAL"; ErrorCause2[ErrorCause2["USER"] = 258] = "USER"; ErrorCause2[ErrorCause2["AUTH"] = 259] = "AUTH"; ErrorCause2[ErrorCause2["TIMEOUT"] = 260] = "TIMEOUT"; ErrorCause2[ErrorCause2["COMPILATION"] = 261] = "COMPILATION"; ErrorCause2[ErrorCause2["NETWORKING"] = 263] = "NETWORKING"; ErrorCause2[ErrorCause2["VALIDATION"] = 264] = "VALIDATION"; ErrorCause2[ErrorCause2["BUILD"] = 265] = "BUILD"; ErrorCause2[ErrorCause2["APP_GATEWAY_COMMUNICATION"] = 266] = "APP_GATEWAY_COMMUNICATION"; return ErrorCause2; })(ErrorCause || {}); 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 `; function getCredentialsError(message) { const errorMessage = [message, CREDENTIALS_ERROR]; return new Error(errorMessage.filter(Boolean).join("\n"), { cause: 259 /* AUTH */ }); } 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)); } // 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_path5 = require("path"); // src/utils/run-esbuild.ts var import_path4 = 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/utils/get-source-files.ts var import_fast_glob = 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/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_path4.isAbsolute)(entryPoint) ? entryPoint : (0, import_path4.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_path4.isAbsolute)(options.tsconfig) ? options.tsconfig : (0, import_path4.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_path5.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_path5.sep}${(0, import_path5.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_chalk2 = require("chalk"); var import_fs3 = 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_chalk2.cyan)(diagnostic.filePath)}:${(0, import_chalk2.yellow)(diagnostic.line)}:${(0, import_chalk2.yellow)( diagnostic.column )}` : "<no file>"; const category = diagnostic.category === "Error" /* error */ ? (0, import_chalk2.red)("Error:") : (0, import_chalk2.yellow)("Warning:"); const code = diagnostic.code ? (0, import_chalk2.gray)(` ${diagnostic.code}`) : ""; const header = `> ${file} - ${category}${code} ${diagnostic.text}`; const codeFrame = (0, import_chalk2.bold)(`${createCodeFrame(diagnostic)}`); return `${header} ${codeFrame}`; }; var createCodeFrame = (error) => { try { if (!error.filePath) { return ""; } const fileString = (0, import_fs3.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_fs5 = 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_lodash2 = require("lodash"); var import_micromatch = require("micromatch"); var import_fs4 = require("fs"); var import_path6 = 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 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_path6.isAbsolute)(filepath) ? filepath : (0, import_path6.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_path6.isAbsolute)(filepath) ? filepath : (0, import_path6.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_fs4.readFileSync)(path).toString() ); let tsConfigFileDiagnostics = []; if (configFile.error && (0, import_lodash2.has)(configFile.error.file, "parseDiagnostics")) { tsConfigFileDiagnostics = (0, import_lodash2.get)( configFile.error?.file, "parseDiagnostics" ); } const tsConfigContents = parseJsonConfigFileContent( configFile.config, sys, (0, import_path6.dirname)(tsConfigPath) ); tsConfigFileDiagnostics.push(...tsConfigContents.errors); return { tsConfigContents, tsConfigFileDiagnostics }; } // src/utils/config/extract-dt-app-config-from-ts.ts async function extractDtAppConfigFromTs(root, configFilePath, tsconfigPath, skipTypeCheck) { logger.debug( "Perform build and type-check on configuration file", DEBUG_CONTEXT ); const buildPromise = compile({ cwd: (0, import_path7.dirname)(configFilePath), tsconfig: tsconfigPath, entryPoints: [configFilePath], external: ["esbuild"], platform: "node", target: "node16", 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_path7.join)(randomTmpDir, "app.config.js"); if (!(0, import_fs5.existsSync)(randomTmpDir)) { (0, import_fs5.mkdirSync)(randomTmpDir, { recursive: true }); } (0, import_fs5.writeFileSync)( dtAppConfigJsPath, buildResult.fileMap[(0, import_path7.join)(import_path7.sep, "dist", "app.config.js")].content.toString( "utf-8" ) ); try { const result = await require(dtAppConfigJsPath); logger.debug("Finished reading the configuration", DEBUG_CONTEXT); return result; } catch (error) { throw new Error(`Error at resolving of app.config.ts: ${error}`, { cause: 258 /* USER */ }); } finally { try { (0, import_fs5.rmSync)(randomTmpDir, { recursive: true }); } catch (e) { logger.debug( `A temporary file can't be cleaned up: ${dtAppConfigJsPath}. Error: ${toError(e).message}`, DEBUG_CONTEXT ); } } } function generateRandomDirectoryName(filePath, dir) { const randomString = import_crypto.default.randomBytes(4).toString("hex"); return (0, import_path7.join)((0, import_path7.dirname)(filePath), `${dir}/${randomString}`); } // src/utils/read-json.ts var import_fs6 = require("fs"); function readJson(file) { if (!(0, import_fs6.existsSync)(file) && !(0, import_fs6.lstatSync)(file, { throwIfNoEntry: false })?.isFile()) { throw new Error(`[read-json.ts] File does not exist: ${file}`, { cause: 257 /* INTERNAL */ }); } const content = (0, import_fs6.readFileSync)(file, "utf-8"); return JSON.parse(content); } // src/utils/file-operations.ts var import_node_path = require("node:path"); var import_fs7 = require("fs"); var import_inquirer = require("inquirer"); // src/utils/file-utils.ts var import_path8 = require("path"); var import_lodash3 = require("lodash"); function nameToFileName({ name, suffix, allowNested = true, overrideNameWithKebabCase = true }) { const { name: nameWithoutExtension, dir, base } = (0, import_path8.parse)(name); const parsedName = allowNested ? nameWithoutExtension : base; const fileName = `${overrideNameWithKebabCase ? (0, import_lodash3.kebabCase)(parsedName) : parsedName}.${suffix}`; if (allowNested) { return (0, import_path8.join)(dir, fileName); } return fileName; } // src/utils/file-operations.ts function checkIfFileExists(fileSearch) { const { filePath } = getFileNameAndPath(fileSearch); return (0, import_fs7.existsSync)(filePath); } function getFileNameAndPath({ allowNested = true, overrideNameWithKebabCase = false, ...options }) { let fileName = options.suffix ? nameToFileName({ name: options.name, suffix: options.suffix, allowNested, overrideNameWithKebabCase }) : 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 }; } async function checkIfExists(FileSearchOption, template, errorCauseType) { const isFilePresent = checkIfFileExists(FileSearchOption); if (!isFilePresent) { logger.spinner.pause(); const { fileName, filePath } = getFileNameAndPath(FileSearchOption); const createFile = await terminalAction( async () => { const { createFile: createFile2 } = await (0, import_inquirer.prompt)([ { name: "createFile", type: "confirm", message: `${filePath} is missing${errorCauseType ? ", and it is required" : ""}. Do you want to generate the file?` } ]); return createFile2; } ); logger.spinner.resume(); const fileTemplate = JSON.stringify(template, null, 2); if (!createFile && errorCauseType) { throw new Error( `Failed to continue Please create a ${fileName} file in the ${filePath} directory. Example ${filePath}: ${fileTemplate} `, { cause: ErrorCause[errorCauseType] } ); } if (createFile) { (0, import_fs7.writeFileSync)(filePath, fileTemplate); logger.log(`${filePath} created!`); } } } // src/utils/config/get-dt-app-file-config.ts 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_path9.join)(root, configFileName); logger.debug(`Found ${fullPath}`, DEBUG_CONTEXT); const fileExtension = (0, import_path9.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": { const templateFile = JSON.parse(`{ "compileOnSave": false, "compilerOptions": { "baseUrl": ".", "module": "ESNext", "esModuleInterop": true, "moduleResolution": "node", "rootDir": ".", "strictNullChecks": true, "target": "ESNext", "types": ["node"], "typeRoots": ["./node_modules/@types", "./node_modules/@dynatrace"] }, "exclude": ["node_modules", "dist"] }`); await checkIfExists( { cwd: root, name: "tsconfig", suffix: "json" }, templateFile, "USER" ); const tsconfigPath = (0, import_path9.join)(root, "tsconfig.json"); return await extractDtAppConfigFromTs( root, fullPath, tsconfigPath, skipTypeCheck ); } } } } async function getConfigFileName(cwd) { const configFileExtensionList = ["ts", "js", "json"]; let configFound = false; const configFiles = []; for (const [index, ext] of configFileExtensionList.entries()) { let isfilePresent = false; const isLastCheck = configFileExtensionList.length === index + 1; const requirePrompt = !configFound && isLastCheck; const fileSearchArg = { name: "app", suffix: `config.${ext}`, cwd }; if (!requirePrompt) { isfilePresent = checkIfFileExists(fileSearchArg); } else { const templateFile = JSON.parse(`{ "environmentUrl": "https://your-tenant.apps.dynatrace.com/", "app": { "name": "some-new-app", "version": "0.0.0", "description": "A starting project with routing, fetching data, and charting", "id": "my.some.new.app", "scopes": [ { "name": "storage:logs:read", "comment": "default template" }, { "name": "storage:buckets:read", "comment": "default template" } ] } }`); await checkIfExists( { name: "app", suffix: `config.${ext}`, cwd }, templateFile, "USER" ); isfilePresent = true; } configFound ||= isfilePresent; isfilePresent && configFiles.push(`app.config.${ext}`); } if (configFiles.length > 1) { logger.warn(`Multiple app.config files have been detected. (${configFiles.join( ", " )}) ${configFiles[0]} will be used.`); } return configFiles[0]; } // src/utils/config/cli-options.ts var import_path17 = require("path"); // src/utils/config/validate-actions.ts var import_fs8 = require("fs"); var import_path10 = require("path"); var import_fast_glob2 = __toESM(require("fast-glob")); function validateActions(actions, options) { return actions.map((action) => validateAction(action, options)); } async function validateAction(action, options) { const validatedAction = action; const actionExists = (0, import_fs8.existsSync)( (0, import_path10.join)(options.root, options.actionsDir, `${action.name}.action.ts`) ) || (0, import_fs8.existsSync)( (0, import_path10.join)( options.root, options.actionsDir, `${action.name}.resumable.action.ts` ) ) || (0, import_fs8.existsSync)( (0, import_path10.join)( options.root, options.actionsDir, `${action.name}.stateful-action.ts` ) ); if (actionExists) { validatedAction.isUiAction = false; return validatedAction; } if (!(await (0, import_fast_glob2.default)("api/**/" + action.name + "*.ts", { cwd: options.root })).length) { throw new Error( action.name + " has no UI and no function implementation!", { cause: 257 /* INTERNAL */ } ); } validatedAction.isUiAction = false; return validatedAction; } // src/utils/config/validate-wave-config.ts var import_ajv_errors = __toESM(require("ajv-errors")); var import__ = __toESM(require("ajv/dist/2019")); var import_chalk3 = require("chalk"); var import_path13 = require("path"); // src/auth/get-automation-token.ts var import_url = require("url"); // src/utils/request.ts var import_undici = require("undici"); async function dtFetch(url, options) { return (0, import_undici.fetch)(url, options); } // src/auth/get-automation-token.ts async function getAutomationToken({ clientID, clientSecret, accountURN }, oauthUrl, oAuthScopes) { if (!clientID || !clientSecret) { throw new Error( '"clientID" and "clientSecret" are required to fetch an automation token..', { cause: 259 /* AUTH */ } ); } const parameters = new import_url.URLSearchParams(); parameters.append("grant_type", "client_credentials"); if (oAuthScopes) { parameters.append( "scope", oAuthScopes.map((scope) => scope.name).join(" ") ); } parameters.append("client_id", clientID); parameters.append("client_secret", clientSecret); if (accountURN) { parameters.append("resource", accountURN); } try { const response = await dtFetch( `${new URL(oauthUrl).origin}/sso/oauth2/token`, { method: "POST", body: Buffer.from(parameters.toString()), headers: { "content-type": "application/x-www-form-urlencoded" } } ); const body = await response.json(); if (response.ok) { return body; } else { throw getCredentialsError( JSON.stringify(body) ?? "Could not get token from authentication provider" ); } } catch (e) { const fetchError = e; const fetchErrorOutput = fetchError.cause && typeof fetchError.cause !== "number" ? fetchError.cause : fetchError; logger.debug( `Failed to get automation token! Backend responded with: ${fetchErrorOutput.message}`, "AUTH" ); throw new Error(`${fetchErrorOutput.message}`, { cause: 259 /* AUTH */ }); } } // src/utils/get-sso-url.ts var import_url2 = require("url"); // src/utils/get-url-from-location-header.ts var linkCache = /* @__PURE__ */ new Map(); async function getUrlFromLocationHeader(url) { if (linkCache.has(url)) { const resolved2 = linkCache.get(url); logger.debug( `Found resolved URL in cache: ${resolved2.toString()}`, "HTTP(S)" ); return resolved2; } logger.debug( `Execute GET request without allowing redirection to '${url}'`, "HTTP(S)" ); const response = await dtFetch(url, { redirect: "follow" }); let resolved; const headerLocation = response.headers.get("location"); if (headerLocation) { resolved = new URL(headerLocation); } else { resolved = new URL(response.url); } logger.debug(`Resolved URL is ${resolved.toString()}`, "HTTP(S)"); linkCache.set(url, resolved); return resolved; } // src/utils/get-sso-url.ts async function getSSOUrl(environmentUrl) { try { const reqUrl = new import_url2.URL(environmentUrl); reqUrl.pathname = "platform/oauth2/authorization/dynatrace-sso"; logger.debug( `Retrieving SSO-URL for environment '${environmentUrl}'`, "AUTH" ); const resolved = (await getUrlFromLocationHeader(reqUrl.toString())).origin; logger.debug(`Using SSO-URL '${resolved}'`, "AUTH"); return resolved; } catch (e) { const error = toError(e); logger.debug( `SSO-URL for '${environmentUrl}' could not be processed.`, "AUTH" ); logger.debug(`Error: ${error.message}`, "AUTH"); throw new Error( `SSO-URL for '${environmentUrl}' could not be processed. ${error.message} ${"If you need further assistance visit the connectivity troubleshooting guide here: https://dt-url.net/c4jp0s12"}`, { cause: 258 /* USER */ } ); } } // src/auth/get-bearer-token.ts var import_path12 = require("path"); var import_fs11 = require("fs"); // src/auth/get-access-token.ts var import_fastify = __toESM(require("fastify")); var import_open = __toESM(require("open")); var import_mime_types = require("mime-types"); var import_querystring = require("querystring"); // src/auth/constants.ts var import_crypto2 = require("crypto"); var MINIMAL_SCOPES = [ { name: "app-engine:apps:run", comment: "cli-scope" } ]; function base64URLEncode(buffer) { return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function sha256(content) { return (0, import_crypto2.createHash)("sha256").update(content).digest(); } var OAUTH_CODE_VERIFIER = base64URLEncode((0, import_crypto2.randomBytes)(46)); var OAUTH_CODE_CHALLENGE = base64URLEncode( sha256(OAUTH_CODE_VERIFIER) ); var OAUTH_STATE_PARAMETER = (0, import_crypto2.randomBytes)(20).toString("hex"); // src/auth/request-oauth2-token.ts async function requestOauth2Token(oauthUrl, clientId, code, callbackUrl) { const url = new URL(oauthUrl); url.pathname = "/sso/oauth2/token"; url.searchParams.append("grant_type", "authorization_code"); url.searchParams.append("code", code); url.searchParams.append("client_id", clientId); url.searchParams.append("redirect_uri", callbackUrl.toString()); url.searchParams.append("code_verifier", OAUTH_CODE_VERIFIER); const response = await dtFetch(url.toString(), { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" } }); if (!response.ok) { throw new Error(response.statusText, { cause: 259 /* AUTH */ }); } return await response.json(); } // src/utils/clouddev/codespaces.ts var codespacesEnv = process.env.CODESPACE_NAME && process.env.CODESPACE_NAME.trim() !== "" ? process.env.CODESPACE_NAME : void 0; function getPublicUrl(port) { if (!codespacesEnv) { return; } const domain = process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; return `${codespacesEnv}-${port}.${domain}`; } // 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; function getPublicUrl2(port) { if (!gitpodEnv) { return; } const hostname = new URL(gitpodEnv).hostname; return `${port}-${hostname}`; } // 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; var isCloudDevEnv = gitpodEnv || codespacesEnv || customEnv; function cloudDevEnvPublicUrl(port) { if (!isCloudDevEnv) { return; } if (customEnv) { return customEnv.replace(/^https?:\/\//, ""); } return gitpodEnv ? getPublicUrl2(port) : getPublicUrl(port); } // src/auth/utils.ts async function getDtAppCliCallbackUrl(port, environmentUrl) { if (isCloudDevEnv) { const cloudDevAuthUrl = `https://${cloudDevEnvPublicUrl(port)}/auth/login`; return `${environmentUrl}/platform-reserved/app-registry/v1/cde-auth?cde-uri=${encodeURIComponent( cloudDevAuthUrl )}`; } return `http://localhost:${port}/auth/login`; } // src/utils/is-docker.ts var import_fs9 = __toESM(require("fs")); var isDockerCached; function hasDockerEnv() { try { import_fs9.default.statSync("/.dockerenv"); return true; } catch { return false; } } function hasDockerCGroup() { try { return import_fs9.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker"); } catch { return false; } } function isDocker() { if (isDockerCached === void 0) { isDockerCached = hasDockerEnv() || hasDockerCGroup(); } return isDockerCached; } // src/auth/get-access-token.ts var import_devkit = require("@dynatrace/devkit"); // src/auth/successful-auth-page.ts var successfulAuthPageHtml = ( /* html */ `<html> <head> <meta charset="utf-8" /> <title>dt-app - Dynatrace</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <style> @font-face { font-family: 'DynatraceFlow'; font-style: normal; font-weight: 400; font-display: swap; src: url(https://dt-cdn.net/fonts/DTFlow-Regular-v002.woff2) format('woff2'); } @font-face { font-family: 'DynatraceFlow'; font-style: normal; font-weight: 500; font-display: swap; src: url(https://dt-cdn.net/fonts/DTFlow-Medium-v002.woff2) format('woff2'); } @font-face { font-family: 'DynatraceFlow'; font-style: normal; font-weight: 600; font-display: swap; sr