cumulocity-cypress
Version:
Cypress commands for Cumulocity IoT
860 lines (846 loc) • 32.1 kB
JavaScript
;
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;