UNPKG

cumulocity-cypress

Version:
860 lines (846 loc) 32.1 kB
'use strict'; var _$2 = require('lodash'); var fs = require('fs'); var path = require('path'); var yargs = require('yargs/yargs'); var helpers = require('yargs/helpers'); var winston = require('winston'); var transportsDirect = require('winston/lib/winston/transports/'); var morgan = require('morgan'); var index = require('./index-DEijUXb0.js'); var glob = require('glob'); var debug = require('debug'); require('set-cookie-parser'); require('cookie'); var yaml = require('yaml'); require('util'); require('node:os'); require('express'); require('raw-body'); require('cookie-parser'); require('@c8y/client'); require('date-fns'); require('cross-fetch'); require('http-proxy-middleware'); require('semver'); require('swagger-ui-express'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var ___namespace = /*#__PURE__*/_interopNamespaceDefault(_$2); var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob); var yaml__namespace = /*#__PURE__*/_interopNamespaceDefault(yaml); var c8yctrlExports = index.requireC8yctrl(); /** * Normalizes a URL to ensure it has a protocol and proper trailing slash. * If no protocol is present, HTTPS is added by default. * If the URL has no path component, a trailing slash is appended. * * @param url - The URL string to normalize * @returns The normalized URL with HTTPS protocol and trailing slash if appropriate, or undefined for invalid input */ function normalizeBaseUrl(url) { if (!url || !_$2.isString(url)) { return undefined; } const trimmedUrl = url.trim(); if (!trimmedUrl) { return undefined; } let normalizedUrl; // Check if URL already has a protocol if (/^https?:\/\//i.test(trimmedUrl)) { normalizedUrl = trimmedUrl; } else { // Add https:// if no protocol is present normalizedUrl = `https://${trimmedUrl}`; } try { const urlObj = new URL(normalizedUrl); // remove all components other than protocol, host normalizedUrl = `${urlObj.protocol}//${urlObj.host}`; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to normalize base url ${url}. ${errorMessage}`); } return normalizedUrl; } function safeStringify(obj, indent = 2) { let cache = []; const retVal = JSON.stringify(obj, (key, value) => typeof value === "object" && value !== null ? cache.includes(value) ? undefined : cache.push(value) && value : value, indent); cache = []; return retVal; } /// <reference types="cypress" /> // workaround for lodash import in Cypress nodejs typescript runtime and browser const _$1 = _$2 || ___namespace; /** * Creates an C8yPactID for a given string or array of strings. * @param value The string or array of strings to convert to a pact id. * @returns The pact id. */ function pactId(value) { let result = ""; const suiteSeparator = "__"; const normalize = (value) => value .split(suiteSeparator) .map((v) => _$1.words(_$1.deburr(v), /[a-zA-Z0-9_-]+/g).join("_")) .join(suiteSeparator); if (value != null && _$1.isArray(value)) { result = value.map((v) => normalize(v)).join(suiteSeparator); } else if (value != null && _$1.isString(value)) { result = normalize(value); } if (result == null || _$1.isEmpty(result)) { return !value ? value : undefined; } return result; } const _ = _$2 || ___namespace; /** * Default implementation of C8yPactFileAdapter which loads and saves C8yPact objects * Provide location of the files using folder option. Default location is * cypress/fixtures/c8ypact folder. * * This adapter supports loading of JSON and YAML pact files (.json, .yaml, .yml). When * saviing pact files, it saves them as JSON files (.json). * * By using C8yPactAdapterOptions you can enable loading of JavaScript pact files (.js, .cjs). * Use with caution, as this can lead to security issues if the files are not trusted. */ class C8yPactDefaultFileAdapter { /** * Creates an instance of C8yPactDefaultFileAdapter. * * @param folder - The folder where pact files are stored. Can be an absolute or relative path. * @param options - Optional configuration for the adapter. * @param options.enableJavaScript - If true, enables loading of JavaScript pact files (.js, .cjs). Defaults to false. */ constructor(folder, options) { this.fileExtension = "json"; this.folder = path__namespace.isAbsolute(folder) ? folder : this.toAbsolutePath(folder); this.enabledExtensions = [`.${this.fileExtension}`, ".yaml", ".yml"]; if (options?.enableJavaScript) { this.enabledExtensions.push(".js", ".cjs"); } this.id = options?.id || "fileadapter"; this.log = debug(`c8y:${this.id}`); } description() { return `C8yPactDefaultFileAdapter: ${this.folder}`; } getFolder() { return this.folder; } loadPacts() { const pactObjects = this.loadPactObjects(); this.log(`loadPacts() - ${pactObjects.length} pact files from ${this.folder}`); return pactObjects.reduce((acc, obj) => { if (!obj?.info?.id) return acc; acc[obj.info.id] = obj; return acc; }, {}); } loadPact(id) { this.log(`loadPact() - ${id}`); const pId = pactId(id); if (pId == null) { this.log(`loadPact() - invalid pact id ${id} -> ${pId}`); return null; } if (!this.folder || !fs__namespace.existsSync(this.folder)) { this.log(`loadPact() - folder ${this.folder} does not exist`); return null; } // Try to find the file with different supported extensions const extensions = this.enabledExtensions; let loadedPact = null; for (const ext of extensions) { const file = path__namespace.join(this.folder, `${pId}${ext}`); if (fs__namespace.existsSync(file)) { try { loadedPact = this.loadPactFromFile(file); if (loadedPact) { this.log(`loadPact() - ${file} loaded successfully`); return loadedPact; } } catch (error) { this.log(`loadPact() - error loading ${file}: ${error}`); } } } this.log(`loadPact() - no valid pact file found for id ${pId}`); return null; } pactExists(id) { const pId = pactId(id); return this.enabledExtensions.some((ext) => fs__namespace.existsSync(path__namespace.join(this.folder, `${pId}${ext}`))); } savePact(pact) { this.createFolderRecursive(this.folder); const pId = pactId(pact.id); if (pId == null) { this.log(`savePact() - invalid pact id ${pact.id} -> ${pId}`); return; } const file = path__namespace.join(this.folder, `${pId}.${this.fileExtension}`); this.log(`savePact() - write ${file} (${pact.records?.length || 0} records)`); try { fs__namespace.writeFileSync(file, safeStringify({ id: pact.id, info: pact.info, records: pact.records, }, 2), "utf-8"); } catch (error) { console.error(`Failed to save pact.`, error); } } deletePact(id) { const pId = pactId(id); if (pId == null) { this.log(`deletePact() - invalid pact id ${id} -> ${pId}`); return; } const filePath = path__namespace.join(this.folder, `${pId}.${this.fileExtension}`); if (fs__namespace.existsSync(filePath)) { fs__namespace.unlinkSync(filePath); this.log(`deletePact() - deleted ${filePath}`); } else { this.log(`deletePact() - ${filePath} does not exist`); } } readPactFiles() { this.log(`readPactFiles() - ${this.folder}`); if (!this.folder || !fs__namespace.existsSync(this.folder)) { this.log(`readPactFiles() - ${this.folder} does not exist`); return []; } const pacts = this.loadPactObjects(); return pacts.map((pact) => { return safeStringify(pact); }); } /** * @deprecated Use readPactFiles() instead. */ readJsonFiles() { this.log(`readJsonFiles() - ${this.folder}`); if (!this.folder || !fs__namespace.existsSync(this.folder)) { this.log(`readJsonFiles() - ${this.folder} does not exist`); return []; } const jsonFiles = glob__namespace.sync(path__namespace.join(this.folder, `*.${this.fileExtension}`)); this.log(`readJsonFiles() - reading ${jsonFiles.length} ${this.fileExtension} files from ${this.folder}`); const pacts = jsonFiles.map((file) => { return fs__namespace.readFileSync(file, "utf-8"); }); return pacts; } deleteJsonFiles() { if (!this.folder || !fs__namespace.existsSync(this.folder)) { this.log(`deleteJsonFiles() - ${this.folder} does not exist`); return; } const jsonFiles = glob__namespace.sync(path__namespace.join(this.folder, `*.${this.fileExtension}`)); this.log(`deleteJsonFiles() - deleting ${jsonFiles.length} ${this.fileExtension} files from ${this.folder}`); jsonFiles.forEach((file) => { fs__namespace.unlinkSync(file); }); } loadPactObjects() { this.log(`loadPactObjects() - ${this.folder}`); if (!this.folder || !fs__namespace.existsSync(this.folder)) { this.log(`loadPactObjects() - ${this.folder} does not exist`); return []; } // Find all files with supported extensions const combinedPattern = path__namespace.join(this.folder, `*{${this.enabledExtensions.join(",")}}`); const allFiles = glob__namespace.sync(combinedPattern); this.log(`loadPactObjects() - reading ${allFiles.length} files from ${this.folder}`); // Load and parse each file based on its extension const pactObjects = allFiles .map((file) => { try { return this.loadPactFromFile(file); } catch (error) { this.log(`loadPactObjects() - error loading ${file}: ${error}`); return null; } }) .filter(Boolean); this.log(`loadPactObjects() - loaded ${pactObjects.length} valid pact objects`); return pactObjects; } loadPactFromFile(filePath) { if (!fs__namespace.existsSync(filePath)) { this.log(`loadPactFromFile() - file does not exist: ${filePath}`); return null; } const extension = path__namespace.extname(filePath).toLowerCase(); // Check if the extension is enabled if (!this.enabledExtensions.includes(extension)) { this.log(`loadPactFromFile() - file extension ${extension} is not supported or enabled for loading: ${filePath}`); return null; } const content = fs__namespace.readFileSync(filePath, "utf-8"); try { // Handle different file formats if (extension === `.${this.fileExtension}`) { // Load JSON file return JSON.parse(content); } else if (extension === ".yaml" || extension === ".yml") { // Load YAML file return yaml__namespace.parse(content); } else if (extension === ".js" || extension === ".cjs") { // CommonJS modules (.js, .cjs) can use require const absolutePath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(process.cwd(), filePath); try { // Clear cache if needed if (require.cache && require.cache[require.resolve(absolutePath)]) { delete require.cache[require.resolve(absolutePath)]; } // eslint-disable-next-line @typescript-eslint/no-require-imports const pactModule = require(absolutePath); return pactModule.default || pactModule; } catch (error) { this.log(`loadPactFromFile() - error loading ${extension} file ${absolutePath}: ${error}`); } } } catch (error) { this.log(`loadPactFromFile() - error parsing file ${filePath}: ${error}`); } return null; } createFolderRecursive(f) { this.log(`createFolderRecursive() - ${f}`); if (!f || !_.isString(f)) return undefined; const absolutePath = !path__namespace.isAbsolute(f) ? this.toAbsolutePath(f) : f; if (f !== absolutePath) { this.log(`createFolderRecursive() - resolved ${f} to ${absolutePath}`); } if (fs__namespace.existsSync(f)) return undefined; const result = fs__namespace.mkdirSync(absolutePath, { recursive: true }); if (result) { this.log(`createFolderRecursive() - created ${absolutePath}`); } return result; } toAbsolutePath(f) { return path__namespace.isAbsolute(f) ? f : path__namespace.resolve(process.cwd(), f); } isNodeError(error, type) { return error instanceof type; } } /// <reference types="cypress" /> function getAuthOptionsFromEnv(env) { if (env == null || !_$2.isObjectLike(env)) { return undefined; } // check first environment variables const jwtToken = env["C8Y_TOKEN"]; let tokenAuth = undefined; try { const authFromToken = getAuthOptionsFromJWT(jwtToken); if (authFromToken) { tokenAuth = authWithTenant(env, authFromToken); } } catch { // ignore errors from extractTokensFromJWT // this is expected if the token is not a valid JWT } const user = env[`C8Y_USERNAME`] ?? env[`C8Y_USER`]; const password = env[`C8Y_PASSWORD`]; let basicAuth = undefined; if (!_$2.isEmpty(user) && !_$2.isEmpty(password)) { basicAuth = authWithTenant(env, { user, password, }); } if (!tokenAuth && !basicAuth) { return undefined; } return { ...(basicAuth ?? {}), ...(tokenAuth ?? {}) }; } function authWithTenant(env, options) { if (env == null || !_$2.isObjectLike(env)) { return options; } const tenant = env[`C8Y_TENANT`]; if (tenant && !options?.tenant) { _$2.extend(options, { tenant }); } return options; } /** * Extracts the authentication options from a JWT token. * @param jwtToken The JWT token to extract the authentication options from. * @returns The extracted authentication options. */ function getAuthOptionsFromJWT(jwtToken) { try { const payload = JSON.parse(atob(jwtToken.split(".")[1])); // Remove all characters not valid in JWT tokens (base64url: A-Z, a-z, 0-9, -, _, .) const cleanedToken = jwtToken?.replace(/[^A-Za-z0-9\-_.]/g, ""); return { token: cleanedToken, xsrfToken: payload.xsrfToken, tenant: payload.ten, user: payload.sub, baseUrl: normalizeBaseUrl(payload.aud ?? payload.iss), }; } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to decode JWT token: ${message}`); } } function getPackageVersion() { try { let currentDir = __dirname; let packageJsonPath; let maxLevels = 3; while (maxLevels > 0) { packageJsonPath = path__namespace.resolve(currentDir, "package.json"); if (fs__namespace.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs__namespace.readFileSync(packageJsonPath, "utf8")); return packageJson.version; } currentDir = path__namespace.dirname(currentDir); maxLevels--; } } catch { console.error("Failed to get version from package.json. package.json not found."); } return "unknown"; } debug("c8y:ctrl:http"); function morganErrorOptions(logger = undefined) { const options = { skip: (req, res) => { return (res.statusCode < 400 || req.url.startsWith("/notification/realtime")); }, }; if (logger != null) { options.stream = { write: (message) => { logger.error(message.trim()); }, }; } return options; } const log = debug("c8y:ctrl:startup"); function getEnvVar(name) { return (process.env[name] || process.env[_$2.camelCase(name)] || process.env[`CYPRESS_${name}`] || process.env[name.replace(/^C8Y_/i, "")] || process.env[_$2.camelCase(`CYPRESS_${name}`)] || process.env[`CYPRESS_${_$2.camelCase(name.replace(/^C8Y_/i, ""))}`]); } function getConfigFromArgs() { // doc: https://github.com/yargs/yargs/blob/0c95f9c79e1810cf9c8964fbf7d139009412f7e7/docs/api.md const result = yargs(helpers.hideBin(process.argv)) .usage("Usage: $0 [options]") .scriptName("c8yctrl") .option("folder", { alias: "pactFolder", type: "string", requiresArg: true, description: "The folder recordings are stored in", }) .option("port", { type: "number", requiresArg: true, default: +(getEnvVar("C8YCTRL_PORT") || getEnvVar("C8Y_HTTP_PORT") || 3000), defaultDescription: "3000", description: "The HTTP port c8yctrl listens on", }) .option("baseUrl", { alias: "baseurl", type: "string", requiresArg: true, description: "The Cumulocity URL for proxying requests", }) .option("user", { alias: "username", requiresArg: true, type: "string", description: "The username to login at baseUrl", }) .option("password", { type: "string", requiresArg: true, description: "The password to login at baseUrl", }) .option("tenant", { type: "string", requiresArg: true, description: "The tenant id of baseUrl", }) .option("staticRoot", { alias: "static", requiresArg: true, type: "string", description: "The root folder to serve static files from", }) .option("mode", { type: "string", requiresArg: true, default: getEnvVar("C8YCTRL_MODE") || c8yctrlExports.C8yPactHttpControllerDefaultMode, defaultDescription: c8yctrlExports.C8yPactHttpControllerDefaultMode, description: `One of ${Object.values(c8yctrlExports.C8yPactModeValues) .filter((m) => m !== "recording") .join(", ")}`, }) .option("recordingMode", { type: "string", requiresArg: true, default: getEnvVar("C8YCTRL_RECORDING_MODE") || c8yctrlExports.C8yPactHttpControllerDefaultRecordingMode, defaultDescription: c8yctrlExports.C8yPactHttpControllerDefaultRecordingMode, description: `One of ${Object.values(c8yctrlExports.C8yPactRecordingModeValues).join(", ")}`, }) .option("config", { type: "string", requiresArg: true, default: getEnvVar("C8YCTRL_CONFIG") || "c8yctrl.config.ts", description: "The path to the config file", }) .option("log", { type: "boolean", default: getEnvVar("C8YCTRL_LOG") !== "false", defaultDescription: "true", requiresArg: false, description: "Enable or disable logging", }) .option("logLevel", { type: "string", default: getEnvVar("C8YCTRL_LOG_LEVEL") || "info", defaultDescription: "info", requiresArg: true, description: "The log level used for logging", }) .option("logFile", { type: "string", requiresArg: true, description: "The path of the logfile", }) .option("accessLogFile", { type: "string", requiresArg: true, description: "The path of the access logfile", }) .option("apps", { type: "array", requiresArg: true, description: "Array of of static folder app names and semver ranges separated by '/'", coerce: (arg) => parseApps(arg), }) .help() .wrap(120) .version(getPackageVersion()) .parseSync(); const logLevelValues = Object.values(c8yctrlExports.C8yPactHttpControllerLogLevel); // pick only the options that are set and apply defaults // yargs creates properties we do not want, this way we can filter them out return [ { folder: result.folder, port: result.port, baseUrl: result.baseUrl, user: result.user, password: result.password, tenant: result.tenant, staticRoot: result.staticRoot, logFilename: result.logFile, accessLogFilename: result.accessLogFile, log: result.log, logLevel: logLevelValues.includes(result.logLevel || "") ? result.logLevel : undefined, appsVersions: result.apps, mode: result.mode, recordingMode: result.recordingMode, }, result.config, ]; } function getConfigFromEnvironment() { const auth = getAuthOptionsFromEnv(process.env); return { folder: getEnvVar("C8YCTRL_FOLDER"), port: +(getEnvVar("C8YCTRL_PORT") || getEnvVar("C8Y_HTTP_PORT") || 3000), baseUrl: normalizeBaseUrl(getEnvVar("C8YCTRL_BASEURL") || getEnvVar("C8Y_BASE_URL") || getEnvVar("C8Y_BASEURL") || getEnvVar("C8Y_HOST") || auth?.baseUrl), user: getEnvVar("C8YCTRL_USERNAME") || getEnvVar("C8YCTRL_USER") || getEnvVar("C8Y_USERNAME") || getEnvVar("C8Y_USER"), password: getEnvVar("C8YCTRL_PASSWORD") || getEnvVar("C8Y_PASSWORD"), tenant: getEnvVar("C8YCTRL_TENANT") || getEnvVar("C8Y_TENANT"), staticRoot: getEnvVar("C8YCTRL_ROOT") || // compatibility with old env var names getEnvVar("C8Y_STATIC") || getEnvVar("C8Y_STATIC_ROOT"), logFilename: getEnvVar("C8YCTRL_LOG_FILE"), accessLogFilename: getEnvVar("C8YCTRL_ACCESS_LOG_FILE"), log: getEnvVar("C8YCTRL_LOG") !== "false", logLevel: getEnvVar("C8YCTRL_LOG_LEVEL"), mode: getEnvVar("C8YCTRL_MODE"), recordingMode: getEnvVar("C8YCTRL_RECORDING_MODE"), config: getEnvVar("C8YCTRL_CONFIG"), appsVersions: parseApps(getEnvVar("C8YCTRL_APPS")), auth, }; } function getConfigFromArgsOrEnvironment() { const [args, config] = getConfigFromArgs(); const env = getConfigFromEnvironment(); return [_$2.defaults(args, env), config]; } function validateConfig(config) { if (!c8yctrlExports.C8yPactModeValues.includes(config.mode)) { throw new Error(`Configured mode "${config.mode}" is not valid. Must be one of ${c8yctrlExports.C8yPactModeValues.join(", ")}.`); } if (!c8yctrlExports.C8yPactRecordingModeValues.includes(config.recordingMode)) { throw new Error(`Configured recording mode "${config.recordingMode}" is not valid. Must be one of ${c8yctrlExports.C8yPactRecordingModeValues.join(", ")}.`); } } const safeTransports = !_$2.isEmpty(winston.transports) ? winston.transports : transportsDirect; /** * Default logger for the HTTP controller. It logs to the console with colors and simple format. * This needs to be passed to the config, so it must be created before applying the default config. */ const defaultLogger = winston.createLogger({ transports: [ new safeTransports.Console({ format: winston.format.combine(winston.format.colorize({ all: true, colors: { info: "green", error: "red", warn: "yellow", debug: "white", }, }), winston.format.simple()), }), ], }); /** * Default config object for the HTTP controller. It takes a configuration object and * adds required defaults, as for example the adapter, an error response record or the logger. * * This config can be overwritten by a config file, which is loaded by cosmiconfig. */ const applyDefaultConfig = (config) => { if (!config?.auth) { log("no auth options provided, trying to create from user and password"); const { user, password, tenant } = config; config.auth = user && password ? { user, password, tenant } : undefined; } if (!("on" in config)) { config.on = {}; log("configured empty object callback 'on' property of config"); } // check all default properties as _.defaults seems to still overwrite in some cases if (!("adapter" in config)) { config.adapter = new C8yPactDefaultFileAdapter(config.folder || "./c8ypact"); log(`configured default file adapter for folder ${config.folder || "./c8ypact"}.`); } if (!("mockNotFoundResponse" in config)) { config.mockNotFoundResponse = (url) => { return { status: 404, statusText: "Not Found", body: `Not Found: ${url}`, headers: { "content-type": "application/text", }, }; }; log("configured default 404 text mockNotFoundResponse"); } if (!("requestMatching" in config)) { config.requestMatching = { ignoreUrlParameters: ["dateFrom", "dateTo", "_", "nocache"], baseUrl: config.baseUrl, }; log("configured default requestMatching"); } if (!("preprocessor" in config)) { // use default preprocessor config config.preprocessor = new c8yctrlExports.C8yDefaultPactPreprocessor(); log("configured default preprocessor"); } applyDefaultLogConfig(config); return config; }; const applyDefaultLogConfig = (config) => { if ("log" in config && config.log === false) { log("disabled logging as config.log == false"); config.logger = undefined; config.requestLogger = undefined; return; } if (!("logger" in config)) { config.logger = defaultLogger; log("configured default logger"); } if ("logFilename" in config && config.logFilename != null && config.logger != null) { const p = path.isAbsolute(config.logFilename) ? config.accessLogFilename : path.join(process.cwd(), config.logFilename); config.logger.add(new safeTransports.File({ format: winston.format.simple(), filename: p, })); log(`configured default logger file transport ${p}.`); } if ("logLevel" in config && config.logLevel != null && config.logger != null) { config.logger.level = config.logLevel; log(`configured log level ${config.logLevel}.`); } if (!("requestLogger" in config)) { config.requestLogger = [ morgan("[c8yctrl] :method :url :status :res[content-length] - :response-time ms", { skip: (req) => { return (!req.url.startsWith("/c8yctrl") || req.url.startsWith("/c8yctrl/log")); }, stream: { write: (message) => { config.logger?.warn(message.trim()); }, }, }), ]; log("configured default requestLogger for /c8yctrl interface and errors"); } if (!("errorLogger" in config) && config.errorLogger == null) { if (morgan["error-object"] == null) { morgan.token("error-object", (req, res) => { let resBody = res.body; if (_$2.isString(resBody) && // parse as json only if body is a cumulocity error response /"error"\s*:\s*"/.test(resBody) && /"message"\s*:\s*"/.test(resBody)) { try { resBody = JSON.parse(resBody); } catch { // ignore, use body as string } } // make sure we do not log too much if (_$2.isString(resBody)) { resBody = resBody.slice(0, 1000); } const errorObject = { url: req.url, status: `${res.statusCode} ${res.statusMessage}`, requestHeader: req.headers, responseHeader: res.getHeaders(), responseBody: resBody, requestBody: req.body, }; return safeStringify(errorObject); }); log("default morgan error-object token compiled and registered"); } config.errorLogger = morgan(":error-object", morganErrorOptions(config.logger)); log("configured default error logger"); } if ("accessLogFilename" in config && config.accessLogFilename != null) { const p = path.isAbsolute(config.accessLogFilename) ? config.accessLogFilename : path.join(process.cwd(), config.accessLogFilename); const accessLogger = morgan("common", { stream: fs.createWriteStream(p, { flags: "a", }), }); if (config.requestLogger != null) { if (_$2.isArrayLike(config.requestLogger)) { config.requestLogger.push(accessLogger); log(`configured file access logger to existing logger ${p}`); } } else { config.requestLogger = [accessLogger]; log(`configured file access logger ${p}`); } } }; const parseApps = (value) => { if (value == null) return undefined; const apps = {}; (_$2.isArray(value) ? value : value.split(",")).forEach((item) => { const [key, ...value] = item.trim().split("/"); const semverRange = value.join("/"); if (key != null && value != null && semverRange != null) { apps[key] = semverRange; } }); return apps; }; exports.applyDefaultConfig = applyDefaultConfig; exports.c8yctrlExports = c8yctrlExports; exports.defaultLogger = defaultLogger; exports.getConfigFromArgs = getConfigFromArgs; exports.getConfigFromArgsOrEnvironment = getConfigFromArgsOrEnvironment; exports.getConfigFromEnvironment = getConfigFromEnvironment; exports.getEnvVar = getEnvVar; exports.parseApps = parseApps; exports.validateConfig = validateConfig;