diff --git a/app.config.js b/app.config.js index a3feb907b..cda0a2290 100644 --- a/app.config.js +++ b/app.config.js @@ -1,35 +1,83 @@ -const SUFFIX = - {development: '.dev', production: '', test: '.test'}[ - process.env.APP_VARIANT - ] ?? ''; +export default ({config}) => { + const IDENTIFIER_SUFFIX = + {development: '.dev', production: '', test: '.test'}[ + process.env.APP_VARIANT + ] ?? ''; -const NAME = - {development: ' (DEV)', production: '', test: ' (TEST)'}[ - process.env.APP_VARIANT - ] ?? ''; + const NAME_SUFFIX = + {development: ' (DEV)', production: '', test: ' (TEST)'}[ + process.env.APP_VARIANT + ] ?? ''; -/** - * @param {object} opts - * @param {import('@expo/config-types').ExpoConfig} opts.config - * - * @returns {import('@expo/config-types').ExpoConfig} - */ -module.exports = ({config}) => ({ - ...config, - extra: { - ...config.extra, - eas: { - projectId: '2d5b8137-12ec-45aa-9c23-56b6a1c522b7', + return { + ...config, + jsEngine: 'hermes', + name: 'CoMapeo' + NAME_SUFFIX, + slug: 'comapeo', + version: '1.0.0', + orientation: 'portrait', + icon: './assets/icon.png', + userInterfaceStyle: 'light', + splash: { + image: './assets/splash.png', + resizeMode: 'cover', + backgroundColor: '#050F77', }, - }, - name: 'CoMapeo' + NAME, - ios: { - ...config.ios, - bundleIdentifier: 'com.comapeo' + SUFFIX, - }, - android: { - ...config.android, - package: 'com.comapeo' + SUFFIX, - googleServicesFile: process.env.GOOGLE_SERVICES_JSON, - }, -}); + assetBundlePatterns: ['**/*'], + plugins: [ + 'expo-localization', + 'expo-secure-store', + [ + '@rnmapbox/maps', + { + RNMapboxMapsImpl: 'mapbox', + RNMapboxMapsVersion: '11.1.0', + RNMapboxMapsDownloadToken: process.env.MAPBOX_DOWNLOAD_TOKEN, + }, + ], + [ + 'expo-location', + { + isIosBackgroundLocationEnabled: true, + isAndroidBackgroundLocationEnabled: true, + }, + ], + [ + 'expo-camera', + { + cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera', + microphonePermission: + 'Allow $(PRODUCT_NAME) to access your microphone', + recordAudioAndroid: true, + }, + ], + ['./expo-config-plugins/removeExpoInputStyles.js'], + ], + android: { + versionCode: 1, + adaptiveIcon: { + foregroundImage: './assets/icon.png', + backgroundColor: '#0033CC', + }, + package: 'com.comapeo' + IDENTIFIER_SUFFIX, + permissions: [ + 'android.permission.ACCESS_COARSE_LOCATION', + 'android.permission.ACCESS_FINE_LOCATION', + 'android.permission.ACCESS_BACKGROUND_LOCATION', + 'android.permission.FOREGROUND_SERVICE', + 'android.permission.FOREGROUND_SERVICE_LOCATION', + 'android.permission.CAMERA', + 'android.permission.RECORD_AUDIO', + ], + }, + ios: { + bundleIdentifier: 'com.comapeo' + IDENTIFIER_SUFFIX, + }, + extra: { + eas: { + projectId: '2d5b8137-12ec-45aa-9c23-56b6a1c522b7', + }, + }, + owner: 'digidem', + }; +}; diff --git a/app.json b/app.json deleted file mode 100644 index e1b45aabf..000000000 --- a/app.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "expo": { - "jsEngine": "hermes", - "name": "CoMapeo", - "slug": "comapeo", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "cover", - "backgroundColor": "#050F77" - }, - "assetBundlePatterns": ["**/*"], - "plugins": [ - "expo-localization", - "expo-secure-store", - [ - "@rnmapbox/maps", - { - "RNMapboxMapsImpl": "mapbox", - "RNMapboxMapsVersion": "11.1.0", - "RNMapboxMapsDownloadToken": "sk.eyJ1IjoiYnN0ZWZhbmN6eWsiLCJhIjoiY2x1dXV1NHNlMGU5dzJqcnh1Zno4YW4xcyJ9.P_LsVHh_HWTh25x5rRdrQg" - } - ], - [ - "expo-location", - { - "isIosBackgroundLocationEnabled": true, - "isAndroidBackgroundLocationEnabled": true - } - ], - [ - "expo-camera", - { - "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera", - "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone", - "recordAudioAndroid": true - } - ], - ["./expo-config-plugins/removeExpoInputStyles.js"] - ], - "android": { - "versionCode": 1, - "adaptiveIcon": { - "foregroundImage": "./assets/icon.png", - "backgroundColor": "#0033CC" - }, - "permissions": [ - "android.permission.ACCESS_COARSE_LOCATION", - "android.permission.ACCESS_FINE_LOCATION", - "android.permission.ACCESS_BACKGROUND_LOCATION", - "android.permission.FOREGROUND_SERVICE", - "android.permission.FOREGROUND_SERVICE_LOCATION", - "android.permission.CAMERA", - "android.permission.RECORD_AUDIO" - ], - "package": "com.comapeo" - }, - "ios": { - "bundleIdentifier": "com.comapeo" - }, - "extra": { - "eas": { - "projectId": "2d5b8137-12ec-45aa-9c23-56b6a1c522b7" - } - }, - "owner": "digidem" - } -} diff --git a/env.mjs b/env.mjs new file mode 100644 index 000000000..2e7f78f3f --- /dev/null +++ b/env.mjs @@ -0,0 +1,8 @@ +import {z} from 'zod'; + +export default z.object({ + MAPBOX_DOWNLOAD_TOKEN: z.string().min(1), + APP_VARIANT: z.enum(['development', 'test', 'production']), + FEATURE_TRACKS: z.string().optional(), +}); + diff --git a/package-lock.json b/package-lock.json index 1ca26d263..f517e111e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,7 @@ "uint8array-extras": "^0.5.0", "utm": "^1.1.1", "validate-color": "^2.2.4", + "zod": "^3.23.4", "zustand": "^4.4.6" }, "devDependencies": { @@ -96,7 +97,7 @@ "@types/geojson": "^7946.0.14", "@types/jest": "^29.5.12", "@types/lodash.isequal": "^4.5.6", - "@types/node": "^20.8.4", + "@types/node": "^20.12.7", "@types/react": "^18.2.58", "@types/react-native-indicators": "^0.16.2", "@types/react-native-vector-icons": "^6.4.8", @@ -2442,6 +2443,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@digidem/types": { "version": "2.2.0", "license": "ISC", @@ -8381,6 +8408,38 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/@turf/along": { "version": "6.5.0", "license": "MIT", @@ -8733,10 +8792,11 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.8.4", - "license": "MIT", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/prop-types": { @@ -9146,6 +9206,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -10980,6 +11051,14 @@ "ieee754": "^1.1.13" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/cross-fetch": { "version": "3.1.8", "license": "MIT", @@ -11551,6 +11630,17 @@ "diagnostic-channel": "*" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "dev": true, @@ -17635,6 +17725,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/make-fetch-happen": { "version": "13.0.0", "license": "ISC", @@ -18584,10 +18682,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/multi-core-indexer/node_modules/undici-types": { - "version": "5.26.5", - "license": "MIT" - }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -23470,6 +23564,59 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/tslib": { "version": "2.4.0", "license": "0BSD" @@ -23706,8 +23853,9 @@ } }, "node_modules/undici-types": { - "version": "5.25.3", - "license": "MIT" + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -24018,6 +24166,14 @@ "uuid": "bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "dev": true, @@ -24436,6 +24592,17 @@ "node": ">=16" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "license": "MIT", @@ -24453,6 +24620,14 @@ "b4a": "^1.5.3" } }, + "node_modules/zod": { + "version": "3.23.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.4.tgz", + "integrity": "sha512-/AtWOKbBgjzEYYQRNfoGKHObgfAZag6qUJX1VbHo2PRBgS+wfWagEY2mizjfyAPcGesrJOcx/wcl0L9WnVrHFw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zustand": { "version": "4.4.6", "license": "MIT", diff --git a/package.json b/package.json index 499d76683..40d338f20 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,11 @@ "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx --cache --ignore-path .gitignore", "lint:types": "tsc --noEmit", "lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:types", - "postinstall": "patch-package", + "postinstall": "patch-package && npm run build:autogenerated", "prepare": "husky install", "format": "prettier \"src/**/*.{js,ts,jsx,tsx}\" --write", "deep-clean": "./scripts/deep-clean.sh", + "build:autogenerated": "npm run build:translations && npm run build:intl-polyfills && npm run build:backend", "build:translations": "node ./scripts/build-translations.js ", "build:intl-polyfills": "node ./scripts/build-intl-polyfills.mjs", "build:backend": "node ./scripts/build-backend.mjs", @@ -93,6 +94,7 @@ "uint8array-extras": "^0.5.0", "utm": "^1.1.1", "validate-color": "^2.2.4", + "zod": "^3.23.4", "zustand": "^4.4.6" }, "devDependencies": { @@ -112,7 +114,7 @@ "@types/geojson": "^7946.0.14", "@types/jest": "^29.5.12", "@types/lodash.isequal": "^4.5.6", - "@types/node": "^20.8.4", + "@types/node": "^20.12.7", "@types/react": "^18.2.58", "@types/react-native-indicators": "^0.16.2", "@types/react-native-vector-icons": "^6.4.8", diff --git a/scripts/validate-env.mjs b/scripts/validate-env.mjs new file mode 100644 index 000000000..d6a007617 --- /dev/null +++ b/scripts/validate-env.mjs @@ -0,0 +1,38 @@ +import envSchema from '../env.mjs'; +import dotenv from 'dotenv'; + +dotenv.config(); + +function getZodSchemaFieldsShallow(schema) { + const fields = {}; + const proxy = new Proxy(fields, { + get(_, key) { + if (key === 'then' || typeof key !== 'string') { + return; + } + fields[key] = true; + }, + }); + schema.safeParse(proxy); + return fields; +} + +console.log('List of possible environment variables: ') +Object.keys(getZodSchemaFieldsShallow(envSchema)).forEach(key => console.log(` - ${key} (${!!process.env[key] ? "present" : "not present"})`)); +console.log('') +const result = envSchema.safeParse(process.env); + +const {error} = result; +if (error) { + console.error(''); + console.error( + 'Error while parsing environment variables:' + + Object.entries(error.flatten().fieldErrors).map(([k, v]) => { + return `\n - ${k}: ${v}`; + }), + ); + console.error(''); + + process.exit(1); +} +console.log("Environment variables validated successfully.") \ No newline at end of file diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index c5430dabf..2bcefadbd 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -28,9 +28,10 @@ const localDiscoveryController = createLocalDiscoveryController(mapeoApi); localDiscoveryController.start(); initializeNodejs(); -const App = () => { +export default function App() { const navRef = useNavigationContainerRef(); const [permissionsAsked, setPermissionsAsked] = React.useState(false); + React.useEffect(() => { PermissionsAndroid.requestMultiple([ 'android.permission.CAMERA', @@ -60,6 +61,4 @@ const App = () => { ); -}; - -export default App; +} diff --git a/src/frontend/contexts/IntlContext.tsx b/src/frontend/contexts/IntlContext.tsx index eb69781a8..9349e8c2d 100644 --- a/src/frontend/contexts/IntlContext.tsx +++ b/src/frontend/contexts/IntlContext.tsx @@ -90,6 +90,6 @@ a language name and translations in \`src/frontend/languages.json\``, if (supportedLanguages.find(lang => lang.locale === locale)) return locale as keyof typeof languages; const nonRegionalLocale = locale.split('-')[0]; - if (supportedLanguages.find(({locale}) => locale === nonRegionalLocale)) + if (supportedLanguages?.find(({locale}) => locale === nonRegionalLocale)) return nonRegionalLocale as keyof typeof languages; } diff --git a/tsconfig.json b/tsconfig.json index c8c89cc09..006d1506e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,10 @@ "extends": "@react-native/typescript-config/tsconfig.json", "compilerOptions": { "skipLibCheck": true, - "noUncheckedIndexedAccess": true + "noUncheckedIndexedAccess": true, + "types": [ + "node" + ], }, "include": ["src/frontend/**/*"], "exclude": [