diff --git a/_reference/localEmailViewer/README.md b/_reference/localEmailViewer/README.md index 6054686fd..238c2bf22 100644 --- a/_reference/localEmailViewer/README.md +++ b/_reference/localEmailViewer/README.md @@ -1,5 +1,7 @@ -This app connects to your Docker LocalStack SES endpoint and gives you a local inbox-style viewer -for generated emails. +This app connects to your Docker LocalStack endpoints and gives you a compact inspector for: + +- SES generated emails +- CloudWatch log groups, streams, and recent events ```shell npm start @@ -15,13 +17,11 @@ Open: http://localhost:3334 Features: -- Manual refresh -- Live refresh with adjustable polling interval -- Search across subject, addresses, preview text, and attachment names -- Expand/collapse all messages -- Rendered HTML, plain-text, and raw MIME views -- Copy raw MIME source -- New-message highlighting plus fetch timing and parse-error stats +- SES email workspace with manual refresh, live refresh, search, HTML/text/raw views, + attachment downloads, and new-message highlighting +- CloudWatch Logs workspace with log group selection, stream filtering, adjustable time window, + adjustable event limit, live refresh, and in-browser log search +- Compact single-page UI for switching between the local stack tools you use most Optional environment variables: @@ -30,4 +30,9 @@ PORT=3334 SES_VIEWER_ENDPOINT=http://localhost:4566/_aws/ses SES_VIEWER_REFRESH_MS=10000 SES_VIEWER_FETCH_TIMEOUT_MS=5000 +CLOUDWATCH_VIEWER_ENDPOINT=http://localhost:4566 +CLOUDWATCH_VIEWER_REGION=ca-central-1 +CLOUDWATCH_VIEWER_LOG_GROUP=development +CLOUDWATCH_VIEWER_WINDOW_MS=900000 +CLOUDWATCH_VIEWER_LIMIT=200 ``` diff --git a/_reference/localEmailViewer/index.js b/_reference/localEmailViewer/index.js index 7f35fa393..4d935135a 100644 --- a/_reference/localEmailViewer/index.js +++ b/_reference/localEmailViewer/index.js @@ -1,5 +1,11 @@ import express from "express"; import fetch from "node-fetch"; +import { + CloudWatchLogsClient, + DescribeLogGroupsCommand, + DescribeLogStreamsCommand, + FilterLogEventsCommand +} from "@aws-sdk/client-cloudwatch-logs"; import { simpleParser } from "mailparser"; const app = express(); @@ -8,6 +14,20 @@ const PORT = Number(process.env.PORT || 3334); const SES_ENDPOINT = process.env.SES_VIEWER_ENDPOINT || "http://localhost:4566/_aws/ses"; const FETCH_TIMEOUT_MS = Number(process.env.SES_VIEWER_FETCH_TIMEOUT_MS || 5000); const DEFAULT_REFRESH_MS = Number(process.env.SES_VIEWER_REFRESH_MS || 10000); +const CLOUDWATCH_ENDPOINT = process.env.CLOUDWATCH_VIEWER_ENDPOINT || "http://localhost:4566"; +const CLOUDWATCH_REGION = process.env.CLOUDWATCH_VIEWER_REGION || process.env.AWS_DEFAULT_REGION || "ca-central-1"; +const CLOUDWATCH_DEFAULT_GROUP = process.env.CLOUDWATCH_VIEWER_LOG_GROUP || "development"; +const CLOUDWATCH_DEFAULT_WINDOW_MS = Number(process.env.CLOUDWATCH_VIEWER_WINDOW_MS || 15 * 60 * 1000); +const CLOUDWATCH_DEFAULT_LIMIT = Number(process.env.CLOUDWATCH_VIEWER_LIMIT || 200); +const LOCALSTACK_CREDENTIALS = { + accessKeyId: process.env.AWS_ACCESS_KEY_ID || "test", + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "test" +}; +const cloudWatchLogsClient = new CloudWatchLogsClient({ + region: CLOUDWATCH_REGION, + endpoint: CLOUDWATCH_ENDPOINT, + credentials: LOCALSTACK_CREDENTIALS +}); app.use((req, res, next) => { res.set("Cache-Control", "no-store"); @@ -26,6 +46,10 @@ app.get("/health", (req, res) => { res.json({ ok: true, endpoint: SES_ENDPOINT, + endpoints: { + ses: SES_ENDPOINT, + cloudWatchLogs: CLOUDWATCH_ENDPOINT + }, port: PORT, defaultRefreshMs: DEFAULT_REFRESH_MS }); @@ -96,6 +120,71 @@ app.get("/api/messages/:id/attachments/:index", async (req, res) => { } }); +app.get("/api/logs/groups", async (req, res) => { + try { + const groups = await loadLogGroups(); + res.json({ + endpoint: CLOUDWATCH_ENDPOINT, + region: CLOUDWATCH_REGION, + defaultGroup: CLOUDWATCH_DEFAULT_GROUP, + groups + }); + } catch (error) { + console.error("Error fetching log groups:", error); + res.status(502).json({ + error: "Unable to fetch CloudWatch log groups from LocalStack", + details: error.message, + endpoint: CLOUDWATCH_ENDPOINT + }); + } +}); + +app.get("/api/logs/streams", async (req, res) => { + try { + const logGroupName = String(req.query.group || ""); + + if (!logGroupName) { + res.status(400).json({ error: "Query parameter 'group' is required" }); + return; + } + + res.json({ + logGroupName, + streams: await loadLogStreams(logGroupName) + }); + } catch (error) { + console.error("Error fetching log streams:", error); + res.status(502).json({ + error: "Unable to fetch CloudWatch log streams from LocalStack", + details: error.message, + endpoint: CLOUDWATCH_ENDPOINT + }); + } +}); + +app.get("/api/logs/events", async (req, res) => { + try { + const logGroupName = String(req.query.group || ""); + const logStreamName = String(req.query.stream || ""); + const windowMs = clampNumber(req.query.windowMs, CLOUDWATCH_DEFAULT_WINDOW_MS, 60 * 1000, 24 * 60 * 60 * 1000); + const limit = clampNumber(req.query.limit, CLOUDWATCH_DEFAULT_LIMIT, 25, 500); + + if (!logGroupName) { + res.status(400).json({ error: "Query parameter 'group' is required" }); + return; + } + + res.json(await loadLogEvents({ logGroupName, logStreamName, windowMs, limit })); + } catch (error) { + console.error("Error fetching log events:", error); + res.status(502).json({ + error: "Unable to fetch CloudWatch log events from LocalStack", + details: error.message, + endpoint: CLOUDWATCH_ENDPOINT + }); + } +}); + async function loadMessages() { const startedAt = Date.now(); const sesMessages = await fetchSesMessages(); @@ -133,6 +222,136 @@ async function fetchSesMessages() { return Array.isArray(data.messages) ? data.messages : []; } +async function loadLogGroups() { + const groups = []; + let nextToken; + let pageCount = 0; + + do { + const response = await cloudWatchLogsClient.send( + new DescribeLogGroupsCommand({ + nextToken, + limit: 50 + }) + ); + + groups.push( + ...(response.logGroups || []).map((group) => ({ + name: group.logGroupName || "", + arn: group.arn || "", + storedBytes: group.storedBytes || 0, + retentionInDays: group.retentionInDays || 0, + creationTime: group.creationTime || 0 + })) + ); + + nextToken = response.nextToken; + pageCount += 1; + } while (nextToken && pageCount < 10); + + return groups.sort((left, right) => left.name.localeCompare(right.name)); +} + +async function loadLogStreams(logGroupName) { + const streams = []; + let nextToken; + let pageCount = 0; + + do { + const response = await cloudWatchLogsClient.send( + new DescribeLogStreamsCommand({ + logGroupName, + descending: true, + orderBy: "LastEventTime", + nextToken, + limit: 50 + }) + ); + + streams.push( + ...(response.logStreams || []).map((stream) => ({ + name: stream.logStreamName || "", + arn: stream.arn || "", + lastEventTimestamp: stream.lastEventTimestamp || 0, + lastIngestionTime: stream.lastIngestionTime || 0, + storedBytes: stream.storedBytes || 0 + })) + ); + + nextToken = response.nextToken; + pageCount += 1; + } while (nextToken && pageCount < 6 && streams.length < 250); + + return streams; +} + +async function loadLogEvents({ logGroupName, logStreamName, windowMs, limit }) { + const startedAt = Date.now(); + const eventMap = new Map(); + const startTime = Date.now() - windowMs; + let nextToken; + let previousToken = ""; + let pageCount = 0; + let searchedLogStreams = 0; + + do { + const response = await cloudWatchLogsClient.send( + new FilterLogEventsCommand({ + logGroupName, + logStreamNames: logStreamName ? [logStreamName] : undefined, + startTime, + endTime: Date.now(), + limit, + nextToken + }) + ); + + for (const event of response.events || []) { + const id = + event.eventId || `${event.logStreamName || "stream"}-${event.timestamp || 0}-${event.ingestionTime || 0}`; + + if (!eventMap.has(id)) { + const message = String(event.message || "").trim(); + eventMap.set(id, { + id, + timestamp: event.timestamp || 0, + ingestionTime: event.ingestionTime || 0, + logStreamName: event.logStreamName || "", + message, + preview: buildLogPreview(message) + }); + } + } + + searchedLogStreams = Math.max(searchedLogStreams, (response.searchedLogStreams || []).length); + previousToken = nextToken || ""; + nextToken = response.nextToken; + pageCount += 1; + } while (nextToken && nextToken !== previousToken && pageCount < 10 && eventMap.size < limit); + + const events = [...eventMap.values()] + .sort((left, right) => { + if ((right.timestamp || 0) !== (left.timestamp || 0)) { + return (right.timestamp || 0) - (left.timestamp || 0); + } + + return left.logStreamName.localeCompare(right.logStreamName); + }) + .slice(0, limit); + + return { + endpoint: CLOUDWATCH_ENDPOINT, + region: CLOUDWATCH_REGION, + logGroupName, + logStreamName, + fetchDurationMs: Date.now() - startedAt, + latestTimestamp: events[0]?.timestamp || 0, + searchedLogStreams, + totalEvents: events.length, + events + }; +} + async function findSesMessageById(id) { const messages = await fetchSesMessages(); return messages.find((message, index) => resolveMessageId(message, index) === id) || null; @@ -274,6 +493,28 @@ function buildPreview(textContent, renderedHtml) { return source.length > 220 ? `${source.slice(0, 217)}...` : source; } +function buildLogPreview(message) { + const source = String(message || "") + .replace(/\s+/g, " ") + .trim(); + + if (!source) { + return "No log preview available."; + } + + return source.length > 220 ? `${source.slice(0, 217)}...` : source; +} + +function clampNumber(value, fallback, min, max) { + const parsed = Number(value); + + if (!Number.isFinite(parsed)) { + return fallback; + } + + return Math.min(Math.max(parsed, min), max); +} + function buildRenderedHtml(html) { if (!html) { return ""; @@ -325,7 +566,12 @@ function formatAddressList(addresses) { function getClientConfig() { return { defaultRefreshMs: DEFAULT_REFRESH_MS, - endpoint: SES_ENDPOINT + endpoint: SES_ENDPOINT, + cloudWatchEndpoint: CLOUDWATCH_ENDPOINT, + cloudWatchRegion: CLOUDWATCH_REGION, + defaultLogGroup: CLOUDWATCH_DEFAULT_GROUP, + defaultLogWindowMs: CLOUDWATCH_DEFAULT_WINDOW_MS, + defaultLogLimit: CLOUDWATCH_DEFAULT_LIMIT }; } @@ -335,18 +581,28 @@ function renderHtml() { - LocalStack Email Viewer + LocalStack Inspector
-

LocalStack SES

-

Email Viewer

-

Compact inbox for generated emails with live polling, search, and raw MIME inspection.

+

LocalStack Toolbox

+

Inspector

+

Inspect generated SES mail and CloudWatch logs from the same local stack console.

+
+ + +
+

Keep outbound email inspection and CloudWatch tailing in one local viewer without leaving the browser.

+
+
+ +
+
@@ -359,29 +615,94 @@ function renderHtml() { + Waiting for first refresh...
-
-
- Waiting for first refresh... +
+
+ +
+
Total00 visible
+
New0New since last refresh
+
NewestNo messagesNot refreshed yet
+
FetchIdleEndpoint: ${escapeHtml(SES_ENDPOINT)}
+
+ +
+
+ + +
- - -
-
Total00 visible
-
New0New since last refresh
-
NewestNo messagesNot refreshed yet
-
FetchIdleEndpoint: ${escapeHtml(SES_ENDPOINT)}
- - -
+
@@ -391,23 +712,32 @@ function renderHtml() { function renderStyles() { return ` - :root{--panel:rgba(255,255,255,.82);--panel-strong:#fff;--card-shell:linear-gradient(180deg,rgba(255,246,236,.98),rgba(255,252,247,.99));--card-body:#fffdf9;--ink:#1f2933;--muted:#607080;--line:rgba(31,41,51,.12);--card-line:rgba(207,109,60,.24);--accent:#cf6d3c;--accent-soft:rgba(207,109,60,.1);--ok:#1f8f65;--warn:#9d5f00;--bad:#b33a3a;--shadow:0 12px 28px rgba(35,43,53,.08);--card-shadow:0 18px 34px rgba(122,78,34,.12);} + :root{--panel:rgba(255,255,255,.82);--panel-strong:#fff;--card-shell:linear-gradient(180deg,rgba(255,246,236,.98),rgba(255,252,247,.99));--card-body:#fffdf9;--log-shell:linear-gradient(180deg,rgba(239,246,255,.98),rgba(248,251,255,.99));--log-body:#f8fbff;--ink:#1f2933;--muted:#607080;--line:rgba(31,41,51,.12);--card-line:rgba(207,109,60,.24);--log-line:rgba(48,113,169,.22);--accent:#cf6d3c;--accent-soft:rgba(207,109,60,.1);--info:#3071a9;--info-soft:rgba(48,113,169,.1);--ok:#1f8f65;--warn:#9d5f00;--bad:#b33a3a;--shadow:0 12px 28px rgba(35,43,53,.08);--card-shadow:0 18px 34px rgba(122,78,34,.12);--log-shadow:0 16px 32px rgba(48,113,169,.12);} *{box-sizing:border-box} html,body{margin:0;min-height:100%} body{background:radial-gradient(circle at top left,rgba(207,109,60,.18),transparent 28%),radial-gradient(circle at top right,rgba(31,143,101,.12),transparent 24%),linear-gradient(180deg,#f8f5ef,#efe7da);color:var(--ink);font:15px/1.45 "Aptos","Segoe UI Variable Display","Segoe UI",system-ui,sans-serif} button,input,select,textarea{font:inherit} button{cursor:pointer} - .page{max-width:1360px;margin:0 auto;padding:18px} - .hero{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(320px,.95fr);gap:14px;margin-bottom:14px} - .hero-copy,.hero-controls,.stat{background:var(--panel);backdrop-filter:blur(14px);border:1px solid var(--line);box-shadow:var(--shadow)} + .page{display:grid;gap:14px;max-width:1360px;min-height:100vh;margin:0 auto;padding:18px} + .hero{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(320px,.95fr);gap:14px;margin-bottom:0} + .hero-copy,.hero-controls,.toolControls,.stat{background:var(--panel);backdrop-filter:blur(14px);border:1px solid var(--line);box-shadow:var(--shadow)} .card{background:var(--card-shell);border:1px solid var(--card-line);box-shadow:var(--card-shadow)} - .hero-copy,.hero-controls{border-radius:18px;padding:18px} + .hero-copy,.hero-controls,.toolControls{border-radius:18px;padding:18px} .eyebrow{margin:0 0 6px;color:var(--accent);font-size:.76rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase} h1{margin:0;font-size:clamp(1.9rem,4vw,3.1rem);line-height:.98;letter-spacing:-.05em} .lede{margin:10px 0 0;max-width:54ch;color:var(--muted);font-size:.96rem} .hero-controls{display:grid;gap:10px} + .helper{margin:0;color:var(--muted);font-size:.95rem} + .workspaceTabs{display:flex;flex-wrap:wrap;gap:8px;padding:6px;border-radius:999px;background:rgba(31,41,51,.05)} + .workspaceTab,.primary,.ghost,.mini,.tab{border-radius:999px;border:1px solid transparent;transition:transform .12s ease,background-color .12s ease,border-color .12s ease} + .workspaceTab{min-height:34px;padding:0 14px;background:transparent;color:var(--muted);font-weight:700} + .workspaceTab.active{background:#fff;border-color:rgba(31,41,51,.08);color:var(--ink)} + .workspacePanel{display:grid;gap:12px} + .workspacePanel[hidden]{display:none} + .toolControls{display:grid;gap:10px} + .contentPane{height:clamp(360px,50vh,720px);overflow:auto;padding-right:4px} + .contentStack{display:grid;gap:12px;min-width:min-content} .row{display:flex;flex-wrap:wrap;gap:8px;align-items:center} - .primary,.ghost,.mini,.tab{border-radius:999px;border:1px solid transparent;transition:transform .12s ease,background-color .12s ease,border-color .12s ease} .primary,.ghost{min-height:38px;padding:0 14px;font-weight:700} .mini,.tab{min-height:30px;padding:0 10px;font-weight:600} .primary{background:var(--accent);color:#fff7f2} @@ -429,9 +759,10 @@ function renderStyles() { .stat strong{display:block;font-size:clamp(1.6rem,3vw,2rem);line-height:1;letter-spacing:-.05em} .stat strong.small{font-size:1.1rem;line-height:1.3;letter-spacing:-.02em} .stat small{display:block;margin-top:8px;color:var(--muted);font-size:.85rem} - .banner,.empty{margin:0 0 12px;padding:12px 14px;border-radius:14px;border:1px solid var(--line);background:rgba(255,255,255,.82)} + .banner,.empty{margin:0;padding:12px 14px;border-radius:14px;border:1px solid var(--line);background:rgba(255,255,255,.82)} .banner{color:var(--bad);border-color:rgba(179,58,58,.24);background:rgba(179,58,58,.08)} - .list{display:grid;gap:12px} + .list{display:grid;gap:12px;align-content:start} + .logList{display:grid;gap:10px;align-content:start} .card{overflow:hidden;border-radius:16px} .card.new{border-color:rgba(31,143,101,.3);box-shadow:var(--card-shadow),0 0 0 1px rgba(31,143,101,.12)} .summary{list-style:none;display:grid;gap:8px;padding:14px 16px;cursor:pointer;background:linear-gradient(180deg,rgba(255,250,244,.88),rgba(255,246,238,.96))} @@ -459,12 +790,22 @@ function renderStyles() { .attachmentLink{color:#8d5632;text-decoration:none;transition:transform .12s ease,background-color .12s ease,border-color .12s ease} .attachmentLink:hover{transform:translateY(-1px);background:#fff;border-color:rgba(207,109,60,.28)} .panel{overflow:hidden;border-radius:12px;border:1px solid rgba(207,109,60,.14);background:#fff} + .logEvent{overflow:hidden;border-radius:16px;border:1px solid var(--log-line);background:var(--log-shell);box-shadow:var(--log-shadow)} + .logSummary{list-style:none;display:grid;gap:8px;padding:12px 14px;cursor:pointer} + .logSummary::-webkit-details-marker{display:none} + .logSummaryTop{display:flex;flex-wrap:wrap;gap:8px;justify-content:space-between;align-items:center} + .logMeta{display:flex;flex-wrap:wrap;gap:8px;align-items:center} + .logTag{background:var(--info-soft);color:var(--info);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} + .logPreview{margin:0;color:#324150;font-size:.9rem} + .logBody{padding:0 14px 14px;border-top:1px solid rgba(48,113,169,.14);background:var(--log-body)} + .logActions{display:flex;justify-content:flex-end;padding:12px 0 0} + .logBody pre{background:linear-gradient(180deg,rgba(48,113,169,.04),transparent 140px),#fff} iframe{width:100%;min-height:560px;border:none;background:#fff} pre{margin:0;padding:12px;white-space:pre-wrap;word-break:break-word;overflow:auto;font:12.5px/1.45 "Cascadia Code","Consolas",monospace;color:#102030;background:linear-gradient(180deg,rgba(207,109,60,.04),transparent 140px),#fff} .placeholder,.inlineError{padding:12px} .inlineError{color:var(--bad)} @media (max-width:1080px){.hero{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,minmax(0,1fr))}} - @media (max-width:720px){.page{padding:12px}.stats{grid-template-columns:1fr}.primary,.ghost,.chip{width:100%;justify-content:center}.toolbar,.row{align-items:stretch}iframe{min-height:420px}} + @media (max-width:720px){.page{padding:12px}.stats{grid-template-columns:1fr}.workspaceTab,.primary,.ghost,.chip{width:100%;justify-content:center}.toolbar,.row{align-items:stretch}.logSummaryTop{align-items:flex-start}.contentPane{height:clamp(300px,48vh,560px)}iframe{min-height:420px}} `; } @@ -478,6 +819,11 @@ function escapeHtml(value) { } function clientApp(config) { + const appState = { + panel: "emails", + logsReady: false + }; + const state = { messages: [], filtered: [], @@ -499,7 +845,34 @@ function clientApp(config) { listSignature: "" }; + const logsState = { + groups: [], + streams: [], + events: [], + filtered: [], + group: config.defaultLogGroup || "", + stream: "", + search: "", + auto: true, + interval: config.defaultRefreshMs, + windowMs: config.defaultLogWindowMs, + limit: config.defaultLogLimit, + loading: false, + error: "", + updatedAt: 0, + source: "initial", + duration: 0, + newest: 0, + searchedLogStreams: 0, + openIds: new Set(), + hasInteracted: false, + listSignature: "" + }; + const el = { + workspaceTabs: document.getElementById("workspaceTabs"), + emailsPanel: document.getElementById("emailsPanel"), + logsPanel: document.getElementById("logsPanel"), refreshButton: document.getElementById("refreshButton"), autoToggle: document.getElementById("autoToggle"), intervalSelect: document.getElementById("intervalSelect"), @@ -517,16 +890,54 @@ function clientApp(config) { fetchDetail: document.getElementById("fetchDetail"), banner: document.getElementById("banner"), empty: document.getElementById("empty"), - list: document.getElementById("list") + list: document.getElementById("list"), + logsRefreshButton: document.getElementById("logsRefreshButton"), + logsAutoToggle: document.getElementById("logsAutoToggle"), + logsIntervalSelect: document.getElementById("logsIntervalSelect"), + logsGroupSelect: document.getElementById("logsGroupSelect"), + logsStreamSelect: document.getElementById("logsStreamSelect"), + logsWindowSelect: document.getElementById("logsWindowSelect"), + logsLimitSelect: document.getElementById("logsLimitSelect"), + logsSearchInput: document.getElementById("logsSearchInput"), + logsClearSearchButton: document.getElementById("logsClearSearchButton"), + logsStatusChip: document.getElementById("logsStatusChip"), + logsTotalStat: document.getElementById("logsTotalStat"), + logsVisibleStat: document.getElementById("logsVisibleStat"), + logsStreamsStat: document.getElementById("logsStreamsStat"), + logsNewestStat: document.getElementById("logsNewestStat"), + logsUpdatedStat: document.getElementById("logsUpdatedStat"), + logsFetchStat: document.getElementById("logsFetchStat"), + logsFetchDetail: document.getElementById("logsFetchDetail"), + logsBanner: document.getElementById("logsBanner"), + logsEmpty: document.getElementById("logsEmpty"), + logsList: document.getElementById("logsList") }; el.intervalSelect.value = String(config.defaultRefreshMs); + el.logsIntervalSelect.value = String(config.defaultRefreshMs); + el.logsWindowSelect.value = String(config.defaultLogWindowMs); + el.logsLimitSelect.value = String(config.defaultLogLimit); wire(); + renderWorkspace(); renderAll(); + renderLogsAll(); refreshMessages("initial"); - window.setInterval(renderLiveClock, 1000); + window.setInterval(() => { + renderLiveClock(); + renderLogsLiveClock(); + }, 1000); function wire() { + el.workspaceTabs.addEventListener("click", async (event) => { + const button = event.target.closest("button[data-panel]"); + + if (!button) { + return; + } + + await setPanel(button.dataset.panel); + }); + el.refreshButton.addEventListener("click", () => refreshMessages("manual")); el.autoToggle.addEventListener("change", () => { @@ -598,10 +1009,88 @@ function clientApp(config) { } }); + el.logsRefreshButton.addEventListener("click", () => refreshLogs("manual")); + + el.logsAutoToggle.addEventListener("change", () => { + logsState.auto = el.logsAutoToggle.checked; + scheduleLogsRefresh(); + renderLogsStatus(); + }); + + el.logsIntervalSelect.addEventListener("change", () => { + logsState.interval = Number(el.logsIntervalSelect.value) || config.defaultRefreshMs; + scheduleLogsRefresh(); + renderLogsStatus(); + }); + + el.logsWindowSelect.addEventListener("change", () => { + logsState.windowMs = Number(el.logsWindowSelect.value) || config.defaultLogWindowMs; + refreshLogs("window"); + }); + + el.logsLimitSelect.addEventListener("change", () => { + logsState.limit = Number(el.logsLimitSelect.value) || config.defaultLogLimit; + refreshLogs("limit"); + }); + + el.logsSearchInput.addEventListener("input", (event) => { + logsState.search = event.target.value; + applyLogsFilter(); + }); + + el.logsClearSearchButton.addEventListener("click", () => { + logsState.search = ""; + el.logsSearchInput.value = ""; + applyLogsFilter(); + }); + + el.logsGroupSelect.addEventListener("change", async () => { + logsState.group = el.logsGroupSelect.value; + logsState.stream = ""; + await refreshLogStreams(); + await refreshLogs("group"); + }); + + el.logsStreamSelect.addEventListener("change", () => { + logsState.stream = el.logsStreamSelect.value; + refreshLogs("stream"); + }); + + el.logsList.addEventListener("click", async (event) => { + const button = event.target.closest("button[data-log-action]"); + + if (!button) { + return; + } + + const logEvent = getLogEvent(button.dataset.id); + + if (!logEvent) { + return; + } + + if (button.dataset.logAction === "copy") { + await copyText(formatLogMessage(logEvent.message)); + setLogsStatus("Log payload copied to the clipboard.", "ok"); + } + }); + document.addEventListener("visibilitychange", () => { window.clearTimeout(state.timer); + window.clearTimeout(logsState.timer); - if (!document.hidden && state.auto) { + if (document.hidden) { + renderStatus(); + renderLogsStatus(); + return; + } + + if (appState.panel === "logs" && logsState.auto) { + refreshLogs("visibility"); + return; + } + + if (state.auto) { refreshMessages("visibility"); } else { renderStatus(); @@ -615,11 +1104,64 @@ function clientApp(config) { if (!isField && event.key.toLowerCase() === "r") { event.preventDefault(); + + if (appState.panel === "logs") { + refreshLogs("keyboard"); + return; + } + refreshMessages("keyboard"); } }); } + async function setPanel(panel) { + if (!panel || panel === appState.panel) { + if (panel === "logs") { + await ensureLogsReady(); + } + + renderWorkspace(); + return; + } + + appState.panel = panel; + renderWorkspace(); + + if (panel === "logs") { + await ensureLogsReady(); + } + + scheduleRefresh(); + scheduleLogsRefresh(); + renderStatus(); + renderLogsStatus(); + } + + function renderWorkspace() { + el.emailsPanel.hidden = appState.panel !== "emails"; + el.logsPanel.hidden = appState.panel !== "logs"; + + el.workspaceTabs.querySelectorAll("button[data-panel]").forEach((button) => { + const active = button.dataset.panel === appState.panel; + button.classList.toggle("active", active); + button.setAttribute("aria-pressed", active ? "true" : "false"); + }); + } + + async function ensureLogsReady() { + if (appState.logsReady) { + return; + } + + await refreshLogGroups(); + appState.logsReady = !logsState.error; + + if (logsState.group) { + await refreshLogs("initial"); + } + } + async function refreshMessages(source) { if (state.loading) { return; @@ -672,6 +1214,144 @@ function clientApp(config) { } } + async function refreshLogGroups() { + try { + const response = await fetch("/api/logs/groups", { cache: "no-store" }); + + if (!response.ok) { + const payload = await safeJson(response); + throw new Error(payload?.details || payload?.error || `Request failed with ${response.status}`); + } + + const payload = await response.json(); + logsState.groups = Array.isArray(payload.groups) ? payload.groups : []; + + const availableGroups = logsState.groups.map((group) => group.name).filter(Boolean); + + if (!availableGroups.includes(logsState.group)) { + logsState.group = availableGroups.includes(config.defaultLogGroup) + ? config.defaultLogGroup + : availableGroups[0] || ""; + } + + logsState.error = ""; + await refreshLogStreams(); + } catch (error) { + logsState.error = error.message || "Unknown log group refresh error"; + } finally { + renderLogsAll(); + } + } + + async function refreshLogStreams() { + if (!logsState.group) { + logsState.streams = []; + logsState.stream = ""; + renderLogsAll(); + return; + } + + try { + const response = await fetch(`/api/logs/streams?group=${encodeURIComponent(logsState.group)}`, { + cache: "no-store" + }); + + if (!response.ok) { + const payload = await safeJson(response); + throw new Error(payload?.details || payload?.error || `Request failed with ${response.status}`); + } + + const payload = await response.json(); + logsState.streams = Array.isArray(payload.streams) ? payload.streams : []; + + if (!logsState.streams.some((stream) => stream.name === logsState.stream)) { + logsState.stream = ""; + } + + logsState.error = ""; + } catch (error) { + logsState.streams = []; + logsState.stream = ""; + logsState.error = error.message || "Unknown log stream refresh error"; + } finally { + renderLogsAll(); + } + } + + async function refreshLogs(source) { + if (logsState.loading) { + return; + } + + if (!appState.logsReady) { + await ensureLogsReady(); + return; + } + + if (!logsState.group) { + logsState.error = logsState.groups.length ? "Choose a log group to load events." : "No log groups found."; + renderLogsAll(); + return; + } + + let shouldRenderList = false; + + logsState.loading = true; + logsState.source = source; + logsState.error = ""; + renderLogsStatus(); + renderLogsFetch(); + + try { + const params = new URLSearchParams({ + group: logsState.group, + windowMs: String(logsState.windowMs), + limit: String(logsState.limit) + }); + + if (logsState.stream) { + params.set("stream", logsState.stream); + } + + const response = await fetch(`/api/logs/events?${params.toString()}`, { cache: "no-store" }); + + if (!response.ok) { + const payload = await safeJson(response); + throw new Error(payload?.details || payload?.error || `Request failed with ${response.status}`); + } + + const payload = await response.json(); + const events = Array.isArray(payload.events) ? payload.events : []; + const nextSignature = computeLogListSignature(events); + shouldRenderList = nextSignature !== logsState.listSignature; + logsState.events = events; + logsState.duration = payload.fetchDurationMs || 0; + logsState.newest = payload.latestTimestamp || 0; + logsState.updatedAt = Date.now(); + logsState.searchedLogStreams = payload.searchedLogStreams || (logsState.stream ? 1 : logsState.streams.length); + logsState.listSignature = nextSignature; + + pruneLogsState(); + applyLogsFilter(shouldRenderList); + setLogsStatus(`Updated ${logsState.events.length} log event${logsState.events.length === 1 ? "" : "s"}.`, "ok"); + } catch (error) { + logsState.error = error.message || "Unknown log refresh error"; + setLogsStatus(`Refresh failed: ${logsState.error}`, "bad"); + } finally { + logsState.loading = false; + scheduleLogsRefresh(); + renderLogsAll({ renderList: shouldRenderList }); + } + } + + function applyLogsFilter(shouldRenderList = true) { + const search = logsState.search.trim().toLowerCase(); + logsState.filtered = !search + ? [...logsState.events] + : logsState.events.filter((event) => logHaystack(event).includes(search)); + renderLogsAll({ renderList: shouldRenderList }); + } + function pruneState() { const ids = new Set(state.messages.map((message) => message.id)); state.openIds = new Set([...state.openIds].filter((id) => ids.has(id))); @@ -690,6 +1370,11 @@ function clientApp(config) { }); } + function pruneLogsState() { + const ids = new Set(logsState.events.map((event) => event.id)); + logsState.openIds = new Set([...logsState.openIds].filter((id) => ids.has(id))); + } + function applyFilter(shouldRenderList = true) { const search = state.search.trim().toLowerCase(); state.filtered = !search @@ -714,6 +1399,16 @@ function clientApp(config) { .join("|"); } + function computeLogListSignature(events) { + return events + .map((event) => + [event.id, event.timestamp || 0, event.ingestionTime || 0, event.logStreamName || "", event.message || ""].join( + "::" + ) + ) + .join("|"); + } + function haystack(message) { return [ message.subject, @@ -730,16 +1425,30 @@ function clientApp(config) { .toLowerCase(); } + function logHaystack(event) { + return [event.logStreamName, event.message, event.preview].filter(Boolean).join(" ").toLowerCase(); + } + function scheduleRefresh() { window.clearTimeout(state.timer); - if (!state.auto || document.hidden || state.loading) { + if (appState.panel !== "emails" || !state.auto || document.hidden || state.loading) { return; } state.timer = window.setTimeout(() => refreshMessages("auto"), state.interval); } + function scheduleLogsRefresh() { + window.clearTimeout(logsState.timer); + + if (appState.panel !== "logs" || !logsState.auto || document.hidden || logsState.loading) { + return; + } + + logsState.timer = window.setTimeout(() => refreshLogs("auto"), logsState.interval); + } + function renderAll(options = {}) { const { renderList: shouldRenderList = true } = options; renderStats(); @@ -751,6 +1460,18 @@ function clientApp(config) { renderLiveClock(); } + function renderLogsAll(options = {}) { + const { renderList: shouldRenderList = true } = options; + renderLogsFilters(); + renderLogsStats(); + renderLogsFetch(); + renderLogsStatus(); + if (shouldRenderList) { + renderLogsList(); + } + renderLogsLiveClock(); + } + function renderStats() { el.totalStat.textContent = String(state.messages.length); el.visibleStat.textContent = `${state.filtered.length} visible${state.search ? " after search" : ""}`; @@ -758,6 +1479,34 @@ function clientApp(config) { el.newestStat.textContent = state.newest ? formatDateTime(state.newest) : "No messages"; } + function renderLogsFilters() { + const groups = logsState.groups.length + ? logsState.groups.map((group) => ``) + : ['']; + const streams = [ + '', + ...logsState.streams.map( + (stream) => `` + ) + ]; + + el.logsGroupSelect.innerHTML = groups.join(""); + el.logsStreamSelect.innerHTML = streams.join(""); + + if (logsState.group) { + el.logsGroupSelect.value = logsState.group; + } + + el.logsStreamSelect.value = logsState.stream; + } + + function renderLogsStats() { + el.logsTotalStat.textContent = String(logsState.events.length); + el.logsVisibleStat.textContent = `${logsState.filtered.length} visible${logsState.search ? " after search" : ""}`; + el.logsStreamsStat.textContent = String(logsState.streams.length || logsState.searchedLogStreams || 0); + el.logsNewestStat.textContent = logsState.newest ? formatDateTime(logsState.newest) : "No events"; + } + function renderFetch() { if (state.loading) { el.fetchStat.textContent = "Refreshing..."; @@ -781,6 +1530,29 @@ function clientApp(config) { el.fetchDetail.textContent = `${state.parseErrors} parse error${state.parseErrors === 1 ? "" : "s"}. Endpoint: ${config.endpoint}`; } + function renderLogsFetch() { + if (logsState.loading) { + el.logsFetchStat.textContent = "Refreshing..."; + el.logsFetchDetail.textContent = `Endpoint: ${config.cloudWatchEndpoint} (${config.cloudWatchRegion})`; + return; + } + + if (logsState.error) { + el.logsFetchStat.textContent = "Needs attention"; + el.logsFetchDetail.textContent = logsState.error; + return; + } + + if (!logsState.updatedAt) { + el.logsFetchStat.textContent = "Idle"; + el.logsFetchDetail.textContent = `Endpoint: ${config.cloudWatchEndpoint} (${config.cloudWatchRegion})`; + return; + } + + el.logsFetchStat.textContent = `${logsState.duration}ms`; + el.logsFetchDetail.textContent = `${logsState.group || "No group selected"}. Endpoint: ${config.cloudWatchEndpoint}`; + } + function renderStatus() { el.statusChip.className = "status"; @@ -817,6 +1589,42 @@ function clientApp(config) { el.statusChip.textContent = `Live refresh on, next check in ${seconds}s`; } + function renderLogsStatus() { + el.logsStatusChip.className = "status"; + + if (logsState.loading) { + el.logsStatusChip.classList.add("warn"); + el.logsStatusChip.textContent = "Refreshing logs..."; + return; + } + + if (logsState.error) { + el.logsStatusChip.classList.add("bad"); + el.logsStatusChip.textContent = `Refresh failed: ${logsState.error}`; + return; + } + + if (!logsState.auto) { + el.logsStatusChip.textContent = "Live refresh paused"; + return; + } + + if (document.hidden) { + el.logsStatusChip.classList.add("warn"); + el.logsStatusChip.textContent = "Tab hidden, live refresh paused"; + return; + } + + if (!logsState.updatedAt) { + el.logsStatusChip.textContent = "Waiting for first refresh..."; + return; + } + + const seconds = Math.max(0, Math.ceil((logsState.updatedAt + logsState.interval - Date.now()) / 1000)); + el.logsStatusChip.classList.add("ok"); + el.logsStatusChip.textContent = `Live refresh on, next check in ${seconds}s`; + } + function renderLiveClock() { if (!state.updatedAt) { el.updatedStat.textContent = "Not refreshed yet"; @@ -827,6 +1635,16 @@ function clientApp(config) { renderStatus(); } + function renderLogsLiveClock() { + if (!logsState.updatedAt) { + el.logsUpdatedStat.textContent = "Not refreshed yet"; + return; + } + + el.logsUpdatedStat.textContent = `Updated ${formatRelative(logsState.updatedAt)} via ${logsState.source}`; + renderLogsStatus(); + } + function renderList() { el.banner.hidden = !state.error; el.banner.textContent = state.error ? `Refresh failed: ${state.error}` : ""; @@ -846,6 +1664,76 @@ function clientApp(config) { el.list.querySelectorAll(".card[open]").forEach((details) => hydrate(details, getMessage(details.dataset.id))); } + function renderLogsList() { + el.logsBanner.hidden = !logsState.error; + el.logsBanner.textContent = logsState.error ? `Refresh failed: ${logsState.error}` : ""; + + if (!logsState.group && !logsState.groups.length) { + el.logsList.innerHTML = ""; + el.logsEmpty.hidden = false; + el.logsEmpty.textContent = "No CloudWatch log groups found in LocalStack yet."; + return; + } + + if (!logsState.filtered.length) { + el.logsList.innerHTML = ""; + el.logsEmpty.hidden = false; + el.logsEmpty.textContent = logsState.events.length + ? "No log events match the current search." + : "No log events found for the selected group, stream, and time window."; + return; + } + + el.logsEmpty.hidden = true; + el.logsList.innerHTML = logsState.filtered.map((event, index) => renderLogEvent(event, index)).join(""); + bindLogToggles(); + } + + function renderLogEvent(event, index) { + const open = logsState.openIds.has(event.id) || (!logsState.hasInteracted && index === 0) ? "open" : ""; + + return ` +
+ +
+
+ ${escapeHtml(formatDateTime(event.timestamp))} + ${escapeHtml(event.logStreamName || "Unknown stream")} +
+ ${escapeHtml(formatRelative(event.timestamp || Date.now()))} +
+

${escapeHtml(event.preview || "No preview available.")}

+
+
+
+ +
+
${escapeHtml(formatLogMessage(event.message))}
+
+
+ `; + } + + function bindLogToggles() { + el.logsList.querySelectorAll(".logEvent").forEach((details) => { + details.addEventListener("toggle", () => { + const id = details.dataset.id; + + if (!id) { + return; + } + + logsState.hasInteracted = true; + + if (details.open) { + logsState.openIds.add(id); + } else { + logsState.openIds.delete(id); + } + }); + }); + } + function bindCardToggles() { el.list.querySelectorAll(".card").forEach((details) => { details.addEventListener("toggle", () => { @@ -999,6 +1887,10 @@ function clientApp(config) { return state.messages.find((message) => message.id === id); } + function getLogEvent(id) { + return logsState.events.find((event) => event.id === id); + } + async function loadRaw(id) { if (state.raw[id]?.status === "loaded") { return state.raw[id].value; @@ -1056,6 +1948,16 @@ function clientApp(config) { el.statusChip.textContent = message; } + function setLogsStatus(message, tone) { + el.logsStatusChip.className = "status"; + + if (tone) { + el.logsStatusChip.classList.add(tone); + } + + el.logsStatusChip.textContent = message; + } + function formatDateTime(value) { if (!value) { return "Unknown time"; @@ -1106,6 +2008,20 @@ function clientApp(config) { return `${index === 0 ? size : size.toFixed(1)} ${units[index]}`; } + function formatLogMessage(message) { + const value = String(message || "").trim(); + + if (!value) { + return "No log payload."; + } + + try { + return JSON.stringify(JSON.parse(value), null, 2); + } catch { + return value; + } + } + function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") @@ -1125,6 +2041,7 @@ function clientApp(config) { } app.listen(PORT, () => { - console.log(`Local email viewer is running on http://localhost:${PORT}`); + console.log(`LocalStack inspector is running on http://localhost:${PORT}`); console.log(`Watching LocalStack SES endpoint at ${SES_ENDPOINT}`); + console.log(`Watching LocalStack CloudWatch Logs endpoint at ${CLOUDWATCH_ENDPOINT} (${CLOUDWATCH_REGION})`); }); diff --git a/_reference/localEmailViewer/package-lock.json b/_reference/localEmailViewer/package-lock.json index 2c7ecad93..2dec2be60 100644 --- a/_reference/localEmailViewer/package-lock.json +++ b/_reference/localEmailViewer/package-lock.json @@ -9,11 +9,634 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-cloudwatch-logs": "^3.1012.0", "express": "^5.1.0", "mailparser": "^3.7.4", "node-fetch": "^3.3.2" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.1012.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.1012.0.tgz", + "integrity": "sha512-nkMczmeCG9a1YD48zDKZLX7wLGb5ycXMJXVFnXF8xVxQ2v2uo2xFz1ZZganWHSQxR6ZfBPG7R9OTkDrVx79ZPA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/credential-provider-node": "^3.972.22", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.22", + "@aws-sdk/region-config-resolver": "^3.972.8", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.8", + "@smithy/config-resolver": "^4.4.11", + "@smithy/core": "^3.23.12", + "@smithy/eventstream-serde-browser": "^4.2.12", + "@smithy/eventstream-serde-config-resolver": "^4.3.12", + "@smithy/eventstream-serde-node": "^4.2.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.26", + "@smithy/middleware-retry": "^4.4.43", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.42", + "@smithy/util-defaults-mode-node": "^4.2.45", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.21.tgz", + "integrity": "sha512-OTUcDX9Yfz/FLKbHjiMaP9D4Hs44lYJzN7zBcrK2nDmBt0Wr8D6nYt12QoBkZsW0nVMFsTIGaZCrsU9zCcIMXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.12", + "@smithy/core": "^3.23.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.6", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.19.tgz", + "integrity": "sha512-33NpkQtmnsjLr9QdZvL3w8bjy+WoBJ+jY8JwuzxIq38rDNi1kwpBWW7Yjh+8bMlksd+ZAWW0fH4S/6OeoAdU5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.21.tgz", + "integrity": "sha512-xFke7yjbON4unNOG0TApQwz+o1LH5VhVLgWlUuiLRWNDyBfeHIFje2ck8qHybvJ8Fkm5m3SsN+pvHtVo6PGWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.6", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.21.tgz", + "integrity": "sha512-fmJN7KhB7CoG65w9fC2LVOd2wZbR2d1yJIpZNe2J5CeDPu7nUHSmavuJAeGEoE3OL5UIBVPNhmK/fV/NQrs3Hw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/credential-provider-env": "^3.972.19", + "@aws-sdk/credential-provider-http": "^3.972.21", + "@aws-sdk/credential-provider-login": "^3.972.21", + "@aws-sdk/credential-provider-process": "^3.972.19", + "@aws-sdk/credential-provider-sso": "^3.972.21", + "@aws-sdk/credential-provider-web-identity": "^3.972.21", + "@aws-sdk/nested-clients": "^3.996.11", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.21.tgz", + "integrity": "sha512-ENU+YCiuQocQjfIf9bPxZ+ZY0wIBkl3SMH22optBQwy8UFpSfonHynXzGT27xQxer4cYTNOpwDqbfo57BusbpQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/nested-clients": "^3.996.11", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.22.tgz", + "integrity": "sha512-VE6i8nkmrRyhKut7nnfCWRbdDf+CfyRr8ixSwdaPDguYlgvkAO2pHu9oK11XzbSuatB0io1ozI/vpYhelXn8Pg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.19", + "@aws-sdk/credential-provider-http": "^3.972.21", + "@aws-sdk/credential-provider-ini": "^3.972.21", + "@aws-sdk/credential-provider-process": "^3.972.19", + "@aws-sdk/credential-provider-sso": "^3.972.21", + "@aws-sdk/credential-provider-web-identity": "^3.972.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.19.tgz", + "integrity": "sha512-hjj5bFo4kf5/WzAMjDEFByVOMbq5gZiagIpJexf7Kp9nIDaGzhCphMsx03NCA8s9zUJzHlD1lXazd7MS+e03Lg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.21.tgz", + "integrity": "sha512-9jWRCuMZpZKlqCZ46bvievqdfswsyB2yPAr9rOiN+FxaGgf8jrR5iYDqJgscvk1jrbAxiK4cIjHv3XjIAWAhzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/nested-clients": "^3.996.11", + "@aws-sdk/token-providers": "3.1012.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.21.tgz", + "integrity": "sha512-ShWQO/cQVZ+j3zUDK7Kj+m7grPzQCVA2iaZdJ+hJTGvVH5lR32Ip/rgZZ+zBdH6D6wczP9Upa4NMXoqJdGpK1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/nested-clients": "^3.996.11", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz", + "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.22.tgz", + "integrity": "sha512-pZPNGWZVQvgUIO/P9PXZNz7ciq9mLYb/wQEurg3phKTa3DiBIunIRcgA0eBNwmog6S3oy0KR1bv4EJ4ld9A5sQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.11.tgz", + "integrity": "sha512-i7SwoSR4JB/79JoGDUACnFUQOZwXGLWNX35lIb1Pq72nUGlVV+RFZp+BLa8S+mog2pbXU9+6Kc5YwGiMi5bKhQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.22", + "@aws-sdk/region-config-resolver": "^3.972.8", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.8", + "@smithy/config-resolver": "^4.4.11", + "@smithy/core": "^3.23.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.26", + "@smithy/middleware-retry": "^4.4.43", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.42", + "@smithy/util-defaults-mode-node": "^4.2.45", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.8.tgz", + "integrity": "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1012.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1012.0.tgz", + "integrity": "sha512-vzKwy020zjuiF4WTJzejx5nYcXJnRhHpb6i3lyZHIwfFwXG1yX4bzBVNMWYWF+bz1i2Pp2VhJbPyzpqj4VuJXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.21", + "@aws-sdk/nested-clients": "^3.996.11", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.8.tgz", + "integrity": "sha512-Kvb96TafGPLYo4Z2GRCzQTne77epXgiZEo0DDXwavzkWmgDV/1XD1tMA766gzRcHHFUraWsE+4T8DKtPTZUxgQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.22", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.13.tgz", + "integrity": "sha512-I/+BMxM4WE/6xL0tyV7tAUDOAXmyw/va1oGr/eSly43HmLUcD1G+v96vEKAA8VoLcZ03ZQo/PWzjmN9zQErqPQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", @@ -27,6 +650,657 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", + "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.12", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz", + "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", + "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", + "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", + "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", + "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", + "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.27.tgz", + "integrity": "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.44", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.44.tgz", + "integrity": "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.15.tgz", + "integrity": "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.0.tgz", + "integrity": "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.7.tgz", + "integrity": "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.43", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.43.tgz", + "integrity": "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.47.tgz", + "integrity": "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.13", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.20.tgz", + "integrity": "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -60,6 +1334,12 @@ "node": ">=18" } }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -373,6 +1653,41 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", + "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.1.3", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -856,6 +2171,21 @@ "node": ">= 0.8" } }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -1113,6 +2443,18 @@ "node": ">= 0.8" } }, + "node_modules/strnum": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/tlds": { "version": "1.259.0", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz", @@ -1131,6 +2473,12 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", diff --git a/_reference/localEmailViewer/package.json b/_reference/localEmailViewer/package.json index a6b522e0e..377708878 100644 --- a/_reference/localEmailViewer/package.json +++ b/_reference/localEmailViewer/package.json @@ -10,8 +10,9 @@ "keywords": [], "author": "", "license": "ISC", - "description": "LocalStack SES email viewer for inspecting local outbound mail", + "description": "LocalStack inspector for SES emails and CloudWatch logs", "dependencies": { + "@aws-sdk/client-cloudwatch-logs": "^3.1012.0", "express": "^5.1.0", "mailparser": "^3.7.4", "node-fetch": "^3.3.2"