diff --git a/.eslintrc.js b/.eslintrc.js index e75b18ad..1611b8a7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -73,6 +73,12 @@ module.exports = { 'global-require': 0, 'import/no-dynamic-require': 0 } + }, + { + files: ['./bin/*'], + rules: { + 'no-console': 0 + } } ] } diff --git a/.github/workflows/app-deploy.yml b/.github/workflows/app-deploy.yml index b9a95883..d4540225 100644 --- a/.github/workflows/app-deploy.yml +++ b/.github/workflows/app-deploy.yml @@ -77,10 +77,10 @@ jobs: run: | set -o pipefail - # Find the config associated to the firebase project in .firebaserc gitBranch=${GITHUB_REF##*/} gcloudProject=$(cat .firebaserc | jq -r --arg alias "$gitBranch" '.projects[$alias]') + echo "GCLOUD_PROJECT=$gcloudProject" >> $GITHUB_ENV # Set other app configs (settings within .firebaserc in the ci.setEnv section) config=$(cat .firebaserc | jq -r --arg alias "$gitBranch" '.ci.setEnv[$alias]') @@ -91,26 +91,21 @@ jobs: echo "REACT_APP_ALGOLIA_APP_ID=$(echo $config | jq -r '.REACT_APP_ALGOLIA_APP_ID')" >> $GITHUB_ENV echo "REACT_APP_ALGOLIA_API_KEY=$(echo $config | jq -r '.REACT_APP_ALGOLIA_API_KEY')" >> $GITHUB_ENV - echo Exporting Firebase SDK Config for $gcloudProject project... - - # Use firebase SDK API to get the app's configuration (databaseURL is removed since it is set to the emulator URL above) - firebaseConfig=$($(yarn bin)/firebase --project $gcloudProject apps:sdkconfig WEB \ - $($(yarn bin)/firebase --project $gcloudProject apps:list WEB | grep fireadmin | awk '{ print $4}') | \ - tr '\n' ' ' | \ - sed 's/.*initializeApp(//g' | \ - sed 's/);//g' | \ - jq -r 'to_entries[] | [.key, (.value | tojson)] | join("=")' | \ - sed 's/"//1' | \ - sed 's/:"/:/g; s/^/echo \"REACT_APP_FIREBASE_/g' \ - ) - - echo Begin evaluating project config to export as environment variables: - - # Loop through each line of config and evaluate to export env vars - while IFS= read -r line; do - echo Evaluating: $line - eval $line >> $GITHUB_ENV - done <<< "$firebaseConfig" + - name: Generate Firebase SDK config for ${{ env.GCLOUD_PROJECT }} + env: + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + run: | + set -o pipefail + + # Throw a clear error if FIREBASE_TOKEN secret is not set + if [ -z "$FIREBASE_TOKEN" ]; + then + missingTokenErrMsg="\"FIREBASE_TOKEN\" github secret is required to load project configuration. Visit https://github.com/${{ github.repository }}/settings/secrets to set." + echo "::error ::$missingTokenErrMsg" + exit 1 + fi + + bin/generate-firebase-sdk-config.js --outputEnv >> .env - name: Verify App run: | diff --git a/.github/workflows/app-verify.yml b/.github/workflows/app-verify.yml index 5e5d913e..951cda4f 100644 --- a/.github/workflows/app-verify.yml +++ b/.github/workflows/app-verify.yml @@ -73,6 +73,7 @@ jobs: # Find the config associated to the firebase project in .firebaserc gcloudProject=$(cat .firebaserc | jq -r --arg alias "$gitBranch" '.projects[$alias] // .projects.default') + echo "GCLOUD_PROJECT=$gcloudProject" >> $GITHUB_ENV # Set other app configs (settings within .firebaserc in the ci.setEnv section) config=$(cat .firebaserc | jq -r --arg alias "$gitBranch" '.ci.setEnv[$alias] // .ci.setEnv.master') @@ -83,30 +84,27 @@ jobs: echo "REACT_APP_ALGOLIA_APP_ID=$(echo $config | jq -r '.REACT_APP_ALGOLIA_APP_ID')" >> $GITHUB_ENV echo "REACT_APP_ALGOLIA_API_KEY=$(echo $config | jq -r '.REACT_APP_ALGOLIA_API_KEY')" >> $GITHUB_ENV - echo Exporting Firebase SDK Config for $gcloudProject project... - - # Use firebase SDK API to get the app's configuration (databaseURL is removed since it is set to the emulator URL) - firebaseConfig=$($(yarn bin)/firebase --project $gcloudProject apps:sdkconfig WEB \ - $($(yarn bin)/firebase --project $gcloudProject apps:list WEB | grep fireadmin | awk '{ print $4}') | \ - tr '\n' ' ' | \ - sed 's/.*initializeApp(//g' | \ - sed 's/);//g' | \ - jq -r 'del(.databaseURL) | to_entries[] | [.key, (.value | tojson)] | join("=")' | \ - sed 's/"//1' | \ - sed 's/:"/:/g; s/^/echo \"REACT_APP_FIREBASE_/g' \ - ) - # Set emulator settings echo "REACT_APP_FIREBASE_DATABASE_EMULATOR_HOST=localhost:$(cat firebase.json | jq .emulators.database.port)" >> $GITHUB_ENV echo "REACT_APP_FIRESTORE_EMULATOR_HOST=localhost:$(cat firebase.json | jq .emulators.firestore.port)" >> $GITHUB_ENV - echo Begin evaluating project config to export as environment variables: + - name: Generate Firebase SDK config for ${{ env.GCLOUD_PROJECT }} + # Skip for forks (since they don't have access to secrets) + if: github.event.pull_request.head.repo.full_name == github.repository + env: + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + run: | + set -o pipefail + + # Throw a clear error if FIREBASE_TOKEN secret is not set + if [ -z "$FIREBASE_TOKEN" ]; + then + missingTokenErrMsg="\"FIREBASE_TOKEN\" github secret is required to load project configuration. Visit https://github.com/${{ github.repository }}/settings/secrets to set." + echo "::error ::$missingTokenErrMsg" + exit 1 + fi - # Loop through each line of config and evaluate to export env vars - while IFS= read -r line; do - echo Evaluating: $line - eval $line >> $GITHUB_ENV - done <<< "$firebaseConfig" + bin/generate-firebase-sdk-config.js --outputEnv >> .env - name: Print CI Env Variables run: | diff --git a/bin/generate-firebase-sdk-config.js b/bin/generate-firebase-sdk-config.js new file mode 100755 index 00000000..4a2d406f --- /dev/null +++ b/bin/generate-firebase-sdk-config.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +const fsp = require('fs').promises +const firebase = require('firebase-tools') +const argv = require('minimist')(process.argv.slice(2)) + +const project = process.env.GCLOUD_PROJECT + +if (!project) { + console.error('Missing GCLOUD_PROJECT environment variable.') + process.exit(1) +} + +async function getFirebaseConfig() { + const apps = await firebase.apps.list('WEB', { + project + }) + // NOTE: find is used because displayName is not a supported config option in firebase-tools + const { appId, platform } = argv.appName + ? apps.find((appConfig) => appConfig.displayName === argv.appName) || {} + : apps[0] + + if (!appId) { + throw new Error('No app found matching the provided app name') + } + + const { sdkConfig } = await firebase.apps.sdkconfig(platform, appId) + return sdkConfig +} + +async function run() { + const sdkConfig = await getFirebaseConfig() + const content = JSON.stringify({ firebase: sdkConfig }, null, 2) + + if (argv.file) { + await fsp.writeFile(argv.file, content) + console.log(`Successfully written Firebase SDK config to ${argv.file}`) + } + if (argv.outputEnv) { + const envString = Object.entries(sdkConfig) + .map(([key, val]) => `REACT_APP_FIREBASE_${key}=${val}`) + .join('\n') + console.log(`\n${envString}`) + } else { + console.log(content) + } +} + +run().catch((err) => { + console.error(err) + process.exit(1) +})