Prettier 2: Electric Boogaloo (#6113)

# Description of Changes
When I added Prettier formatting in #6052, my aim was to use just the
default settings in Prettier. Turns out, Prettier looks _really hard_
for any config files if it's not explicitly given one, which means that
if a developer has some sort of Prettier config file lying around on
their system, Prettier might find it and use it. Also, Prettier changes
its defaults based on stuff in `.editorconfig` without any good way of
disabling that behaviour explicitly in its config file.

To solve both of these issues, I've introduced a `.prettierrc` file
which sets Prettier's defaults explicitly, and then reformatted all our
code _again_ in Prettier's actual default settings. This should achieve
the aim of #6052 and remove the possibility for it breaking on different
dev computers.
This commit is contained in:
James Brunton
2026-04-17 09:50:16 +00:00
committed by GitHub
parent de8c483054
commit 8ab060a4be
1207 changed files with 52646 additions and 15352 deletions
+82 -42
View File
@@ -1,7 +1,19 @@
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from "react";
import {
createContext,
useContext,
useEffect,
useState,
ReactNode,
useCallback,
} from "react";
import { springAuth } from "@app/auth/springAuthClient";
import { clearPlatformAuthOnLoginInit } from "@app/extensions/authSessionCleanup";
import type { Session, User, AuthError, AuthChangeEvent } from "@app/auth/springAuthClient";
import type {
Session,
User,
AuthError,
AuthChangeEvent,
} from "@app/auth/springAuthClient";
/**
* Auth Context Type
@@ -54,7 +66,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
try {
setLoading(true);
setError(null);
console.debug("[Auth] refreshSession: start", { path: window.location.pathname });
console.debug("[Auth] refreshSession: start", {
path: window.location.pathname,
});
console.debug("[Auth] Refreshing session...");
const { data, error } = await springAuth.refreshSession();
@@ -110,9 +124,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const initializeAuth = async () => {
try {
console.debug(`[Auth:${mountId}] Initializing auth...`);
console.debug(`[Auth:${mountId}] Path: ${window.location.pathname} Search: ${window.location.search}`);
console.debug(
`[Auth:${mountId}] Path: ${window.location.pathname} Search: ${window.location.search}`,
);
// Clear any platform-specific cached auth on login page init.
if (typeof window !== "undefined" && window.location.pathname.startsWith("/login")) {
if (
typeof window !== "undefined" &&
window.location.pathname.startsWith("/login")
) {
await clearPlatformAuthOnLoginInit();
}
@@ -134,12 +153,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setSession(data.session);
}
} catch (err) {
console.error("[Auth] Unexpected error during auth initialization:", err);
console.error(
"[Auth] Unexpected error during auth initialization:",
err,
);
if (mounted) {
setError(err as AuthError);
}
} finally {
console.debug(`[Auth:${mountId}] Initialize auth complete. mounted=${mounted}`);
console.debug(
`[Auth:${mountId}] Initialize auth complete. mounted=${mounted}`,
);
if (mounted) {
setLoading(false);
}
@@ -152,8 +176,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const handleJwtAvailable = () => {
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
console.log(`[Auth:${mountId}] 🔄 JWT available event received`);
console.log(`[Auth:${mountId}] Current state: loading=${loading}, hasSession=${!!session}`);
console.log(`[Auth:${mountId}] Setting loading=true to stabilize auth state`);
console.log(
`[Auth:${mountId}] Current state: loading=${loading}, hasSession=${!!session}`,
);
console.log(
`[Auth:${mountId}] Setting loading=true to stabilize auth state`,
);
setLoading(true); // Prevent unstable renders during auth state transition
setError(null);
console.log(`[Auth:${mountId}] Refreshing session...`);
@@ -165,40 +193,52 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// Subscribe to auth state changes
const {
data: { subscription },
} = springAuth.onAuthStateChange(async (event: AuthChangeEvent, newSession: Session | null) => {
if (!mounted) {
console.log(`[Auth:${mountId}] ⚠️ Auth state change ignored (unmounted): ${event}`);
return;
}
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
console.log(`[Auth:${mountId}] 📢 Auth state change event: ${event}`);
console.log(`[Auth:${mountId}] Has session: ${!!newSession}`);
console.log(`[Auth:${mountId}] User: ${newSession?.user?.email || "none"}`);
console.log(`[Auth:${mountId}] Timestamp: ${new Date().toISOString()}`);
// Schedule state update
setTimeout(() => {
if (mounted) {
console.log(`[Auth:${mountId}] Applying session update (event: ${event})`);
setSession(newSession);
setError(null);
// Handle specific events
if (event === "SIGNED_OUT") {
console.log(`[Auth:${mountId}] ✓ User signed out, session cleared`);
} else if (event === "SIGNED_IN") {
console.log(`[Auth:${mountId}] ✓ User signed in successfully`);
} else if (event === "TOKEN_REFRESHED") {
console.log(`[Auth:${mountId}] ✓ Token refreshed`);
} else if (event === "USER_UPDATED") {
console.log(`[Auth:${mountId}] ✓ User updated`);
}
} else {
console.log(`[Auth:${mountId}] ⚠️ Session update skipped (unmounted during timeout)`);
} = springAuth.onAuthStateChange(
async (event: AuthChangeEvent, newSession: Session | null) => {
if (!mounted) {
console.log(
`[Auth:${mountId}] ⚠️ Auth state change ignored (unmounted): ${event}`,
);
return;
}
}, 0);
});
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
console.log(`[Auth:${mountId}] 📢 Auth state change event: ${event}`);
console.log(`[Auth:${mountId}] Has session: ${!!newSession}`);
console.log(
`[Auth:${mountId}] User: ${newSession?.user?.email || "none"}`,
);
console.log(`[Auth:${mountId}] Timestamp: ${new Date().toISOString()}`);
// Schedule state update
setTimeout(() => {
if (mounted) {
console.log(
`[Auth:${mountId}] Applying session update (event: ${event})`,
);
setSession(newSession);
setError(null);
// Handle specific events
if (event === "SIGNED_OUT") {
console.log(
`[Auth:${mountId}] ✓ User signed out, session cleared`,
);
} else if (event === "SIGNED_IN") {
console.log(`[Auth:${mountId}] ✓ User signed in successfully`);
} else if (event === "TOKEN_REFRESHED") {
console.log(`[Auth:${mountId}] ✓ Token refreshed`);
} else if (event === "USER_UPDATED") {
console.log(`[Auth:${mountId}] ✓ User updated`);
}
} else {
console.log(
`[Auth:${mountId}] ⚠️ Session update skipped (unmounted during timeout)`,
);
}
}, 0);
},
);
return () => {
console.log(`[Auth:${mountId}] 🔴 AuthProvider unmounting`);
@@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { springAuth } from "@app/auth/springAuthClient";
import { startOAuthNavigation } from "@app/extensions/oauthNavigation";
import apiClient from "@app/services/apiClient";
import { AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
import {
AxiosError,
type AxiosResponse,
type InternalAxiosRequestConfig,
} from "axios";
// Mock apiClient
vi.mock("@app/services/apiClient");
@@ -64,13 +68,19 @@ describe("SpringAuthClient", () => {
const mockToken = "invalid-jwt-token";
localStorage.setItem("stirling_jwt", mockToken);
const mockError = new AxiosError("Unauthorized", "ERR_BAD_REQUEST", undefined, undefined, {
status: 401,
statusText: "Unauthorized",
data: {},
headers: {},
config: {} as InternalAxiosRequestConfig,
});
const mockError = new AxiosError(
"Unauthorized",
"ERR_BAD_REQUEST",
undefined,
undefined,
{
status: 401,
statusText: "Unauthorized",
data: {},
headers: {},
config: {} as InternalAxiosRequestConfig,
},
);
vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);
@@ -86,13 +96,19 @@ describe("SpringAuthClient", () => {
const mockToken = "forbidden-jwt-token";
localStorage.setItem("stirling_jwt", mockToken);
const mockError = new AxiosError("Forbidden", "ERR_BAD_REQUEST", undefined, undefined, {
status: 403,
statusText: "Forbidden",
data: {},
headers: {},
config: {} as InternalAxiosRequestConfig,
});
const mockError = new AxiosError(
"Forbidden",
"ERR_BAD_REQUEST",
undefined,
undefined,
{
status: 403,
statusText: "Forbidden",
data: {},
headers: {},
config: {} as InternalAxiosRequestConfig,
},
);
vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);
@@ -145,7 +161,9 @@ describe("SpringAuthClient", () => {
{ withCredentials: true },
);
expect(localStorage.getItem("stirling_jwt")).toBe(mockToken);
expect(dispatchEventSpy).toHaveBeenCalledWith(expect.objectContaining({ type: "jwt-available" }));
expect(dispatchEventSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: "jwt-available" }),
);
expect(result.user).toEqual(mockUser);
expect(result.session?.access_token).toBe(mockToken);
expect(result.error).toBeNull();
@@ -336,7 +354,9 @@ describe("SpringAuthClient", () => {
options: { redirectTo: "/auth/callback" },
});
expect(startOAuthNavigation).toHaveBeenCalledWith("/oauth2/authorization/github");
expect(startOAuthNavigation).toHaveBeenCalledWith(
"/oauth2/authorization/github",
);
expect(mockAssign).toHaveBeenCalledWith("/oauth2/authorization/github");
expect(result.error).toBeNull();
});
@@ -355,7 +375,9 @@ describe("SpringAuthClient", () => {
options: { redirectTo: "/auth/callback" },
});
expect(startOAuthNavigation).toHaveBeenCalledWith("/oauth2/authorization/github");
expect(startOAuthNavigation).toHaveBeenCalledWith(
"/oauth2/authorization/github",
);
expect(mockAssign).not.toHaveBeenCalled();
expect(result.error).toBeNull();
});
+108 -28
View File
@@ -39,7 +39,12 @@ function getHttpStatus(error: unknown): number | undefined {
// Helper to extract error message from axios error
function getErrorMessage(error: unknown, fallback: string): string {
if (error instanceof AxiosError) {
return error.response?.data?.error || error.response?.data?.message || error.message || fallback;
return (
error.response?.data?.error ||
error.response?.data?.message ||
error.message ||
fallback
);
}
return error instanceof Error ? error.message : fallback;
}
@@ -108,9 +113,16 @@ export interface AuthResponse {
error: AuthError | null;
}
export type AuthChangeEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED";
export type AuthChangeEvent =
| "SIGNED_IN"
| "SIGNED_OUT"
| "TOKEN_REFRESHED"
| "USER_UPDATED";
type AuthChangeCallback = (event: AuthChangeEvent, session: Session | null) => void;
type AuthChangeCallback = (
event: AuthChangeEvent,
session: Session | null,
) => void;
class SpringAuthClient {
private listeners: AuthChangeCallback[] = [];
@@ -138,7 +150,9 @@ class SpringAuthClient {
try {
const payload = this.decodeJwtPayload(token);
if (!payload) {
console.warn("[SpringAuth] Cannot decode token for adaptive intervals, using defaults");
console.warn(
"[SpringAuth] Cannot decode token for adaptive intervals, using defaults",
);
return;
}
@@ -146,7 +160,9 @@ class SpringAuthClient {
const iatSeconds = typeof payload?.iat === "number" ? payload.iat : 0;
if (expSeconds <= 0 || iatSeconds <= 0) {
console.warn("[SpringAuth] Token missing exp/iat claims, using default intervals");
console.warn(
"[SpringAuth] Token missing exp/iat claims, using default intervals",
);
return;
}
@@ -155,12 +171,18 @@ class SpringAuthClient {
// Check interval: check 6 times during token lifetime
// Min: 5 seconds (for very short tokens)
// Max: 60 seconds (don't check too infrequently)
this.sessionCheckIntervalMs = Math.max(5000, Math.min(60000, tokenLifetimeMs / 6));
this.sessionCheckIntervalMs = Math.max(
5000,
Math.min(60000, tokenLifetimeMs / 6),
);
// Refresh threshold: refresh when 25% of lifetime remaining
// Min: 30 seconds (give buffer for refresh to complete)
// Max: 5 minutes (don't wait too long for long-lived tokens)
this.tokenRefreshThresholdMs = Math.max(30000, Math.min(300000, tokenLifetimeMs / 4));
this.tokenRefreshThresholdMs = Math.max(
30000,
Math.min(300000, tokenLifetimeMs / 4),
);
console.log("[SpringAuth] 📊 Adaptive intervals calculated:", {
tokenLifetime: Math.floor(tokenLifetimeMs / 1000) + "s",
@@ -171,7 +193,10 @@ class SpringAuthClient {
// Restart monitoring with new interval
this.restartSessionMonitoring();
} catch (error) {
console.warn("[SpringAuth] Failed to calculate adaptive intervals:", error);
console.warn(
"[SpringAuth] Failed to calculate adaptive intervals:",
error,
);
}
}
@@ -190,7 +215,10 @@ class SpringAuthClient {
return JSON.parse(atob(base64));
}
private getTokenExpiry(token: string): { expiresIn: number; expiresAt: number } {
private getTokenExpiry(token: string): {
expiresIn: number;
expiresAt: number;
} {
try {
const payload = this.decodeJwtPayload(token);
if (!payload) {
@@ -198,8 +226,12 @@ class SpringAuthClient {
}
const expSeconds = typeof payload?.exp === "number" ? payload.exp : 0;
const expiresAt = expSeconds > 0 ? expSeconds * 1000 : Date.now() + 3600 * 1000;
const expiresIn = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
const expiresAt =
expSeconds > 0 ? expSeconds * 1000 : Date.now() + 3600 * 1000;
const expiresIn = Math.max(
0,
Math.floor((expiresAt - Date.now()) / 1000),
);
return { expiresIn, expiresAt };
} catch {
@@ -227,7 +259,10 @@ class SpringAuthClient {
* Get current session
* JWT is stored in localStorage and sent via Authorization header
*/
async getSession(): Promise<{ data: { session: Session | null }; error: AuthError | null }> {
async getSession(): Promise<{
data: { session: Session | null };
error: AuthError | null;
}> {
try {
// Get JWT from localStorage
let token = localStorage.getItem("stirling_jwt");
@@ -265,7 +300,10 @@ class SpringAuthClient {
const session: Session = {
user: {
id: platformUser?.email || platformUser?.username || "desktop-saas-user",
id:
platformUser?.email ||
platformUser?.username ||
"desktop-saas-user",
email: platformUser?.email || "",
username: platformUser?.username || platformUser?.email || "User",
role: "USER",
@@ -334,7 +372,11 @@ class SpringAuthClient {
/**
* Sign in with email and password
*/
async signInWithPassword(credentials: { email: string; password: string; mfaCode?: string }): Promise<AuthResponse> {
async signInWithPassword(credentials: {
email: string;
password: string;
mfaCode?: string;
}): Promise<AuthResponse> {
try {
const response = await apiClient.post(
"/api/v1/auth/login",
@@ -379,7 +421,11 @@ class SpringAuthClient {
console.error("[SpringAuth] signInWithPassword error:", error);
if (error instanceof AxiosError) {
const errorCode = error.response?.data?.error as string | undefined;
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || "Login failed";
const errorMessage =
error.response?.data?.message ||
error.response?.data?.error ||
error.message ||
"Login failed";
return {
user: null,
session: null,
@@ -462,7 +508,10 @@ class SpringAuthClient {
return { error: null };
} catch (error) {
return {
error: { message: error instanceof Error ? error.message : "SSO redirect failed" },
error: {
message:
error instanceof Error ? error.message : "SSO redirect failed",
},
};
}
}
@@ -473,7 +522,10 @@ class SpringAuthClient {
async signOut(): Promise<{ error: AuthError | null }> {
try {
if (typeof window !== "undefined") {
window.sessionStorage.setItem("stirling_sso_auto_login_logged_out", "1");
window.sessionStorage.setItem(
"stirling_sso_auto_login_logged_out",
"1",
);
}
const response = await apiClient.post("/api/v1/auth/logout", null, {
headers: {
@@ -496,14 +548,18 @@ class SpringAuthClient {
// Clear any cached OAuth redirect/session state
resetOAuthState();
} catch (err) {
console.warn("[SpringAuth] Failed to clear Supabase/local auth tokens", err);
console.warn(
"[SpringAuth] Failed to clear Supabase/local auth tokens",
err,
);
}
// Clear cookies that might hold refresh/session tokens
try {
document.cookie.split(";").forEach((cookie) => {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
const name =
eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
if (name) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;`;
}
@@ -515,7 +571,10 @@ class SpringAuthClient {
try {
await clearPlatformAuthAfterSignOut();
} catch (cleanupError) {
console.warn("[SpringAuth] Failed to run platform auth cleanup", cleanupError);
console.warn(
"[SpringAuth] Failed to run platform auth cleanup",
cleanupError,
);
}
// Notify listeners
@@ -529,7 +588,10 @@ class SpringAuthClient {
try {
await clearPlatformAuthAfterSignOut();
} catch (cleanupError) {
console.warn("[SpringAuth] Failed to run platform auth cleanup after error", cleanupError);
console.warn(
"[SpringAuth] Failed to run platform auth cleanup after error",
cleanupError,
);
}
return {
error: { message: getErrorMessage(error, "Logout failed") },
@@ -540,7 +602,10 @@ class SpringAuthClient {
/**
* Refresh JWT token
*/
async refreshSession(): Promise<{ data: { session: Session | null }; error: AuthError | null }> {
async refreshSession(): Promise<{
data: { session: Session | null };
error: AuthError | null;
}> {
try {
if (await isDesktopSaaSAuthMode()) {
const refreshed = await refreshPlatformSession();
@@ -556,7 +621,9 @@ class SpringAuthClient {
if (error || !data.session) {
return {
data: { session: null },
error: error || { message: "Token refresh failed - please log in again" },
error: error || {
message: "Token refresh failed - please log in again",
},
};
}
@@ -610,7 +677,10 @@ class SpringAuthClient {
// Handle different error statuses
const status = getHttpStatus(error);
if (status === 401 || status === 403) {
return { data: { session: null }, error: { message: "Token refresh failed - please log in again" } };
return {
data: { session: null },
error: { message: "Token refresh failed - please log in again" },
};
}
return {
@@ -623,7 +693,9 @@ class SpringAuthClient {
/**
* Listen to auth state changes
*/
onAuthStateChange(callback: AuthChangeCallback): { data: { subscription: { unsubscribe: () => void } } } {
onAuthStateChange(callback: AuthChangeCallback): {
data: { subscription: { unsubscribe: () => void } };
} {
this.listeners.push(callback);
return {
@@ -646,7 +718,10 @@ class SpringAuthClient {
try {
callback(event, session);
} catch (error) {
console.error("[SpringAuth] Error in auth state change listener:", error);
console.error(
"[SpringAuth] Error in auth state change listener:",
error,
);
}
});
}, 0);
@@ -665,9 +740,14 @@ class SpringAuthClient {
const timeUntilExpiry = (data.session.expires_at || 0) - Date.now();
// Refresh if token expires soon (threshold is adaptive)
if (timeUntilExpiry > 0 && timeUntilExpiry < this.tokenRefreshThresholdMs) {
if (
timeUntilExpiry > 0 &&
timeUntilExpiry < this.tokenRefreshThresholdMs
) {
console.log(
"[SpringAuth] 🔄 Proactively refreshing token (expires in " + Math.floor(timeUntilExpiry / 1000) + "s)",
"[SpringAuth] 🔄 Proactively refreshing token (expires in " +
Math.floor(timeUntilExpiry / 1000) +
"s)",
);
await this.refreshSession();
}