From ccdeff98dc9948d3a746b0d278738e22cec5a166 Mon Sep 17 00:00:00 2001 From: Raj Nandan Sharma Date: Sat, 22 Mar 2025 21:54:24 +0530 Subject: [PATCH] feat: implement dynamic versioning in headers and documentation as reported in #346 --- src/lib/server/notification/discord.js | 3 +- src/lib/server/notification/email.js | 151 +++++++++--------- src/lib/server/notification/slack.js | 3 +- src/lib/server/notification/webhook.js | 3 +- src/lib/server/startup.js | 21 +-- src/lib/version.js | 31 ++++ src/routes/(docs)/+layout.server.js | 11 +- src/routes/(docs)/+layout.svelte | 3 +- .../(manage)/manage/(app)/+layout.server.js | 2 + .../(manage)/manage/(app)/+layout.svelte | 9 +- 10 files changed, 128 insertions(+), 109 deletions(-) create mode 100644 src/lib/version.js diff --git a/src/lib/server/notification/discord.js b/src/lib/server/notification/discord.js index 569d46e9..1b8f51ad 100644 --- a/src/lib/server/notification/discord.js +++ b/src/lib/server/notification/discord.js @@ -3,6 +3,7 @@ import Mustache from "mustache"; import { DiscordJSONTemplate } from "../../anywhere.js"; import variables from "./variables.js"; import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js"; +import version from "../../version.js"; class Discord { url; @@ -15,7 +16,7 @@ class Discord { constructor(url, siteData, monitorData, trigger_meta) { const kenerHeader = { "Content-Type": "application/json", - "User-Agent": "Kener", + "User-Agent": `Kener/${version()}`, }; this.url = url; diff --git a/src/lib/server/notification/email.js b/src/lib/server/notification/email.js index ec795625..fd8c4d3d 100644 --- a/src/lib/server/notification/email.js +++ b/src/lib/server/notification/email.js @@ -3,56 +3,57 @@ import { Resend } from "resend"; import nodemailer from "nodemailer"; import getSMTPTransport from "./smtps.js"; import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js"; +import version from "../../version.js"; class Email { - to; - from; - method; - siteData; - monitorData; - meta; + to; + from; + method; + siteData; + monitorData; + meta; - constructor(meta, siteData, monitorData) { - this.to = meta.to; - this.from = meta.from; - this.siteData = siteData; - this.monitorData = monitorData; + constructor(meta, siteData, monitorData) { + this.to = meta.to; + this.from = meta.from; + this.siteData = siteData; + this.monitorData = monitorData; - let metaString = JSON.stringify(meta); + let metaString = JSON.stringify(meta); - let envSecrets = GetRequiredSecrets(`${JSON.stringify(meta)}`); + let envSecrets = GetRequiredSecrets(`${JSON.stringify(meta)}`); - for (let i = 0; i < envSecrets.length; i++) { - const secret = envSecrets[i]; - metaString = ReplaceAllOccurrences(metaString, secret.find, secret.replace); - } + for (let i = 0; i < envSecrets.length; i++) { + const secret = envSecrets[i]; + metaString = ReplaceAllOccurrences(metaString, secret.find, secret.replace); + } - this.meta = JSON.parse(metaString); - } + this.meta = JSON.parse(metaString); + } - transformData(data) { - let ctaLink = data.actions[0].url; - let ctaText = data.actions[0].text; - let emailApiRequest = { - from: this.from, - to: this.to.split(",").map((email) => email.trim()), - subject: `[${data.status}] ${data.alert_name} at ${data.timestamp}`, - text: `Alert ${data.alert_name} has been ${data.status} at ${data.timestamp}. Click here to view the alert: ${ctaLink}`, - html: "" - }; + transformData(data) { + let ctaLink = data.actions[0].url; + let ctaText = data.actions[0].text; + let emailApiRequest = { + from: this.from, + to: this.to.split(",").map((email) => email.trim()), + subject: `[${data.status}] ${data.alert_name} at ${data.timestamp}`, + text: `Alert ${data.alert_name} has been ${data.status} at ${data.timestamp}. Click here to view the alert: ${ctaLink}`, + html: "", + }; - let bgColor = "#f4f4f4"; - if (data.severity == "critical") { - bgColor = this.siteData.colors.DOWN; - } else if (data.severity == "warning") { - bgColor = this.siteData.colors.DEGRADED; - } + let bgColor = "#f4f4f4"; + if (data.severity == "critical") { + bgColor = this.siteData.colors.DOWN; + } else if (data.severity == "warning") { + bgColor = this.siteData.colors.DEGRADED; + } - if (data.status === "RESOLVED") { - bgColor = this.siteData.colors.UP; - } + if (data.status === "RESOLVED") { + bgColor = this.siteData.colors.UP; + } - let html = ` + let html = ` @@ -177,53 +178,53 @@ class Email { `; - emailApiRequest.html = html; + emailApiRequest.html = html; - return emailApiRequest; - } + return emailApiRequest; + } - type() { - return "email"; - } + type() { + return "email"; + } - async send(data) { - let emailBody = this.transformData(data); // object containing email data (to, subject, text, html, etc) - if (!this.meta.email_type || this.meta.email_type === "resend") { - const resend = new Resend(process.env.RESEND_API_KEY); - try { - return await resend.emails.send(emailBody); - } catch (error) { - console.error("Error sending webhook", error); - return error; - } - } - if (this.meta.email_type === "smtp") { - // Configure the SMTP transporter using environment variables - const transporter = getSMTPTransport(this.meta); + async send(data) { + let emailBody = this.transformData(data); // object containing email data (to, subject, text, html, etc) + if (!this.meta.email_type || this.meta.email_type === "resend") { + const resend = new Resend(process.env.RESEND_API_KEY); + try { + return await resend.emails.send(emailBody); + } catch (error) { + console.error("Error sending webhook", error); + return error; + } + } + if (this.meta.email_type === "smtp") { + // Configure the SMTP transporter using environment variables + const transporter = getSMTPTransport(this.meta); - const mailOptions = { - from: emailBody.from, // sender address - to: Array.isArray(emailBody.to) ? emailBody.to.join(",") : emailBody.to, // recipient address(es) - subject: emailBody.subject, // email subject - text: emailBody.text, // plain text body - html: emailBody.html // HTML body (if any) - }; + const mailOptions = { + from: emailBody.from, // sender address + to: Array.isArray(emailBody.to) ? emailBody.to.join(",") : emailBody.to, // recipient address(es) + subject: emailBody.subject, // email subject + text: emailBody.text, // plain text body + html: emailBody.html, // HTML body (if any) + }; - try { - return await transporter.sendMail(mailOptions); - } catch (error) { - console.error("Error sending email via SMTP", error); - return error; - } - } - } + try { + return await transporter.sendMail(mailOptions); + } catch (error) { + console.error("Error sending email via SMTP", error); + return error; + } + } + } } export default Email; diff --git a/src/lib/server/notification/slack.js b/src/lib/server/notification/slack.js index d810344c..f309b865 100644 --- a/src/lib/server/notification/slack.js +++ b/src/lib/server/notification/slack.js @@ -3,6 +3,7 @@ import variables from "./variables.js"; import { SlackJSONTemplate } from "../../anywhere.js"; import Mustache from "mustache"; import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js"; +import version from "../../version.js"; class Slack { url; @@ -15,7 +16,7 @@ class Slack { constructor(url, siteData, monitorData, trigger_meta) { const kenerHeader = { "Content-Type": "application/json", - "User-Agent": "Kener", + "User-Agent": `Kener/${version()}`, }; this.url = url; diff --git a/src/lib/server/notification/webhook.js b/src/lib/server/notification/webhook.js index dae1fb99..b263d142 100644 --- a/src/lib/server/notification/webhook.js +++ b/src/lib/server/notification/webhook.js @@ -4,6 +4,7 @@ import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js"; import Mustache from "mustache"; import { WebhookJSONTemplate } from "../../anywhere.js"; import variables from "./variables.js"; +import version from "../../version.js"; class Webhook { url; @@ -16,7 +17,7 @@ class Webhook { constructor(trigger_meta, method, siteData, monitorData) { const kenerHeader = { "Content-Type": "application/json", - "User-Agent": "Kener/3.2.5", + "User-Agent": `Kener/${version()}`, }; let headers = trigger_meta.headers; this.trigger_meta = trigger_meta; diff --git a/src/lib/server/startup.js b/src/lib/server/startup.js index 3f2382d4..aae26b2d 100644 --- a/src/lib/server/startup.js +++ b/src/lib/server/startup.js @@ -9,25 +9,12 @@ import { HashString } from "./tool.js"; import { fileURLToPath } from "url"; import { dirname, resolve } from "path"; import fs from "fs"; +import version from "../version.js"; const jobs = []; process.env.TZ = "UTC"; let isStartUP = true; -// Get the version from package.json -const getVersion = () => { - try { - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const packagePath = resolve(__dirname, "../../../package.json"); - const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8")); - return packageJson.version; - } catch (error) { - console.error("Error reading version:", error); - return "unknown"; - } -}; - const scheduleCronJobs = async () => { // Fetch and map all active monitors, creating a unique hash for each const activeMonitors = (await GetMonitorsParsed({ status: "ACTIVE" })).map((monitor) => ({ @@ -86,15 +73,13 @@ async function Startup() { mainJob.trigger(); - const version = getVersion(); - - figlet("Kener v" + version, function (err, data) { + figlet("Kener v" + version(), function (err, data) { if (err) { console.log("Something went wrong..."); return; } console.log(data); - console.log(`Kener version ${version} is running!`); + console.log(`Kener version ${version()} is running!`); }); } diff --git a/src/lib/version.js b/src/lib/version.js new file mode 100644 index 00000000..e66c844a --- /dev/null +++ b/src/lib/version.js @@ -0,0 +1,31 @@ +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; +import fs from "fs"; + +function getVersionUsingVite() { + try { + if (!!import.meta.env.PACKAGE_VERSION) { + return import.meta.env.PACKAGE_VERSION; + } + return null; + } catch (e) { + return null; + } +} + +export default function version() { + let v = getVersionUsingVite(); + if (!!v) { + return v; + } else { + try { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const packagePath = resolve(__dirname, "../../package.json"); + const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8")); + return packageJson.version; + } catch (e) { + return "0.0.0"; + } + } +} diff --git a/src/routes/(docs)/+layout.server.js b/src/routes/(docs)/+layout.server.js index ea367218..dae58cb6 100644 --- a/src/routes/(docs)/+layout.server.js +++ b/src/routes/(docs)/+layout.server.js @@ -2,6 +2,7 @@ import fs from "fs-extra"; import path from "path"; import { marked } from "marked"; +import version from "$lib/version.js"; function extractMetadataAndContent(fileContent) { // Regular expression to match metadata section @@ -15,9 +16,7 @@ function extractMetadataAndContent(fileContent) { if (match) { // Extract metadata section and remaining content - const metadataLines = match[1] - .split("\n") - .filter((line) => line.trim() !== ""); + const metadataLines = match[1].split("\n").filter((line) => line.trim() !== ""); result.content = fileContent.slice(match[0].length).trim(); // Remaining content after metadata metadataLines.forEach((line) => { @@ -40,10 +39,7 @@ export async function load({ params, route, url, cookies, request }) { docFilePath = docFolderPath + `/${docFile}.md`; } const fileContents = await fs.readFileSync(docFilePath, "utf-8"); - const siteStructure = await fs.readFileSync( - docFolderPath + "/structure.json", - "utf-8", - ); + const siteStructure = await fs.readFileSync(docFolderPath + "/structure.json", "utf-8"); const { metadata, content } = extractMetadataAndContent(fileContents); const selectedDoc = docFilePath.replace(docFolderPath, ""); @@ -54,5 +50,6 @@ export async function load({ params, route, url, cookies, request }) { title: metadata.title || "Kener Docs", description: metadata.description || "Kener Docs", siteStructure: siteStructureJSON, + kenerVersion: version(), }; } diff --git a/src/routes/(docs)/+layout.svelte b/src/routes/(docs)/+layout.svelte index b317d41b..75c2de24 100644 --- a/src/routes/(docs)/+layout.svelte +++ b/src/routes/(docs)/+layout.svelte @@ -11,6 +11,7 @@ import Moon from "lucide-svelte/icons/moon"; import { onMount } from "svelte"; import { base } from "$app/paths"; + import { page } from "$app/stores"; let defaultTheme = "light"; export let data; let siteStructure = data.siteStructure; @@ -111,7 +112,7 @@ Kener Documentation - {import.meta.env.PACKAGE_VERSION} + {$page.data.kenerVersion} diff --git a/src/routes/(manage)/manage/(app)/+layout.server.js b/src/routes/(manage)/manage/(app)/+layout.server.js index dec34b01..dd62b7d1 100644 --- a/src/routes/(manage)/manage/(app)/+layout.server.js +++ b/src/routes/(manage)/manage/(app)/+layout.server.js @@ -4,6 +4,7 @@ import { redirect } from "@sveltejs/kit"; import { base } from "$app/paths"; import { MaskString } from "$lib/server/tool.js"; import db from "$lib/server/db/db.js"; +import version from "$lib/version.js"; //write a function to mask a string, just have last 4 characters visible export async function load({ params, route, url, cookies, request }) { @@ -25,5 +26,6 @@ export async function load({ params, route, url, cookies, request }) { siteData, KENER_SECRET_KEY: !!process.env.KENER_SECRET_KEY ? MaskString(process.env.KENER_SECRET_KEY) : "", user: userDB, + kenerVersion: version(), }; } diff --git a/src/routes/(manage)/manage/(app)/+layout.svelte b/src/routes/(manage)/manage/(app)/+layout.svelte index dd8bd6ec..1f4f75ae 100644 --- a/src/routes/(manage)/manage/(app)/+layout.svelte +++ b/src/routes/(manage)/manage/(app)/+layout.svelte @@ -9,9 +9,7 @@ import Github from "lucide-svelte/icons/github"; import { Button } from "$lib/components/ui/button"; import { afterNavigate } from "$app/navigation"; - - const version = import.meta.env.PACKAGE_VERSION; - + import { page } from "$app/stores"; import { Play, User } from "lucide-svelte"; import { setMode, mode, ModeWatcher } from "mode-watcher"; @@ -23,7 +21,6 @@ setMode("light"); } } - // setMode("dark"); let nav = [ @@ -168,8 +165,10 @@ v{version} + v{$page.data.kenerVersion} + by