From 73fa7dd2b27a1ed71bba802bb0c1673ea5340473 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 23 Mar 2026 19:52:32 -0600 Subject: [PATCH 1/7] Spike: Clean up console noise and enforce no-console in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changed **Removed leftover debug calls** `console.debug` calls used to trace auth flow steps, token lifecycle events, and auto-refresh state were removed. These were never intended to reach production and added no value outside of local debugging sessions. **Introduced a thin logger abstraction** A minimal `logger` module was added as the single place in the codebase allowed to call `console` directly. Legitimate error reporting in `catch` blocks and error handlers — places where surfacing failures is intentional — was migrated to `logger.error` / `logger.warn`. This keeps those signals visible in both dev and production builds while satisfying the linting rule everywhere else. **Added a CI-aware `no-console` lint rule** The ESLint config now enforces `no-console` as an **error** when `CI=true` (set automatically by GitHub Actions) and as a **warning** locally. This means developers can still freely use `console.x` during development without any friction, but leftover calls will fail the CI build before they can merge. **Added a lint step to the CI pipeline** A `Lint Frontend` step now runs `npm run lint` before the build, so the `no-console` rule is enforced on every PR and push to `master`. --- .github/workflows/ci.yml | 3 +++ src/Frontend/eslint.config.mjs | 4 ++++ src/Frontend/src/AuthApp.vue | 14 +++---------- .../messages/FlowDiagram/FlowDiagram.vue | 8 +++----- .../messages/SagaDiagram/SagaUpdateNode.vue | 3 ++- .../monitoring/EndpointInstances.vue | 3 ++- src/Frontend/src/composables/autoRefresh.ts | 10 +--------- src/Frontend/src/composables/useAuth.ts | 20 +++++++------------ src/Frontend/src/logger.ts | 13 ++++++++++++ src/Frontend/src/mount.ts | 3 ++- src/Frontend/src/stores/AuthStore.ts | 3 ++- src/Frontend/src/stores/ConfigurationStore.ts | 3 ++- .../src/stores/ConnectionsAndStatsStore.ts | 3 ++- .../src/stores/EnvironmentAndVersionsStore.ts | 3 ++- src/Frontend/src/stores/HealthChecksStore.ts | 9 +++++---- src/Frontend/src/stores/LicenseStore.ts | 3 ++- src/Frontend/src/stores/MessageStore.ts | 3 ++- src/Frontend/src/stores/MonitoringStore.ts | 3 ++- .../src/stores/RecoverabilityStore.ts | 3 ++- src/Frontend/src/stores/SagaDiagramStore.ts | 3 ++- 20 files changed, 63 insertions(+), 54 deletions(-) create mode 100644 src/Frontend/src/logger.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9c75f47db..eefe7c6a2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,9 @@ jobs: uses: actions/setup-node@v6.3.0 with: node-version: 24.x + - name: Lint Frontend + run: npm run lint + working-directory: src/Frontend - name: Build Frontend run: .\build.ps1 working-directory: src/ServicePulse.Host diff --git a/src/Frontend/eslint.config.mjs b/src/Frontend/eslint.config.mjs index f92446df18..303ed8d523 100644 --- a/src/Frontend/eslint.config.mjs +++ b/src/Frontend/eslint.config.mjs @@ -11,6 +11,9 @@ const localPlugin = { }, }; +// Configure console rule: error in CI, warn in development +const consoleRuleSeverity = process.env.CI === "true" ? "error" : "warn"; + export default tseslint.config( { ignores: ["node_modules/**", "dist/**", "public/js/app.constants.js", "public/mockServiceWorker.js"], @@ -33,6 +36,7 @@ export default tseslint.config( eqeqeq: ["error", "smart"], "no-throw-literal": "warn", "local/no-raw-fetch": "error", + "no-console": consoleRuleSeverity, }, } ); diff --git a/src/Frontend/src/AuthApp.vue b/src/Frontend/src/AuthApp.vue index e28c8544fd..558e74faa3 100644 --- a/src/Frontend/src/AuthApp.vue +++ b/src/Frontend/src/AuthApp.vue @@ -6,6 +6,7 @@ import { useAuthStore } from "@/stores/AuthStore"; import routeLinks from "@/router/routeLinks"; import LoadingSpinner from "@/components/LoadingSpinner.vue"; import App from "./App.vue"; +import logger from "@/logger"; const { authenticate } = useAuth(); const authStore = useAuthStore(); @@ -18,21 +19,18 @@ onMounted(async () => { // If authentication is not enabled, skip authentication if (!authStore.authEnabled) { - console.debug("Authentication is disabled"); authStore.setAuthenticating(false); return; } // Check if auth config is available if (!authStore.authConfig) { - console.debug("Authentication is enabled but configuration not available"); authStore.setAuthenticating(false); return; } // If the user is on an anonymous route (like logged-out), don't trigger authentication if (window.location.hash === `#${routeLinks.loggedOut}`) { - console.debug("User is on logged-out page, skipping authentication"); authStore.setAuthenticating(false); return; } @@ -41,15 +39,9 @@ onMounted(async () => { // 1. Check if we're returning from the identity provider (handle callback) // 2. Check for an existing valid session // 3. If no valid session, redirect to the identity provider - const authenticated = await authenticate(authStore.authConfig); - - if (authenticated) { - console.debug("User authenticated successfully"); - } else { - console.debug("Redirecting to identity provider for authentication..."); - } + await authenticate(authStore.authConfig); } catch (error) { - console.error("Failed to authenticate on app load:", error); + logger.error("Failed to authenticate on app load:", error); authStore.setAuthenticating(false); } }); diff --git a/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue b/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue index 4ed0c78e1b..df32e60674 100644 --- a/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue +++ b/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue @@ -16,6 +16,7 @@ import SagaName from "@/components/SagaName.vue"; import { useLayout } from "@/components/messages/FlowDiagram/useLayout"; import { formatTypeName } from "@/composables/formatUtils"; import TextEllipses from "@/components/TextEllipses.vue"; +import logger from "@/logger"; enum MessageType { Event = "Event message", @@ -144,14 +145,11 @@ function constructEdges(nodes: Node[]): DefaultEdge[] { return m.receiving_endpoint !== undefined && m.sending_endpoint !== undefined && m.message_id === relatedTo && m.message_intent !== MessageIntent.Publish; }); - if (parentMessages.length === 0) { - console.debug(`Fall back to match only on RelatedToMessageId for message with Id '${message.message_id}' matched but link could be invalid.`); - } } switch (parentMessages.length) { case 0: - console.warn( + logger.warn( `No parent could be resolved for the message with Id '${message.message_id}' which has RelatedToMessageId set. This can happen if the parent has been purged due to retention expiration, an ServiceControl node to be unavailable, or because the parent message not been stored (yet).` ); break; @@ -159,7 +157,7 @@ function constructEdges(nodes: Node[]): DefaultEdge[] { // Log nothing, this is what it should be break; default: - console.warn(`Multiple parents matched for message id '${message.message_id}' possibly due to more-than-once processing, linking to all as it is unknown which processing attempt generated the message.`); + logger.warn(`Multiple parents matched for message id '${message.message_id}' possibly due to more-than-once processing, linking to all as it is unknown which processing attempt generated the message.`); break; } diff --git a/src/Frontend/src/components/messages/SagaDiagram/SagaUpdateNode.vue b/src/Frontend/src/components/messages/SagaDiagram/SagaUpdateNode.vue index 3b5a038707..1b099de65f 100644 --- a/src/Frontend/src/components/messages/SagaDiagram/SagaUpdateNode.vue +++ b/src/Frontend/src/components/messages/SagaDiagram/SagaUpdateNode.vue @@ -9,6 +9,7 @@ import { useSagaDiagramStore } from "@/stores/SagaDiagramStore"; import { ref, watch, computed } from "vue"; import { EditorView } from "@codemirror/view"; import { parse, stringify } from "lossless-json"; +import logger from "@/logger"; // Import the images directly import CommandIcon from "@/assets/command.svg"; @@ -82,7 +83,7 @@ const processState = (state: string | undefined): object => { stateObj = parsedState as Record; } catch (e) { - console.error("Error parsing state:", e); + logger.error("Error parsing state:", e); hasParsingError.value = true; return {}; } diff --git a/src/Frontend/src/components/monitoring/EndpointInstances.vue b/src/Frontend/src/components/monitoring/EndpointInstances.vue index 8dc74d8be0..f78d09f91c 100644 --- a/src/Frontend/src/components/monitoring/EndpointInstances.vue +++ b/src/Frontend/src/components/monitoring/EndpointInstances.vue @@ -13,6 +13,7 @@ import { CriticalTime, InstanceName, ProcessingTime, ScheduledRetries, Throughpu import FAIcon from "@/components/FAIcon.vue"; import { faEnvelope, faTrash } from "@fortawesome/free-solid-svg-icons"; import monitoringClient from "./monitoringClient"; +import logger from "@/logger"; const isRemovingEndpointEnabled = ref(false); const router = useRouter(); @@ -27,7 +28,7 @@ async function removeEndpoint(endpointName: string, instance: ExtendedEndpointIn router.push(routeLinks.monitoring.root); } } catch (err) { - console.log(err); + logger.error(err); return false; } } diff --git a/src/Frontend/src/composables/autoRefresh.ts b/src/Frontend/src/composables/autoRefresh.ts index 64b233ad29..493f0c6332 100644 --- a/src/Frontend/src/composables/autoRefresh.ts +++ b/src/Frontend/src/composables/autoRefresh.ts @@ -1,7 +1,7 @@ import { watch, ref, shallowReadonly, type WatchStopHandle } from "vue"; import { useCounter, useDocumentVisibility, useTimeoutPoll } from "@vueuse/core"; -export default function useFetchWithAutoRefresh(name: string, fetchFn: () => Promise, intervalMs: number) { +export default function useFetchWithAutoRefresh(_name: string, fetchFn: () => Promise, intervalMs: number) { let watchStop: WatchStopHandle | null = null; const { count, inc, dec, reset } = useCounter(0); const interval = ref(intervalMs); @@ -28,21 +28,17 @@ export default function useFetchWithAutoRefresh(name: string, fetchFn: () => Pro const start = async () => { inc(); if (count.value === 1) { - console.debug(`[AutoRefresh] Starting auto-refresh for ${name} every ${interval.value}ms`); resume(); watchStop = watch(visibility, (current, previous) => { if (current === "visible" && previous === "hidden") { - console.debug(`[AutoRefresh] Resuming auto-refresh for ${name} as document became visible`); resume(); } if (current === "hidden" && previous === "visible") { - console.debug(`[AutoRefresh] Pausing auto-refresh for ${name} as document became hidden`); pause(); } }); } else { - console.debug(`[AutoRefresh] Incremented refCount for ${name} to ${count.value}`); // Because another component has started using the auto-refresh, do an immediate refresh to ensure it has up-to-date data await fetchWrapper(); } @@ -51,13 +47,10 @@ export default function useFetchWithAutoRefresh(name: string, fetchFn: () => Pro const stop = () => { dec(); if (count.value <= 0) { - console.debug(`[AutoRefresh] Stopping auto-refresh for ${name}`); pause(); watchStop?.(); watchStop = null; reset(); - } else { - console.debug(`[AutoRefresh] Decremented refCount for ${name} to ${count.value}`); } }; @@ -65,7 +58,6 @@ export default function useFetchWithAutoRefresh(name: string, fetchFn: () => Pro if (interval.value === newIntervalMs) return; interval.value = newIntervalMs; - console.debug(`[AutoRefresh] updated polling ${name} to ${newIntervalMs}ms`); if (isActive.value) { // We need to do this hack, because useTimeoutPoll doesn't react to interval changes while active diff --git a/src/Frontend/src/composables/useAuth.ts b/src/Frontend/src/composables/useAuth.ts index 137d70d73b..4318a5cf0b 100644 --- a/src/Frontend/src/composables/useAuth.ts +++ b/src/Frontend/src/composables/useAuth.ts @@ -1,6 +1,7 @@ import { useAuthStore } from "@/stores/AuthStore"; import type { AuthConfig } from "@/types/auth"; import { UserManager, type User } from "oidc-client-ts"; +import logger from "@/logger"; let userManager: UserManager | null = null; @@ -17,31 +18,27 @@ export function useAuth() { // Set up event handlers userManager.events.addUserLoaded((user: User) => { - console.debug("User loaded:", user.profile); authStore.setToken(user.access_token); }); userManager.events.addUserUnloaded(() => { - console.debug("User unloaded"); authStore.clearToken(); }); userManager.events.addAccessTokenExpiring(async () => { - console.debug("Access token expiring, attempting silent renewal..."); try { await userManager?.signinSilent(); } catch (error) { - console.error("Silent token renewal failed:", error); + logger.error("Silent token renewal failed:", error); } }); userManager.events.addAccessTokenExpired(() => { - console.debug("Access token expired"); authStore.clearToken(); }); userManager.events.addSilentRenewError((error) => { - console.error("Silent renew error:", error); + logger.error("Silent renew error:", error); }); } @@ -75,11 +72,9 @@ export function useAuth() { if (hasCode && hasState) { // This is an OAuth callback with authorization code - console.debug("Processing OAuth callback..."); authStore.setAuthenticating(true); try { const user = await manager.signinCallback(); - console.debug("Signin callback successful"); if (user) { authStore.setToken(user.access_token); // Clean up URL by removing OAuth parameters @@ -87,7 +82,7 @@ export function useAuth() { return true; } } catch (error) { - console.error("Signin callback error details:", { + logger.error("Signin callback error details:", { error, errorMessage: error instanceof Error ? error.message : "Unknown error", errorStack: error instanceof Error ? error.stack : undefined, @@ -101,7 +96,7 @@ export function useAuth() { } else if (hasError) { // OAuth error in callback const errorDescription = params.get("error_description") || params.get("error"); - console.error("OAuth error:", errorDescription); + logger.error("OAuth error:", errorDescription); authStore.setAuthError(errorDescription || "Authentication failed"); return false; } @@ -109,7 +104,6 @@ export function useAuth() { // Check for existing valid user session const user = await manager.getUser(); if (user && !user.expired) { - console.debug("Existing user session found", user.profile); authStore.setToken(user.access_token); return true; } @@ -122,7 +116,7 @@ export function useAuth() { authStore.setAuthenticating(false); const errorMessage = error instanceof Error ? error.message : "Unknown authentication error"; authStore.setAuthError(errorMessage); - console.error("Authentication error:", error); + logger.error("Authentication error:", error); throw error; } } @@ -146,7 +140,7 @@ export function useAuth() { authStore.clearToken(); } } catch (error) { - console.error("Logout error:", error); + logger.error("Logout error:", error); authStore.clearToken(); } } diff --git a/src/Frontend/src/logger.ts b/src/Frontend/src/logger.ts new file mode 100644 index 0000000000..aa182b6a4c --- /dev/null +++ b/src/Frontend/src/logger.ts @@ -0,0 +1,13 @@ +// Centralised logger — the only place in the codebase that touches `console` directly. +// Using this wrapper keeps ESLint's no-console rule satisfied everywhere else and gives +// a single point of control if log behaviour ever needs to change (e.g. send errors to +// a remote service, suppress output in specific environments, etc.). + +/* eslint-disable no-console */ +const logger = { + error: (...args: unknown[]) => console.error(...args), + warn: (...args: unknown[]) => console.warn(...args), +}; +/* eslint-enable no-console */ + +export default logger; diff --git a/src/Frontend/src/mount.ts b/src/Frontend/src/mount.ts index af44602868..dbc0969f98 100644 --- a/src/Frontend/src/mount.ts +++ b/src/Frontend/src/mount.ts @@ -1,5 +1,6 @@ import { createApp } from "vue"; import type { Router } from "vue-router"; +import logger from "@/logger"; import AuthApp from "./AuthApp.vue"; import Toast, { type PluginOptions, POSITION } from "vue-toastification"; import VueTippy from "vue-tippy"; @@ -26,7 +27,7 @@ export function mount({ router }: { router: Router }) { app.mount(`#app`); app.config.errorHandler = (err, instance) => { - console.error(instance, err); + logger.error(instance, err); }; return app; diff --git a/src/Frontend/src/stores/AuthStore.ts b/src/Frontend/src/stores/AuthStore.ts index 8ee3062751..e1cfaed097 100644 --- a/src/Frontend/src/stores/AuthStore.ts +++ b/src/Frontend/src/stores/AuthStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore } from "pinia"; +import logger from "@/logger"; import { ref } from "vue"; import type { AuthConfig } from "@/types/auth"; import { WebStorageStateStore } from "oidc-client-ts"; @@ -40,7 +41,7 @@ export const useAuthStore = defineStore("auth", () => { const [, data] = await serviceControlClient.fetchTypedFromServiceControl("authentication/configuration"); return data; } catch (err) { - console.error("Error fetching auth configuration", err); + logger.error("Error fetching auth configuration", err); return null; } } diff --git a/src/Frontend/src/stores/ConfigurationStore.ts b/src/Frontend/src/stores/ConfigurationStore.ts index 3c3e107826..4473d355e0 100644 --- a/src/Frontend/src/stores/ConfigurationStore.ts +++ b/src/Frontend/src/stores/ConfigurationStore.ts @@ -2,6 +2,7 @@ import { acceptHMRUpdate, defineStore } from "pinia"; import { computed, ref } from "vue"; import Configuration from "@/resources/Configuration"; import serviceControlClient from "@/components/serviceControlClient"; +import logger from "@/logger"; export const useConfigurationStore = defineStore("ConfigurationStore", () => { const configuration = ref(null); @@ -15,7 +16,7 @@ export const useConfigurationStore = defineStore("ConfigurationStore", () => { return configuration.value; }) .catch((error) => { - console.error("Failed to fetch configuration:", error); + logger.error("Failed to fetch configuration:", error); }); return { diff --git a/src/Frontend/src/stores/ConnectionsAndStatsStore.ts b/src/Frontend/src/stores/ConnectionsAndStatsStore.ts index 11d022aab4..293312cef3 100644 --- a/src/Frontend/src/stores/ConnectionsAndStatsStore.ts +++ b/src/Frontend/src/stores/ConnectionsAndStatsStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore } from "pinia"; +import logger from "@/logger"; import { computed, onMounted, onUnmounted, reactive, ref } from "vue"; import { FailedMessageStatus } from "@/resources/FailedMessage"; import { ConnectionState } from "@/resources/ConnectionState"; @@ -96,7 +97,7 @@ async function fetchAndSetConnectionState(fetchFunction: () => Promise { const emailNotifications = ref({ @@ -27,7 +28,7 @@ export const useHealthChecksStore = defineStore("HealthChecksStore", () => { const [, data] = await serviceControlClient.fetchTypedFromServiceControl("notifications/email"); result = data; } catch (err) { - console.error(err); + logger.error(err); result = { enabled: false, enable_tls: false, @@ -54,7 +55,7 @@ export const useHealthChecksStore = defineStore("HealthChecksStore", () => { ); if (result.message === "success") return true; else { - console.error(result.message); + logger.error(result.message); //set it back to what it was emailNotifications.value.enabled = !emailNotifications.value.enabled; return false; @@ -68,7 +69,7 @@ export const useHealthChecksStore = defineStore("HealthChecksStore", () => { ); if (result.message === "success") return true; else { - console.error(result.message); + logger.error(result.message); return false; } } @@ -88,7 +89,7 @@ export const useHealthChecksStore = defineStore("HealthChecksStore", () => { }; return true; } else { - console.error(result.message); + logger.error(result.message); return false; } } diff --git a/src/Frontend/src/stores/LicenseStore.ts b/src/Frontend/src/stores/LicenseStore.ts index 8c077b7df1..03e7f269b9 100644 --- a/src/Frontend/src/stores/LicenseStore.ts +++ b/src/Frontend/src/stores/LicenseStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore } from "pinia"; +import logger from "@/logger"; import { computed, reactive, ref } from "vue"; import serviceControlClient from "@/components/serviceControlClient"; import LicenseInfo, { LicenseStatus } from "@/resources/LicenseInfo"; @@ -99,7 +100,7 @@ export const useLicenseStore = defineStore("LicenseStore", () => { const [, data] = await serviceControlClient.fetchTypedFromServiceControl("license?refresh=true&clientName=servicepulse"); return data; } catch (err) { - console.error("Error fetching license information", err); + logger.error("Error fetching license information", err); return null; } } diff --git a/src/Frontend/src/stores/MessageStore.ts b/src/Frontend/src/stores/MessageStore.ts index 447b9d3e43..a018b435a7 100644 --- a/src/Frontend/src/stores/MessageStore.ts +++ b/src/Frontend/src/stores/MessageStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore, storeToRefs } from "pinia"; +import logger from "@/logger"; import { computed, reactive, Ref, ref } from "vue"; import Header from "@/resources/Header"; import type EndpointDetails from "@/resources/EndpointDetails"; @@ -85,7 +86,7 @@ export const useMessageStore = defineStore("MessageStore", () => { const [, data] = await serviceControlClient.fetchTypedFromServiceControl("edit/config"); edit_and_retry_config.value = data; } catch { - console.warn("Failed to load Edit and Retry configuration"); + logger.warn("Failed to load Edit and Retry configuration"); } } loadEditAndRetryConfiguration(); diff --git a/src/Frontend/src/stores/MonitoringStore.ts b/src/Frontend/src/stores/MonitoringStore.ts index 1519bd03dd..03d35f1823 100644 --- a/src/Frontend/src/stores/MonitoringStore.ts +++ b/src/Frontend/src/stores/MonitoringStore.ts @@ -1,5 +1,6 @@ import monitoringClient from "@/components/monitoring/monitoringClient"; import { defineStore, acceptHMRUpdate } from "pinia"; +import logger from "@/logger"; import { computed, ref, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import { useMonitoringHistoryPeriodStore } from "./MonitoringHistoryPeriodStore"; @@ -91,7 +92,7 @@ export const useMonitoringStore = defineStore("MonitoringStore", () => { }); } } catch (error) { - console.error(error); + logger.error(error); } } return endpoints; diff --git a/src/Frontend/src/stores/RecoverabilityStore.ts b/src/Frontend/src/stores/RecoverabilityStore.ts index c00f8c2e56..e94bcfb36f 100644 --- a/src/Frontend/src/stores/RecoverabilityStore.ts +++ b/src/Frontend/src/stores/RecoverabilityStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore, storeToRefs } from "pinia"; +import logger from "@/logger"; import { computed, ref, watch, shallowReadonly } from "vue"; import serviceControlClient from "@/components/serviceControlClient"; import { useCookies } from "vue3-cookies"; @@ -127,7 +128,7 @@ export const useRecoverabilityStore = defineStore("RecoverabilityStore", () => { loaded = true; } catch (err) { if (err instanceof Error && err.name === "AbortError") return; - console.error(err); + logger.error(err); } } diff --git a/src/Frontend/src/stores/SagaDiagramStore.ts b/src/Frontend/src/stores/SagaDiagramStore.ts index 191282231d..1bc660a821 100644 --- a/src/Frontend/src/stores/SagaDiagramStore.ts +++ b/src/Frontend/src/stores/SagaDiagramStore.ts @@ -1,4 +1,5 @@ import { acceptHMRUpdate, defineStore } from "pinia"; +import logger from "@/logger"; import { ref, watch } from "vue"; import { SagaHistory, SagaMessage } from "@/resources/SagaHistory"; import Message from "@/resources/Message"; @@ -148,7 +149,7 @@ export const useSagaDiagramStore = defineStore("SagaDiagramStore", () => { } return await response.json(); } catch (error) { - console.error("Error fetching audit messages:", error); + logger.error("Error fetching audit messages:", error); return { result: [] }; } } From 91a8eb5449c30e7b75016cf8ab178f46be60d7a8 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 23 Mar 2026 19:59:52 -0600 Subject: [PATCH 2/7] Revert redundant lint step from CI workflow --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eefe7c6a2c..e9c75f47db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,6 @@ jobs: uses: actions/setup-node@v6.3.0 with: node-version: 24.x - - name: Lint Frontend - run: npm run lint - working-directory: src/Frontend - name: Build Frontend run: .\build.ps1 working-directory: src/ServicePulse.Host From c87287611b853228fc405f9da6ee7570042833b9 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 23 Mar 2026 20:34:34 -0600 Subject: [PATCH 3/7] Fix no-console rule and remove test console noise --- src/Frontend/eslint.config.mjs | 5 +++-- src/Frontend/test/drivers/vitest/driver.ts | 4 ---- src/Frontend/test/drivers/vitest/setup.ts | 3 --- src/Frontend/test/mocks/scenarios/index.ts | 2 +- src/Frontend/test/preconditions/licensing.ts | 1 - 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Frontend/eslint.config.mjs b/src/Frontend/eslint.config.mjs index 303ed8d523..073736ff02 100644 --- a/src/Frontend/eslint.config.mjs +++ b/src/Frontend/eslint.config.mjs @@ -11,8 +11,9 @@ const localPlugin = { }, }; -// Configure console rule: error in CI, warn in development -const consoleRuleSeverity = process.env.CI === "true" ? "error" : "warn"; +// Configure console rule: error in CI to catch leftover debug calls before merge, +// off locally so developers get no IDE warnings and can use console freely during development. +const consoleRuleSeverity = process.env.CI === "true" ? "error" : "off"; export default tseslint.config( { diff --git a/src/Frontend/test/drivers/vitest/driver.ts b/src/Frontend/test/drivers/vitest/driver.ts index e14a90876d..b5a344edfa 100644 --- a/src/Frontend/test/drivers/vitest/driver.ts +++ b/src/Frontend/test/drivers/vitest/driver.ts @@ -63,19 +63,15 @@ const test = testVitest.extend<{ driver: Driver }>({ // eslint-disable-next-line no-empty-pattern, @typescript-eslint/no-explicit-any driver: async ({}, use: any) => { const driver = makeDriver(); - console.log("Starting test"); - //run the test await use(driver); - console.log("Test ended"); //unmount the app after the test runs driver.disposeApp(); // We need to wait for any pending promises to resolve before resetting handlers and clearing storage await flushPromises(); - console.log("Cleanup after test"); mockServer.resetHandlers(); //Make JSDOM create a fresh document per each test run jsdom.reconfigure({ url: "http://localhost:3000/" }); diff --git a/src/Frontend/test/drivers/vitest/setup.ts b/src/Frontend/test/drivers/vitest/setup.ts index c8005feded..94b9d44410 100644 --- a/src/Frontend/test/drivers/vitest/setup.ts +++ b/src/Frontend/test/drivers/vitest/setup.ts @@ -25,8 +25,6 @@ beforeEach(() => { }); beforeAll(() => { - console.log("Starting mock server"); - mockServer.listen({ onUnhandledRequest: (_, print) => { print.warning(); @@ -35,6 +33,5 @@ beforeAll(() => { }); afterAll(() => { - console.log("Shutting down mock server"); mockServer.close(); }); diff --git a/src/Frontend/test/mocks/scenarios/index.ts b/src/Frontend/test/mocks/scenarios/index.ts index 269b15dda0..728c0c5715 100644 --- a/src/Frontend/test/mocks/scenarios/index.ts +++ b/src/Frontend/test/mocks/scenarios/index.ts @@ -45,13 +45,13 @@ export async function loadScenario(): Promise { const loader = scenarios[scenarioName]; if (!loader) { + // eslint-disable-next-line no-console console.warn(`Unknown mock scenario: "${scenarioName}", falling back to default. Available: ${Object.keys(scenarios).join(", ")}`); const module = await scenarios.default(); if (module.setupComplete) await module.setupComplete; return module; } - console.log(`Loading mock scenario: ${scenarioName}`); const module = await loader(); // Wait for setup to complete before returning diff --git a/src/Frontend/test/preconditions/licensing.ts b/src/Frontend/test/preconditions/licensing.ts index 1b7aec9bc9..cbd55db079 100644 --- a/src/Frontend/test/preconditions/licensing.ts +++ b/src/Frontend/test/preconditions/licensing.ts @@ -63,7 +63,6 @@ const getLicenseMockedResponse = license_status: status, license_extension_url: licenseExtensionUrl, }; - console.log(response); driver.mockEndpoint(`${serviceControlInstanceUrl}license`, { body: response, }); From da6f3caa088aa00000fd0d078724141e66a97a5b Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Mar 2026 09:17:34 -0600 Subject: [PATCH 4/7] linting --- src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue b/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue index df32e60674..5645720069 100644 --- a/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue +++ b/src/Frontend/src/components/messages/FlowDiagram/FlowDiagram.vue @@ -144,7 +144,6 @@ function constructEdges(nodes: Node[]): DefaultEdge[] { if (m === undefined) return false; return m.receiving_endpoint !== undefined && m.sending_endpoint !== undefined && m.message_id === relatedTo && m.message_intent !== MessageIntent.Publish; }); - } switch (parentMessages.length) { From 585a6710d9e7b3cf0ab35b2671d35623b774c658 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Mar 2026 09:46:30 -0600 Subject: [PATCH 5/7] Fix logger type signatures to match console methods --- src/Frontend/src/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Frontend/src/logger.ts b/src/Frontend/src/logger.ts index aa182b6a4c..9762c9ca14 100644 --- a/src/Frontend/src/logger.ts +++ b/src/Frontend/src/logger.ts @@ -5,8 +5,8 @@ /* eslint-disable no-console */ const logger = { - error: (...args: unknown[]) => console.error(...args), - warn: (...args: unknown[]) => console.warn(...args), + error: ((...args) => console.error(...args)) as typeof console.error, + warn: ((...args) => console.warn(...args)) as typeof console.warn, }; /* eslint-enable no-console */ From 658d6e3af76eef840d677dfe61fcde0d26ed0136 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Mar 2026 10:11:05 -0600 Subject: [PATCH 6/7] testing the build will fail --- src/Frontend/test/mocks/scenarios/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Frontend/test/mocks/scenarios/index.ts b/src/Frontend/test/mocks/scenarios/index.ts index 728c0c5715..269b15dda0 100644 --- a/src/Frontend/test/mocks/scenarios/index.ts +++ b/src/Frontend/test/mocks/scenarios/index.ts @@ -45,13 +45,13 @@ export async function loadScenario(): Promise { const loader = scenarios[scenarioName]; if (!loader) { - // eslint-disable-next-line no-console console.warn(`Unknown mock scenario: "${scenarioName}", falling back to default. Available: ${Object.keys(scenarios).join(", ")}`); const module = await scenarios.default(); if (module.setupComplete) await module.setupComplete; return module; } + console.log(`Loading mock scenario: ${scenarioName}`); const module = await loader(); // Wait for setup to complete before returning From 132d690071f5dd6959601f2c1cb5067d0aa7fdfc Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Mar 2026 10:46:41 -0600 Subject: [PATCH 7/7] Revert "testing the build will fail" This reverts commit 658d6e3af76eef840d677dfe61fcde0d26ed0136. --- src/Frontend/test/mocks/scenarios/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Frontend/test/mocks/scenarios/index.ts b/src/Frontend/test/mocks/scenarios/index.ts index 269b15dda0..728c0c5715 100644 --- a/src/Frontend/test/mocks/scenarios/index.ts +++ b/src/Frontend/test/mocks/scenarios/index.ts @@ -45,13 +45,13 @@ export async function loadScenario(): Promise { const loader = scenarios[scenarioName]; if (!loader) { + // eslint-disable-next-line no-console console.warn(`Unknown mock scenario: "${scenarioName}", falling back to default. Available: ${Object.keys(scenarios).join(", ")}`); const module = await scenarios.default(); if (module.setupComplete) await module.setupComplete; return module; } - console.log(`Loading mock scenario: ${scenarioName}`); const module = await loader(); // Wait for setup to complete before returning