From e3515c1102ccfd25c857a387eb09c8dfa281d48e Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:07:29 +0000 Subject: [PATCH 1/7] chore(husky): fix broken pre-commit hook after repo split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hook ran 'cd companion' from a non-existent subdirectory — a leftover from when this code lived inside calcom/cal as a 'companion/' subdir. Replace with 'bunx lint-staged' run from the repo root so commits aren't blocked in fresh clones. --- .husky/pre-commit | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 0874d0a1..ea5a55b6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1 @@ -cd companion -npx lint-staged +bunx lint-staged From 2f37ded34a214e24094a38ff31f5ca59e325c93c Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:07:29 +0000 Subject: [PATCH 2/7] feat(extension): add region-aware URL helpers with chrome.storage sync --- apps/extension/lib/region.ts | 138 +++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 apps/extension/lib/region.ts diff --git a/apps/extension/lib/region.ts b/apps/extension/lib/region.ts new file mode 100644 index 00000000..3d0094fd --- /dev/null +++ b/apps/extension/lib/region.ts @@ -0,0 +1,138 @@ +/** + * Cal.com data region helpers for the Chrome extension. + * + * Mirrors `apps/mobile/utils/region.ts` but reads from + * `chrome.storage.local["cal_region"]` — the same key the background worker + * writes via the `sync-oauth-tokens` handler + * (`apps/extension/entrypoints/background/index.ts:26`). + * + * Region is cached synchronously in a module-level variable so content-script + * callsites can build URLs without an `await`. The cache is initialized on + * module import via the fire-and-forget `preloadRegion()` and kept in sync + * thereafter by a `chrome.storage.onChanged` listener. + * + * NEVER inline literal `cal.com` / `cal.eu` hostnames elsewhere in + * `apps/extension/**`. The CI grep `check:no-cal-hostnames` enforces this. + */ + +export type CalRegion = "us" | "eu"; + +const REGION_STORAGE_KEY = "cal_region"; +const DEFAULT_REGION: CalRegion = "us"; + +let currentRegion: CalRegion = DEFAULT_REGION; +let preloaded = false; + +function isValidRegion(value: unknown): value is CalRegion { + return value === "us" || value === "eu"; +} + +function isChromeStorageAvailable(): boolean { + return ( + typeof chrome !== "undefined" && + typeof chrome.storage !== "undefined" && + typeof chrome.storage.local !== "undefined" + ); +} + +/** + * Load the persisted region from chrome.storage.local. Idempotent — subsequent + * calls re-read storage but do not re-register listeners. Best-effort: on + * storage failure the cache stays at its current value (default US for the + * first call, last-known otherwise). + */ +export async function preloadRegion(): Promise { + if (!isChromeStorageAvailable()) { + preloaded = true; + return currentRegion; + } + try { + const result = await chrome.storage.local.get([REGION_STORAGE_KEY]); + if (isValidRegion(result[REGION_STORAGE_KEY])) { + currentRegion = result[REGION_STORAGE_KEY]; + } + } catch { + // Best-effort; stay on default. + } + preloaded = true; + return currentRegion; +} + +if ( + typeof chrome !== "undefined" && + chrome.storage?.onChanged && + typeof chrome.storage.onChanged.addListener === "function" +) { + chrome.storage.onChanged.addListener((changes, areaName) => { + if (areaName !== "local") return; + if (!changes[REGION_STORAGE_KEY]) return; + const next = changes[REGION_STORAGE_KEY].newValue; + if (isValidRegion(next)) { + currentRegion = next; + } else if (next === undefined) { + currentRegion = DEFAULT_REGION; + } + }); +} + +// Kick off the initial read on import. Fire-and-forget — the helpers below are +// safe to call before this resolves (they fall back to the default). +preloadRegion(); + +export function getRegion(): CalRegion { + return currentRegion; +} + +export function isRegionPreloaded(): boolean { + return preloaded; +} + +/** + * `https://app.cal.{com|eu}` — Cal.com web app origin for the current region. + * + * Note: the very first synchronous call after extension load may return the + * US default before `preloadRegion()` resolves. In practice this is benign: + * content scripts only construct URLs in user-event handlers, by which time + * the async preload has completed. + */ +export function getCalAppUrl(region: CalRegion = currentRegion): string { + return region === "eu" ? "https://app.cal.eu" : "https://app.cal.com"; +} + +/** + * `https://api.cal.{com|eu}` — Cal.com API origin for the current region. + * Same first-call caveat as `getCalAppUrl()`. + */ +export function getCalApiUrl(region: CalRegion = currentRegion): string { + return region === "eu" ? "https://api.cal.eu" : "https://api.cal.com"; +} + +/** + * `https://cal.{com|eu}` — Cal.com booking-page origin for the current region. + * Same first-call caveat as `getCalAppUrl()`. + */ +export function getCalWebUrl(region: CalRegion = currentRegion): string { + return region === "eu" ? "https://cal.eu" : "https://cal.com"; +} + +/** + * Cal.com Framer marketing landing for the "open extension" deep link. Stays + * global — the Framer-hosted marketing site does not have an EU mirror. + * Same precedent as `getCalSupportUrl()` / `getCalHelpUrl()` in mobile's + * region module. + */ +export function getCalMarketingAppUrl(): string { + return "https://cal.com/app?openExtension=true"; +} + +/** + * Hostnames the extension recognizes as Cal.com booking surfaces across both + * regions. Use for regex / `text.includes(...)` host detection. Do NOT use to + * build outbound URLs (use the getters above). + */ +export const CAL_WEB_HOSTNAMES: ReadonlyArray = [ + "cal.com", + "cal.eu", + "app.cal.com", + "app.cal.eu", +]; From 5ea7a9d6633411f381c0e905d9c7fcc19a825dd1 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:09:22 +0000 Subject: [PATCH 3/7] fix(extension): build content-script URLs via region-aware helpers Replace 13 hardcoded https://cal.com / https://app.cal.com URLs in content scripts (Gmail integration, embed slot picker, fallback handles) with the getCalAppUrl() / getCalWebUrl() helpers from lib/region. EU users will now get cal.eu / app.cal.eu URLs in composed emails, copied booking links, and 'Edit event type' deep links. --- apps/extension/entrypoints/content.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/extension/entrypoints/content.ts b/apps/extension/entrypoints/content.ts index e1abcc94..347b7ae7 100644 --- a/apps/extension/entrypoints/content.ts +++ b/apps/extension/entrypoints/content.ts @@ -1,6 +1,7 @@ /// import { initGoogleCalendarIntegration } from "../lib/google-calendar"; import { initLinkedInIntegration } from "../lib/linkedin"; +import { getCalAppUrl, getCalWebUrl } from "../lib/region"; import { escapeHtml } from "../lib/utils"; /** @@ -1091,7 +1092,7 @@ export default defineContentScript({ e.stopPropagation(); const bookingUrl = eventType.bookingUrl || - `https://cal.com/${ + `${getCalWebUrl()}/${ eventType.users?.[0]?.username || "user" }/${eventType.slug}`; window.open(bookingUrl, "_blank"); @@ -1135,7 +1136,7 @@ export default defineContentScript({ // Copy to clipboard const bookingUrl = eventType.bookingUrl || - `https://cal.com/${ + `${getCalWebUrl()}/${ eventType.users?.[0]?.username || "user" }/${eventType.slug}`; navigator.clipboard @@ -1197,7 +1198,7 @@ export default defineContentScript({ editBtn.addEventListener("click", (e) => { e.stopPropagation(); - const editUrl = `https://app.cal.com/event-types/${eventType.id}`; + const editUrl = `${getCalAppUrl()}/event-types/${eventType.id}`; window.open(editUrl, "_blank"); }); editBtn.addEventListener("mouseenter", () => { @@ -1308,7 +1309,7 @@ export default defineContentScript({ // Construct the Cal.com booking link const bookingUrl = eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; // Try to insert at cursor position in the compose field const inserted = insertTextAtCursor(bookingUrl); @@ -1336,7 +1337,7 @@ export default defineContentScript({ // Construct the Cal.com booking link const bookingUrl = eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; // Try to insert at cursor position in the compose field const inserted = insertTextAtCursor(bookingUrl); @@ -1840,7 +1841,7 @@ export default defineContentScript({ e.stopPropagation(); const bookingUrl = eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; window.open(bookingUrl, "_blank"); }); previewBtn.addEventListener("mouseenter", () => { @@ -1882,7 +1883,7 @@ export default defineContentScript({ // Copy to clipboard const bookingUrl = eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; navigator.clipboard .writeText(bookingUrl) .then(() => { @@ -1942,7 +1943,7 @@ export default defineContentScript({ editBtn.addEventListener("click", (e) => { e.stopPropagation(); - const editUrl = `https://app.cal.com/event-types/${eventType.id}`; + const editUrl = `${getCalAppUrl()}/event-types/${eventType.id}`; window.open(editUrl, "_blank"); }); editBtn.addEventListener("mouseenter", () => { @@ -2053,7 +2054,7 @@ export default defineContentScript({ // Construct the Cal.com booking link const bookingUrl = eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`; // Try to insert at cursor position in the message field const inserted = insertTextAtCursor(bookingUrl); @@ -2242,7 +2243,7 @@ export default defineContentScript({ .map((slot) => { // URL-encode the timezone to handle special characters like "/" const encodedTimezone = encodeURIComponent(timezone); - const bookingURL = `https://cal.com/${username}/${eventType.slug}?duration=${duration}&date=${slot.isoDate}&slot=${slot.isoTimestamp}&cal.tz=${encodedTimezone}`; + const bookingURL = `${getCalWebUrl()}/${username}/${eventType.slug}?duration=${duration}&date=${slot.isoDate}&slot=${slot.isoTimestamp}&cal.tz=${encodedTimezone}`; return ` @@ -2306,7 +2307,7 @@ export default defineContentScript({ ${datesHTML}
- See all available times → @@ -3416,7 +3417,7 @@ export default defineContentScript({ const username = eventTypes.length > 0 ? eventTypes[0].users?.[0]?.username || "user" : "user"; - const createUrl = `https://app.cal.com/event-types?dialog=new&eventPage=${username}`; + const createUrl = `${getCalAppUrl()}/event-types?dialog=new&eventPage=${username}`; window.open(createUrl, "_blank"); showGmailNotification( `Opening Cal.com to create ${parsedData.detectedDuration}min event type`, @@ -3694,7 +3695,7 @@ export default defineContentScript({ const selectedUsername = selectedEventType.users?.[0]?.username || "user"; // Generate Cal.com URL with slot parameters - const baseUrl = `https://cal.com/${selectedUsername}/${selectedSlug}`; + const baseUrl = `${getCalWebUrl()}/${selectedUsername}/${selectedSlug}`; const params = new URLSearchParams({ overlayCalendar: "true", date: slot.isoDate, From 846c6e5140223c448293f2b2eb26cd8e4075862b Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:09:53 +0000 Subject: [PATCH 4/7] fix(extension): build LinkedIn integration URLs via region-aware helpers LinkedIn's 'Edit event type' and the booking-URL fallback both pointed at https://app.cal.com / https://cal.com. Route them through the new getCalAppUrl() / getCalWebUrl() helpers so EU hosts get app.cal.eu / cal.eu links. --- apps/extension/lib/linkedin.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/extension/lib/linkedin.ts b/apps/extension/lib/linkedin.ts index 6f5b103a..fe12cd3c 100644 --- a/apps/extension/lib/linkedin.ts +++ b/apps/extension/lib/linkedin.ts @@ -1,4 +1,5 @@ /// +import { getCalAppUrl, getCalWebUrl } from "./region"; import { escapeHtml } from "./utils"; // LinkedIn integration: inject a Cal.com scheduling button in LinkedIn messaging @@ -617,7 +618,7 @@ export function initLinkedInIntegration() { const editBtn = createActionButton(SVG_ICONS.EDIT, "Edit", "0 6px 6px 0", tooltipsToCleanup); editBtn.addEventListener("click", (e) => { e.stopPropagation(); - const editUrl = `https://app.cal.com/event-types/${eventType.id}`; + const editUrl = `${getCalAppUrl()}/event-types/${eventType.id}`; window.open(editUrl, "_blank"); }); @@ -668,7 +669,7 @@ export function initLinkedInIntegration() { function buildBookingUrl(eventType: EventType): string { return ( eventType.bookingUrl || - `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}` + `${getCalWebUrl()}/${eventType.users?.[0]?.username || "user"}/${eventType.slug}` ); } From 6a2d8a6d10328e7735dd3a8b4bd7ea705b09aa25 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:10:30 +0000 Subject: [PATCH 5/7] fix(extension): make Google Calendar parsers region-aware extractBookingUid()'s regex matched only https://(app.)?cal.com/booking/{uid}, so descriptions referencing cal.eu booking URLs never triggered the no-show button injection. Broaden the regex to (com|eu) and switch the two text.includes() host checks to a lowercased CAL_WEB_HOSTNAMES scan so cal.com, Cal.com, cal.eu, Cal.eu, app.cal.com, app.cal.eu all match. --- apps/extension/lib/google-calendar.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/extension/lib/google-calendar.ts b/apps/extension/lib/google-calendar.ts index c528cb10..ead8d8b9 100644 --- a/apps/extension/lib/google-calendar.ts +++ b/apps/extension/lib/google-calendar.ts @@ -1,4 +1,5 @@ /// +import { CAL_WEB_HOSTNAMES } from "./region"; // Google Calendar integration: inject a no-show toggle next to attendees in event popups. const bookingStatusCache = new Map; timestamp: number }>(); @@ -266,11 +267,12 @@ function injectStyles(): void { } /** - * Extract booking UID from event description - * Looks for patterns like: https://cal.com/booking/{uid} or https://app.cal.com/booking/{uid} + * Extract booking UID from event description. + * Matches both regions: https://cal.{com|eu}/booking/{uid} and + * https://app.cal.{com|eu}/booking/{uid}. */ function extractBookingUid(text: string): string | null { - const regex = /https:\/\/(?:app\.)?cal\.com\/booking\/([a-zA-Z0-9]+)/; + const regex = /https:\/\/(?:app\.)?cal\.(?:com|eu)\/booking\/([a-zA-Z0-9]+)/; const match = text.match(regex); return match ? match[1] : null; } @@ -1199,10 +1201,10 @@ function observeEventPopups(): void { } const text = element.textContent || ""; - // Check if this looks like a Cal.com event + const lowerText = text.toLowerCase(); + // Check if this looks like a Cal.com event (case-insensitive across both regions) const isCalComEvent = - text.includes("cal.com") || - text.includes("Cal.com") || + CAL_WEB_HOSTNAMES.some((host) => lowerText.includes(host)) || text.includes("booking") || extractBookingUid(text) !== null; @@ -1354,11 +1356,11 @@ function observeEventPopups(): void { return; } - // Check if this is a Cal.com event + // Check if this is a Cal.com event (case-insensitive across both regions) const text = popup.textContent || ""; + const lowerText = text.toLowerCase(); const isCalComEvent = - text.includes("cal.com") || - text.includes("Cal.com") || + CAL_WEB_HOSTNAMES.some((host) => lowerText.includes(host)) || text.includes("booking") || extractBookingUid(text) !== null; From 4115636f6da446d29ff3c7012c819b44d199b6c3 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:12:17 +0000 Subject: [PATCH 6/7] fix(extension): build background URLs via region-aware helpers openAppPage() now routes through getCalMarketingAppUrl() so the policy (Framer marketing is intentionally cross-region) is explicit at the helper level rather than inline. apiBaseUrlForRegion() is deleted; both call sites (getApiBaseUrl and validateTokens) now build the URL via getCalApiUrl(region) + '/v2', so there is a single source of truth for Cal.com origins shared with the content-script callsites. --- apps/extension/entrypoints/background/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/extension/entrypoints/background/index.ts b/apps/extension/entrypoints/background/index.ts index e8eed4a9..3635eb6f 100644 --- a/apps/extension/entrypoints/background/index.ts +++ b/apps/extension/entrypoints/background/index.ts @@ -1,5 +1,6 @@ /// +import { getCalApiUrl, getCalMarketingAppUrl } from "../../lib/region"; import type { Booking } from "../../types/bookings.types"; import type { OAuthTokens } from "../../types/oauth"; @@ -325,11 +326,12 @@ function isRestrictedUrl(url: string | undefined): boolean { return restrictedPatterns.some((pattern) => pattern.test(url)); } -// Open cal.com/app (Framer marketing page) in a new tab with auto-open parameter +// Open the Cal.com Framer marketing landing in a new tab with auto-open parameter. +// The marketing site is intentionally cross-region — see getCalMarketingAppUrl(). function openAppPage(): void { const tabsAPI = getTabsAPI(); if (tabsAPI) { - tabsAPI.create({ url: "https://cal.com/app?openExtension=true" }); + tabsAPI.create({ url: getCalMarketingAppUrl() }); } } @@ -895,13 +897,9 @@ async function getStoredRegion(): Promise<"us" | "eu"> { } } -function apiBaseUrlForRegion(region: "us" | "eu"): string { - return region === "eu" ? "https://api.cal.eu/v2" : "https://api.cal.com/v2"; -} - async function getApiBaseUrl(): Promise { const region = await getStoredRegion(); - return apiBaseUrlForRegion(region); + return `${getCalApiUrl(region)}/v2`; } const tokenOperationTimestamps: number[] = []; @@ -934,7 +932,7 @@ async function validateTokens(tokens: OAuthTokens, region?: "us" | "eu"): Promis } try { - const apiBaseUrl = region ? apiBaseUrlForRegion(region) : await getApiBaseUrl(); + const apiBaseUrl = region ? `${getCalApiUrl(region)}/v2` : await getApiBaseUrl(); const response = await fetchWithTimeout(`${apiBaseUrl}/me`, { headers: { Authorization: `Bearer ${tokens.accessToken}`, From 23b7c2d912c036aa2c4b179e9f447e63da99b1c4 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Tue, 19 May 2026 20:13:08 +0000 Subject: [PATCH 7/7] ci(hostname-check): extend Cal hostname guard to scan apps/extension The CI grep previously scanned only apps/mobile, so the extension drifted without enforcement. After this change: - cd walks up to repo root (was: apps/mobile only) - rg scans both apps/mobile AND apps/extension - PATTERN broadens to \b(app\.)?cal\.(com|eu)\b so app.cal.eu also fails - FILE_ALLOWLIST is path-anchored (^path:) so future code lines can't bypass the check by mentioning a hostname; adds extension paths that must stay literal (lib/region.ts, wxt.config.ts, public/manifest.json) - CONTENT_ALLOWLIST exempts the companion.cal.com OAuth/iframe origin, which is not a Cal app URL Verified manually: planting https://cal.com/foo or https://app.cal.eu/foo in extension code makes the script exit 1; reverting brings it back to exit 0. --- apps/mobile/scripts/check-no-cal-hostnames.sh | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/apps/mobile/scripts/check-no-cal-hostnames.sh b/apps/mobile/scripts/check-no-cal-hostnames.sh index 6e8fa059..297ffbc9 100755 --- a/apps/mobile/scripts/check-no-cal-hostnames.sh +++ b/apps/mobile/scripts/check-no-cal-hostnames.sh @@ -1,35 +1,48 @@ #!/usr/bin/env bash -# Fail if any file under apps/mobile inlines a literal `cal.com` or `cal.eu` -# hostname in code. Region-aware construction must go through the helpers in -# `apps/mobile/utils/region.ts` so EU users get routed to `app.cal.eu` / -# `api.cal.eu` / `cal.eu`. +# Fail if any file under apps/mobile or apps/extension inlines a literal +# `cal.com` / `cal.eu` hostname in code. Region-aware construction must go +# through the helpers in: +# - apps/mobile/utils/region.ts (mobile + web build) +# - apps/extension/lib/region.ts (Chrome extension) # -# Scope: scans the entire `apps/mobile` tree. `rg` honors `.gitignore` so -# `node_modules/`, build artifacts, and other ignored paths are skipped -# automatically — no manual directory list to drift out of sync. +# Scope: scans both `apps/mobile` and `apps/extension`. `rg` honors +# `.gitignore` so build artifacts and `node_modules/` are skipped. # # Filters applied on top of the raw matches: -# - Word boundaries in `PATTERN` so incidental substrings like -# `group.com.cal.companion` (an iOS app-group id) are not flagged. -# - Lines whose content starts with `//`, `*`, or `/*` are filtered, since -# documentation prose / docstring @example URLs don't cause runtime bugs. -# - `ALLOWLIST` exempts files that legitimately reference the bare hostnames: -# the region helpers themselves, the env template, the hostname match-set -# used for video-call URL detection, and this script. +# - PATTERN uses word boundaries so incidental substrings like +# `group.com.cal.companion` (iOS app-group id) and `companion.cal.com` +# (extension OAuth/iframe origin) ARE captured by the pattern and then +# filtered out below — see CONTENT_ALLOWLIST. +# - FILE_ALLOWLIST exempts files that legitimately reference Cal hostnames: +# the region helpers, env templates, manifests / build configs that must +# stay static, and the hostname match-sets used for detection. +# - CONTENT_ALLOWLIST exempts the `companion.cal.com` token (the extension's +# OAuth landing / iframe origin — it matches the pattern but is not a Cal +# app URL). +# - Comment lines (starting with //, *, /*) are filtered. set -euo pipefail -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../../.." # repo root -PATTERN='\bcal\.(com|eu)\b' -ALLOWLIST='^(\./)?(utils/region\.ts|utils/booking\.ts|\.env\.example|scripts/check-no-cal-hostnames\.sh):' +PATTERN='\b(app\.)?cal\.(com|eu)\b' -raw=$(rg -n --no-heading "$PATTERN" . 2>/dev/null || true) -hits=$(echo "$raw" | grep -Ev "$ALLOWLIST" | grep -Ev '^[^:]+:[0-9]+:[[:space:]]*(//|\*|/\*)' || true) +FILE_ALLOWLIST='^(apps/mobile/utils/region\.ts|apps/mobile/utils/booking\.ts|apps/mobile/\.env\.example|apps/mobile/scripts/check-no-cal-hostnames\.sh|apps/extension/lib/region\.ts|apps/extension/\.env\.example|apps/extension/wxt\.config\.ts|apps/extension/public/manifest\.json):' + +# Match-anywhere allowlist for tokens that aren't Cal app URLs but happen to +# contain a substring matching PATTERN. +CONTENT_ALLOWLIST='companion\.cal\.com' + +raw=$(rg -n --no-heading "$PATTERN" apps/mobile apps/extension 2>/dev/null || true) +hits=$(echo "$raw" \ + | grep -Ev "$FILE_ALLOWLIST" \ + | grep -Ev "$CONTENT_ALLOWLIST" \ + | grep -Ev '^[^:]+:[0-9]+:[[:space:]]*(//|\*|/\*)' \ + || true) if [[ -n "$hits" ]]; then { - echo "Hardcoded Cal hostname found. Use helpers from utils/region.ts:" - echo " getCalAppUrl(), getCalApiUrl(), getCalWebUrl()" - echo " getCalSupportUrl(), getCalHelpUrl(slug)" + echo "Hardcoded Cal hostname found. Use region helpers:" + echo " apps/mobile/**: import from utils/region.ts" + echo " apps/extension/**: import from lib/region.ts" echo "" echo "$hits" } >&2