diff --git a/frontend/app.json b/frontend/app.json index 56b31ed..4b9a56d 100644 --- a/frontend/app.json +++ b/frontend/app.json @@ -122,7 +122,8 @@ "microphonePermission": "Allow Keepsafe to access your microphone." } ], - "expo-background-task" + "expo-background-task", + "expo-localization" ], "experiments": { "typedRoutes": true diff --git a/frontend/app/capture/details.tsx b/frontend/app/capture/details.tsx index f8f5dcb..88edd7a 100644 --- a/frontend/app/capture/details.tsx +++ b/frontend/app/capture/details.tsx @@ -10,6 +10,7 @@ import { useUserEntries } from '@/hooks/use-user-entries'; import { usePrivacySettings } from '@/hooks/use-privacy-settings'; import { PrivacySettings } from '@/types/privacy'; import { MediaCapture } from '@/types/media'; +import { posthog } from '@/constants/posthog'; import { moderateScale, verticalScale } from 'react-native-size-matters'; import * as Crypto from 'expo-crypto'; @@ -215,6 +216,16 @@ export default function DetailsScreen() { }); if (result.success) { + try { + posthog.capture('entry_captured', { + type: capture.type, + is_private: isPrivate, + is_everyone: isEveryone, + friends_count: selectedFriends.length + }); + } catch (error) { + if (__DEV__) console.warn('Analytics capture failed:', error); + } toast(result.message, 'success'); setTimeout(() => { router.push('/capture'); diff --git a/frontend/bun.lock b/frontend/bun.lock index 6adfd84..97dad3c 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -21,6 +21,7 @@ "axios": "^1.12.2", "date-fns": "^4.1.0", "expo": "54", + "expo-application": "~7.0.8", "expo-audio": "~1.0.13", "expo-av": "~16.0.7", "expo-background-task": "~1.0.8", @@ -31,15 +32,16 @@ "expo-contacts": "~15.0.8", "expo-crypto": "~15.0.7", "expo-dev-client": "~6.0.12", - "expo-device": "^8.0.8", + "expo-device": "~8.0.10", "expo-document-picker": "~14.0.7", - "expo-file-system": "~19.0.12", + "expo-file-system": "~19.0.21", "expo-font": "~14.0.8", "expo-haptics": "~15.0.7", "expo-image": "~3.0.8", "expo-image-picker": "~17.0.8", "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.8", + "expo-localization": "~17.0.8", "expo-location": "~19.0.7", "expo-notifications": "~0.32.11", "expo-router": "~6.0.1", @@ -53,6 +55,7 @@ "expo-video": "~3.0.11", "expo-web-browser": "~15.0.7", "lucide-react-native": "^0.475.0", + "posthog-react-native": "^4.17.0", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.4", @@ -407,6 +410,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@posthog/core": ["@posthog/core@1.9.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-j7KSWxJTUtNyKynLt/p0hfip/3I46dWU2dk+pt7dKRoz2l5CYueHuHK4EO7Wlgno5yo1HO4sc4s30MXMTICHJw=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], @@ -955,7 +960,7 @@ "expo": ["expo@54.0.2", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.2", "@expo/config": "~12.0.8", "@expo/config-plugins": "~54.0.1", "@expo/devtools": "0.1.7", "@expo/fingerprint": "0.15.0", "@expo/metro": "~0.1.1", "@expo/metro-config": "54.0.2", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.0", "expo-asset": "~12.0.8", "expo-constants": "~18.0.8", "expo-file-system": "~19.0.12", "expo-font": "~14.0.8", "expo-keep-awake": "~15.0.7", "expo-modules-autolinking": "3.0.10", "expo-modules-core": "3.0.15", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-0YcsvMuiZlVFVZRdD4PlhwsYrTmcN1qm5b1IRSLIeLZjH6ZnQwBb3KBSnK8WRC9V6EUPSbuLGpNT5AEewrtakQ=="], - "expo-application": ["expo-application@7.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-Jt1/qqnoDUbZ+bK91+dHaZ1vrPDtRBOltRa681EeedkisqguuEeUx4UHqwVyDK2oHWsK6lO3ojetoA4h8OmNcg=="], + "expo-application": ["expo-application@7.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q=="], "expo-asset": ["expo-asset@12.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-jj2U8zw9+7orST2rlQGULYiqPoECOuUyffs2NguGrq84bYbkM041T7TOMXH2raPVJnM9lEAP54ezI6XL+GVYqw=="], @@ -985,13 +990,13 @@ "expo-dev-menu-interface": ["expo-dev-menu-interface@2.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw=="], - "expo-device": ["expo-device@8.0.8", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-t515WOkeVgIeO3izj+FoXodKTHiSxZ2uF5E9YvCwiR4kANAjvyjFP3vSls2Utjx5ss8y652pZTgh3tOYQmwuZA=="], + "expo-device": ["expo-device@8.0.10", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA=="], "expo-document-picker": ["expo-document-picker@14.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-81Jh8RDD0GYBUoSTmIBq30hXXjmkDV1ZY2BNIp1+3HR5PDSh2WmdhD/Ezz5YFsv46hIXHsQc+Kh1q8vn6OLT9Q=="], "expo-eas-client": ["expo-eas-client@1.0.8", "", {}, "sha512-5or11NJhSeDoHHI6zyvQDW2cz/yFyE+1Cz8NTs5NK8JzC7J0JrkUgptWtxyfB6Xs/21YRNifd3qgbBN3hfKVgA=="], - "expo-file-system": ["expo-file-system@19.0.12", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ=="], + "expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="], "expo-font": ["expo-font@14.0.8", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-bTUHaJWRZ7ywP8dg3f+wfOwv6RwMV3mWT2CDUIhsK70GjNGlCtiWOCoHsA5Od/esPaVxqc37cCBvQGQRFStRlA=="], @@ -1011,6 +1016,8 @@ "expo-linking": ["expo-linking@8.0.8", "", { "dependencies": { "expo-constants": "~18.0.8", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg=="], + "expo-localization": ["expo-localization@17.0.8", "", { "dependencies": { "rtl-detect": "^1.0.2" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-UrdwklZBDJ+t+ZszMMiE0SXZ2eJxcquCuQcl6EvGHM9K+e6YqKVRQ+w8qE+iIB3H75v2RJy6MHAaLK+Mqeo04g=="], + "expo-location": ["expo-location@19.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-YNkh4r9E6ECbPkBCAMG5A5yHDgS0pw+Rzyd0l2ZQlCtjkhlODB55nMCKr5CZnUI0mXTkaSm8CwfoCO8n2MpYfg=="], "expo-manifests": ["expo-manifests@1.0.8", "", { "dependencies": { "@expo/config": "~12.0.8", "expo-json-utils": "~0.15.0" }, "peerDependencies": { "expo": "*" } }, "sha512-nA5PwU2uiUd+2nkDWf9e71AuFAtbrb330g/ecvuu52bmaXtN8J8oiilc9BDvAX0gg2fbtOaZdEdjBYopt1jdlQ=="], @@ -1539,6 +1546,8 @@ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "posthog-react-native": ["posthog-react-native@4.17.0", "", { "dependencies": { "@posthog/core": "1.9.0" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.0.0", "@react-navigation/native": ">= 5.0.0", "expo-application": ">= 4.0.0", "expo-device": ">= 4.0.0", "expo-file-system": ">= 13.0.0", "expo-localization": ">= 11.0.0", "posthog-react-native-session-replay": ">= 1.2.0", "react-native-device-info": ">= 10.0.0", "react-native-localize": ">= 3.0.0", "react-native-navigation": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-svg": ">= 15.0.0" }, "optionalPeers": ["@react-native-async-storage/async-storage", "@react-navigation/native", "expo-application", "expo-device", "expo-file-system", "expo-localization", "posthog-react-native-session-replay", "react-native-device-info", "react-native-localize", "react-native-navigation", "react-native-safe-area-context"] }, "sha512-9NLhqNjI7JymjeZ8Nkf8sZi1bejBK+tzgxaI50b+b+bm7MRAmA7AnB3KR15u6G5f1OAu05ewy/qOe8Q2MgJwzA=="], + "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], "pretty-format": ["pretty-format@30.0.5", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw=="], @@ -1683,6 +1692,8 @@ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "rtl-detect": ["rtl-detect@1.1.2", "", {}, "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], @@ -2137,6 +2148,8 @@ "expo/@expo/config": ["@expo/config@12.0.8", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~54.0.0", "@expo/config-types": "^54.0.7", "@expo/json-file": "^10.0.7", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-yFadXa5Cmja57EVOSyEYV1hF7kCaSbPnd1twx0MfvTr1Yj2abIbrEu2MUZqcvElNQOtgADnLRP0YJiuEdgoO5A=="], + "expo/expo-file-system": ["expo-file-system@19.0.12", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ=="], + "expo/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], "expo-constants/@expo/config": ["@expo/config@12.0.8", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~54.0.0", "@expo/config-types": "^54.0.7", "@expo/json-file": "^10.0.7", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-yFadXa5Cmja57EVOSyEYV1hF7kCaSbPnd1twx0MfvTr1Yj2abIbrEu2MUZqcvElNQOtgADnLRP0YJiuEdgoO5A=="], @@ -2147,6 +2160,8 @@ "expo-modules-autolinking/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "expo-notifications/expo-application": ["expo-application@7.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-Jt1/qqnoDUbZ+bK91+dHaZ1vrPDtRBOltRa681EeedkisqguuEeUx4UHqwVyDK2oHWsK6lO3ojetoA4h8OmNcg=="], + "expo-router/@expo/metro-runtime": ["@expo/metro-runtime@6.1.1", "", { "dependencies": { "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-H5ZFj7nisMJ5a4joMGpF4Xt/m4hWDAroMNv5ld/2iniWoXLvNt+YQpMdyecu/lHpydKAjHzXcyE08hTGgURaIA=="], "expo-router/semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], diff --git a/frontend/constants/posthog.ts b/frontend/constants/posthog.ts new file mode 100644 index 0000000..8d830a7 --- /dev/null +++ b/frontend/constants/posthog.ts @@ -0,0 +1,26 @@ +import { PostHog } from 'posthog-react-native'; + +const apiKey = process.env.EXPO_PUBLIC_POSTHOG_API_KEY; +const host = process.env.EXPO_PUBLIC_POSTHOG_HOST; + +if (!apiKey || !host) { + if (__DEV__) { + console.warn('PostHog environment variables not configured. Analytics will be disabled.'); + } +} + +export const posthog = apiKey && host + ? new PostHog(apiKey, { host }) + : { + // Provide a no-op mock for when PostHog is not configured + capture: (_event: string, _properties?: object) => Promise.resolve(), + identify: (_distinctId: string, _properties?: object) => Promise.resolve(), + reset: () => Promise.resolve(), + screen: (_screenName: string, _properties?: object) => Promise.resolve(), + group: (_groupType: string, _groupKey: string, _properties?: object) => Promise.resolve(), + alias: (_alias: string) => Promise.resolve(), + reloadFeatureFlags: () => {}, + isFeatureEnabled: () => false, + getFeatureFlag: () => null, + getFeatureFlagPayload: () => null, + } as unknown as PostHog; \ No newline at end of file diff --git a/frontend/hooks/use-friends.ts b/frontend/hooks/use-friends.ts index 46ef600..0bc7609 100644 --- a/frontend/hooks/use-friends.ts +++ b/frontend/hooks/use-friends.ts @@ -6,6 +6,7 @@ import { Database } from '@/types/database'; import { deviceStorage } from '@/services/device-storage'; import { FriendService } from '@/services/friend-service'; import { useAuthContext } from '@/providers/auth-provider'; +import { posthog } from '@/constants/posthog'; import { FriendWithProfile } from '@/types/friends'; @@ -161,6 +162,12 @@ export function useFriends(userId?: string): UseFriendsResult { status: FRIENDSHIP_STATUS.BLOCKED, blocked_by: userId }); + try { + // Omitting friendship_id for privacy compliance + posthog.capture('friend_blocked', {}); + } catch (error) { + if (__DEV__) console.warn('Analytics capture failed:', error); + } return { success: true }; } catch (error) { return { diff --git a/frontend/hooks/use-invite-acceptance.ts b/frontend/hooks/use-invite-acceptance.ts index b42fbe4..8c7f828 100644 --- a/frontend/hooks/use-invite-acceptance.ts +++ b/frontend/hooks/use-invite-acceptance.ts @@ -2,6 +2,7 @@ import { useState, useCallback } from 'react'; import { useMutation, useQuery } from '@tanstack/react-query'; import { supabase } from '@/lib/supabase'; import { TABLES, FRIENDSHIP_STATUS } from '@/constants/supabase'; +import { posthog } from '@/constants/posthog'; export interface InviteData { id: string; @@ -130,6 +131,13 @@ export function useInviteAcceptance(inviteId?: string): UseInviteAcceptanceResul userId: userId }); + if (inviteeId && userId) { + posthog.capture('invite_accepted', { + inviter_id: inviteeId, + invitee_id: userId + }); + } + return { success: true, message: 'Invitation accepted successfully!', diff --git a/frontend/package.json b/frontend/package.json index 29f0be6..f4ddd3c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "axios": "^1.12.2", "date-fns": "^4.1.0", "expo": "54", + "expo-application": "~7.0.8", "expo-audio": "~1.0.13", "expo-av": "~16.0.7", "expo-background-task": "~1.0.8", @@ -45,15 +46,16 @@ "expo-contacts": "~15.0.8", "expo-crypto": "~15.0.7", "expo-dev-client": "~6.0.12", - "expo-device": "^8.0.8", + "expo-device": "~8.0.10", "expo-document-picker": "~14.0.7", - "expo-file-system": "~19.0.12", + "expo-file-system": "~19.0.21", "expo-font": "~14.0.8", "expo-haptics": "~15.0.7", "expo-image": "~3.0.8", "expo-image-picker": "~17.0.8", "expo-linear-gradient": "~15.0.7", "expo-linking": "~8.0.8", + "expo-localization": "~17.0.8", "expo-location": "~19.0.7", "expo-notifications": "~0.32.11", "expo-router": "~6.0.1", @@ -67,6 +69,7 @@ "expo-video": "~3.0.11", "expo-web-browser": "~15.0.7", "lucide-react-native": "^0.475.0", + "posthog-react-native": "^4.17.0", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.4", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 39efc9f..b3be46f 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -16,9 +16,5 @@ "expo-env.d.ts", "nativewind-env.d.ts" ], - "exclude": [ - "node_modules", - "**/*.flow.js", - "**/*.flow" - ] + "exclude": ["node_modules", "**/*.flow.js", "**/*.flow"] }