diff --git a/.agents/workflows/generate-release.md b/.agents/workflows/generate-release.md index 7e05c01305..c6107339bb 100644 --- a/.agents/workflows/generate-release.md +++ b/.agents/workflows/generate-release.md @@ -259,7 +259,7 @@ git pull origin main VERSION=$(node -p "require('./package.json').version") # Extracts the changelog section for this version -NOTES=$(awk '/^## \['"$VERSION"'\]/{flag=1; next} /^## \[[0-9]+/{if(flag) exit} flag' CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') +NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi git tag -a "v$VERSION" -m "Release v$VERSION" diff --git a/src/app/api/usage/analytics/route.ts b/src/app/api/usage/analytics/route.ts index e52a4141f4..2c34e593a1 100644 --- a/src/app/api/usage/analytics/route.ts +++ b/src/app/api/usage/analytics/route.ts @@ -122,7 +122,7 @@ function computeActivityStreak(activityMap: Record): number { return streak; } -export async function GET(request) { +export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const range = searchParams.get("range") || "30d"; diff --git a/src/shared/utils/formatting.ts b/src/shared/utils/formatting.ts index 5a42c87ecf..6dd941cf2a 100644 --- a/src/shared/utils/formatting.ts +++ b/src/shared/utils/formatting.ts @@ -10,8 +10,9 @@ * @param {string} isoString - ISO 8601 date string * @returns {string} */ -export function formatTime(isoString) { +export function formatTime(isoString: string | null | undefined) { try { + if (!isoString) return "-"; const d = new Date(isoString); return d.toLocaleTimeString("en-US", { hour12: false, @@ -29,7 +30,7 @@ export function formatTime(isoString) { * @param {number} ms - Duration in milliseconds * @returns {string} e.g., "42ms", "1.2s", "-" */ -export function formatDuration(ms) { +export function formatDuration(ms: number | null | undefined) { if (!ms) return "-"; if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(1)}s`; @@ -40,8 +41,9 @@ export function formatDuration(ms) { * @param {string} iso - ISO 8601 date string * @returns {string} */ -export function formatDateTime(iso) { +export function formatDateTime(iso: string | null | undefined) { try { + if (!iso) return "-"; const d = new Date(iso); return d.toLocaleDateString("pt-BR") + ", " + d.toLocaleTimeString("en-US", { hour12: false }); } catch { @@ -56,7 +58,7 @@ export function formatDateTime(iso) { * @param {number} end - Number of characters to show at end (default: 2) * @returns {string} */ -export function maskSegment(value, start = 2, end = 2) { +export function maskSegment(value: string | null | undefined, start = 2, end = 2) { if (!value) return ""; if (value.length <= start + end) return `${value.slice(0, 1)}***`; return `${value.slice(0, start)}***${value.slice(-end)}`; @@ -68,7 +70,7 @@ export function maskSegment(value, start = 2, end = 2) { * @param {boolean} emailsVisible - Whether to show full email (true) or mask it (false) * @returns {string} */ -export function maskAccount(account, emailsVisible) { +export function maskAccount(account: string | null | undefined, emailsVisible: boolean) { if (!account || account === "-") return "-"; const atIdx = account.indexOf("@"); if (atIdx > 3) { @@ -87,7 +89,10 @@ export function maskAccount(account, emailsVisible) { * @param {string} apiKeyId - Unique ID of the key * @returns {string} */ -export function formatApiKeyLabel(apiKeyName, apiKeyId) { +export function formatApiKeyLabel( + apiKeyName: string | null | undefined, + apiKeyId: string | null | undefined +) { if (!apiKeyName && !apiKeyId) return "—"; const displayName = apiKeyName || "key"; if (!apiKeyId) return displayName; @@ -99,7 +104,7 @@ export function formatApiKeyLabel(apiKeyName, apiKeyId) { * @param {string} key - API key or token to mask * @returns {string} */ -export function maskKey(key) { +export function maskKey(key: string | null | undefined) { if (!key || key.length < 8) return "***"; return `${key.slice(0, 4)}...${key.slice(-4)}`; } @@ -109,10 +114,10 @@ export function maskKey(key) { * @param {number} n - Number to format * @returns {string} */ -export function fmtCompact(n) { - if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`; - if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; - if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; +export function fmtCompact(n: number | null | undefined) { + if (n && n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`; + if (n && n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n && n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return new Intl.NumberFormat().format(n || 0); } @@ -121,7 +126,7 @@ export function fmtCompact(n) { * @param {number} n - Number to format * @returns {string} */ -export function fmtFull(n) { +export function fmtFull(n: number | null | undefined) { return new Intl.NumberFormat().format(n || 0); } @@ -147,7 +152,7 @@ export const fmtCost = formatCost; * @param {number} max - Maximum characters (default: 50) * @returns {string} */ -export function truncateUrl(url, max = 50) { +export function truncateUrl(url: string | null | undefined, max = 50) { if (!url) return "-"; try { const parsed = new URL(url); diff --git a/tests/unit/chatgpt-web.test.ts b/tests/unit/chatgpt-web.test.ts index e899e2306d..dd49da1c32 100644 --- a/tests/unit/chatgpt-web.test.ts +++ b/tests/unit/chatgpt-web.test.ts @@ -1231,9 +1231,9 @@ test("thinking_effort: bare chatgpt.com slug (e.g. gpt-5-4-t-mini) passed as mod 1, `bare slug ${bareSlug} must trigger thinking_effort PATCH` ); - assert.match( - m.calls.userConfigUrls[0], - new RegExp(`model_slug=${bareSlug.replace(/\./g, "\\.")}`) + assert.ok( + m.calls.userConfigUrls[0].includes(`model_slug=${bareSlug}`), + `URL should contain model_slug=${bareSlug}` ); } finally { m.restore();