diff --git a/.github/actions/build-and-upload-aselo-webchat-react-app/action.yml b/.github/actions/build-and-upload-aselo-webchat-react-app/action.yml index b2903f8aa8..03ea67106c 100644 --- a/.github/actions/build-and-upload-aselo-webchat-react-app/action.yml +++ b/.github/actions/build-and-upload-aselo-webchat-react-app/action.yml @@ -40,28 +40,6 @@ runs: aws-access-key-id: ${{ inputs.aws-access-key-id }} aws-secret-access-key: ${{ inputs.aws-secret-access-key }} aws-region: ${{ inputs.aws-region }} - # Get AWS parameters - # - name: Set Fullstory org id - # uses: "marvinpinto/action-inject-ssm-secrets@latest" - # with: - # ssm_parameter: "FULLSTORY_ID" - # env_variable_name: "FULLSTORY_ID" - - # - name: Create secret.js - # run: | - # touch ./src/private/secret.js - # working-directory: ./plugin-hrm-form - # shell: bash - # - name: Fill secret.js - # run: | - # cat <> ./src/private/secret.js - # export const datadogAccessToken = '$DATADOG_ACCESS_TOKEN'; - # export const datadogApplicationID = '$DATADOG_APP_ID'; - # export const fullStoryId = '$FULLSTORY_ID'; - # EOT - # working-directory: ./plugin-hrm-form - # shell: bash - # Build aselo-webchat-react-app - name: Run build command run: npm run build ## TODO: remove CI=false once lint is fixed @@ -73,28 +51,20 @@ runs: with: script: | core.setFailed('Bundle was not built!') - # Zip files individually + - name: Merge configs for helplines and copy to build + run: | + npm run mergeConfigs + mv mergedConfigs build/mergedConfigs + working-directory: ./aselo-webchat-react-app + shell: bash + # Zip files recursively # Cloudfront only zips files under 10000000 bytes on the fly, so we need to store these zipped - name: Zip files run: | - gzip -9 -v app.css - gzip -9 -v app.js - gzip -9 -v asset-manifest.json - gzip -9 -v index.html - mv app.css.gz app.css - mv app.js.gz app.js - mv asset-manifest.json.gz asset-manifest.json - mv index.html.gz index.html + gzip -r -9 . + find . -type f -name '*.gz' -exec sh -c 'mv -n "$1" "${1%.gz}"' _ {} \; working-directory: ./aselo-webchat-react-app/build shell: bash - - name: Zip js files - run: | - gzip -9 -v webchat.min.js - gzip -9 -v webchat.min.js.map - mv webchat.min.js.gz webchat.min.js - mv webchat.min.js.map.gz webchat.min.js.map - working-directory: ./aselo-webchat-react-app/build/static/js - shell: bash # Upload to S3 buckets - name: Upload files to Development S3 bucket uses: jakejarvis/s3-sync-action@master diff --git a/.github/actions/copy-aselo-webchat-react-app-to-release-location/action.yml b/.github/actions/copy-aselo-webchat-react-app-to-release-location/action.yml new file mode 100644 index 0000000000..1c53e74beb --- /dev/null +++ b/.github/actions/copy-aselo-webchat-react-app-to-release-location/action.yml @@ -0,0 +1,73 @@ +# Copyright (C) 2021-2024 Technology Matters +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see https://www.gnu.org/licenses/. + +name: copy-aselo-webchat-react-app-to-release-location +description: 'Copy aselo-webchat-react-app from the build location to the release location' +inputs: + aws-access-key-id: + description: 'AWS credentials for Aselo user' + required: true + aws-secret-access-key: + description: 'AWS credentials for Aselo user' + required: true + aws-region: + description: 'AWS credentials for Aselo user' + required: true + source-git-reference: + description: 'Identifies the git reference for target key' + required: true + source-git-reference-type: + description: 'Identifies if the git reference is a branch or tag' + required: true + target-git-tag: + description: 'Identifies the release tag for the plugin' + required: true +runs: + using: "composite" + steps: + # Setup credentials to access AWS for parameters + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ inputs.aws-access-key-id }} + aws-secret-access-key: ${{ inputs.aws-secret-access-key }} + aws-region: ${{ inputs.aws-region }} + + - name: Copy to Development S3 release folder + shell: bash + run: | + aws s3 cp --recursive 's3://assets-development.tl.techmatters.org/aselo-webchat-react-app/${{ inputs.source-git-reference-type }}/${{ inputs.source-git-reference }}/' 's3://assets-development.tl.techmatters.org/aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/' + + - name: Copy to Staging S3 release folder + shell: bash + run: | + aws s3 cp --recursive 's3://assets-staging.tl.techmatters.org/aselo-webchat-react-app/${{ inputs.source-git-reference-type }}/${{ inputs.source-git-reference }}/' 's3://assets-staging.tl.techmatters.org/aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/' + + - name: Copy to Production S3 release folder + shell: bash + run: | + aws s3 cp --recursive 's3://assets-production.tl.techmatters.org/aselo-webchat-react-app/${{ inputs.source-git-reference-type }}/${{ inputs.source-git-reference }}/' 's3://assets-production.tl.techmatters.org/aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/' + + - name: Clear cloudfront caches + shell: bash + run: | + CF_DISTRO=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{id:Id,origin:Origins.Items[0].DomainName}[?origin=='assets-development.tl.techmatters.org.s3-website.us-east-1.amazonaws.com'].id" --output text) + aws cloudfront create-invalidation --distribution-id $CF_DISTRO --paths /aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/* + + CF_DISTRO=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{id:Id,origin:Origins.Items[0].DomainName}[?origin=='assets-staging.tl.techmatters.org.s3-website.us-east-1.amazonaws.com'].id" --output text) + aws cloudfront create-invalidation --distribution-id $CF_DISTRO --paths /aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/* + + CF_DISTRO=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{id:Id,origin:Origins.Items[0].DomainName}[?origin=='assets-production.tl.techmatters.org.s3-website.us-east-1.amazonaws.com'].id" --output text) + aws cloudfront create-invalidation --distribution-id $CF_DISTRO --paths /aselo-webchat-react-app/tag/${{ inputs.target-git-tag }}/* + diff --git a/.github/workflows/aselo-webchat-react-app-deploy-environment.yml b/.github/workflows/aselo-webchat-react-app-deploy-environment.yml new file mode 100644 index 0000000000..87dbb613e8 --- /dev/null +++ b/.github/workflows/aselo-webchat-react-app-deploy-environment.yml @@ -0,0 +1,70 @@ +# Copyright (C) 2021-2023 Technology Matters +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see https://www.gnu.org/licenses/. + +name: Deploy Aselo Webchat React App - environment + +on: + workflow_dispatch: + inputs: + environment: + description: Environment to deploy. + default: development + required: true + type: choice + options: + - development + - staging + - production + +jobs: + configure: + name: Determine Configuration + runs-on: ubuntu-latest + outputs: + target_helplines: ${{ steps.determine-target-helplines.outputs.target_helplines }} + steps: + - uses: actions/checkout@v6 + - id: determine-target-helplines + shell: bash + run: | + helplines=$(node aselo-webchat-react-app/scripts/outputHelplinesForEnvironment.js '${{ inputs.environment }}') + echo "target_helplines=$helplines" >> $GITHUB_OUTPUT + deploy-helplines: + name: Deploy to multiple helplines + needs: configure + uses: ./.github/workflows/deploy-multiple-helplines.yml + secrets: inherit + with: + project: aselo-webchat-react-app + helplines: ${{ needs.configure.outputs.target_helplines }} + environments: '[ ${{ inputs.environment }} ]' + invalidate-cache: 'false' + + invalidate-cache: + name: Invalidate CloudFront cache for all production aselo-webchat-react-app files + needs: deploy-helplines + runs-on: ubuntu-latest + steps: + # Setup credentials to access AWS for parameters + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: "us-east-1" + - name: Clear cloudfront cache + run: | + CF_DISTRO=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{id:Id,origin:Origins.Items[0].DomainName}[?origin=='assets-${{ inputs.environment }}.tl.techmatters.org.s3-website.us-east-1.amazonaws.com'].id" --output text) + aws cloudfront create-invalidation --distribution-id $CF_DISTRO --paths /aselo-webchat-react-app/${{ steps.helpline_code.outputs.lowercase }}/* + diff --git a/.github/workflows/aselo-webchat-react-app-deploy.yml b/.github/workflows/aselo-webchat-react-app-deploy.yml index dfc9237a7e..3dbe57ea67 100644 --- a/.github/workflows/aselo-webchat-react-app-deploy.yml +++ b/.github/workflows/aselo-webchat-react-app-deploy.yml @@ -13,7 +13,7 @@ # along with this program. If not, see https://www.gnu.org/licenses/. # Upload Webchat to S3 bucket with Github Actions -name: Aselo Webchat React App Deploy +name: Deploy Aselo Webchat React App - single helpline on: workflow_dispatch: @@ -57,7 +57,131 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "build" - build: + deploy: # The type of runner that the job will run on runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Change String Case for Helpline Code + - name: Change String Case for Helpline Code + id: helpline_code + uses: "ASzc/change-string-case-action@v6" + with: + string: "${{ inputs.helpline_code }}" + # Set SHORT_ENVIRONMENT_CODE + - name: Production Environment + if: inputs.environment == 'production' + run: echo "SHORT_ENVIRONMENT_CODE=prod" >> $GITHUB_ENV + + - name: Staging Environment + if: inputs.environment == 'staging' + run: echo "SHORT_ENVIRONMENT_CODE=stg" >> $GITHUB_ENV + + - name: Development Environment + if: inputs.environment == 'development' + run: echo "SHORT_ENVIRONMENT_CODE=dev" >> $GITHUB_ENV + + # Setup credentials to access AWS for parameters + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: "us-east-1" + + # Get AWS parameters + - name: Set GITHUB_ACTIONS_SLACK_BOT_TOKEN + uses: "marvinpinto/action-inject-ssm-secrets@latest" + with: + ssm_parameter: "GITHUB_ACTIONS_SLACK_BOT_TOKEN" + env_variable_name: "GITHUB_ACTIONS_SLACK_BOT_TOKEN" + + - name: Set ASELO_DEPLOYS_CHANNEL_ID + uses: "marvinpinto/action-inject-ssm-secrets@latest" + with: + ssm_parameter: "ASELO_DEPLOYS_CHANNEL_ID" + env_variable_name: "ASELO_DEPLOYS_CHANNEL_ID" + + - name: Set ACCOUNT_SID + uses: "marvinpinto/action-inject-ssm-secrets@latest" + with: + ssm_parameter: "/${{ inputs.environment }}/twilio/${{ inputs.helpline_code }}/account_sid" + env_variable_name: "ACCOUNT_SID" + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v6 + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version: "22.x" + + - name: Create local working directory + run: mkdir -p ./aselo-webchat-react-app/temp + + - name: Download target build from S3 + run: | + aws s3 sync 's3://assets-${{ inputs.environment }}.tl.techmatters.org/aselo-webchat-react-app/${{ github.ref_type }}/${{ github.ref_name }}' './aselo-webchat-react-app/temp' --delete --no-progress + shell: bash + + - name: Copy target config and remove the rest + run: | + mv './mergedConfigs/${{ steps.helpline_code.outputs.lowercase }}/${{ inputs.environment }}.json' 'config.json' + rm -rf ./mergedConfigs + working-directory: aselo-webchat-react-app/temp + shell: bash + + # Upload the app to S3 corresponding buckets + - name: Upload Webchat to S3 bucket + uses: jakejarvis/s3-sync-action@master + with: + args: --acl public-read --follow-symlinks --exclude '*' --include '*.js' --include '*.js.map' --include '*.json' --include '*.css' --include 'index.html' --content-encoding 'gzip' + env: + AWS_S3_BUCKET: tl-public-chat-${{ steps.helpline_code.outputs.lowercase }}-$SHORT_ENVIRONMENT_CODE + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: "${{ secrets.AWS_DEFAULT_REGION }}" + SOURCE_DIR: "aselo-webchat-react-app/temp" + DEST_DIR: "aselo-webchat-react-app" + - name: Upload Webchat to S3 bucket + uses: jakejarvis/s3-sync-action@master + with: + args: --acl public-read --follow-symlinks --exclude '*' --include '*.js' --include '*.js.map' --include '*.json' --include '*.css' --include 'index.html' --content-encoding 'gzip' + env: + AWS_S3_BUCKET: assets-${{ inputs.environment }}.tl.techmatters.org + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: "${{ secrets.AWS_DEFAULT_REGION }}" + SOURCE_DIR: "aselo-webchat-react-app/temp" + DEST_DIR: "aselo-webchat-react-app/${{ steps.helpline_code.outputs.lowercase }}" + + - name: Clear cloudfront cache + if: ${{ inputs.invalidate-cache != 'false' }} + run: | + CF_DISTRO=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{id:Id,origin:Origins.Items[0].DomainName}[?origin=='assets-${{ inputs.environment }}.tl.techmatters.org.s3-website.us-east-1.amazonaws.com'].id" --output text) + aws cloudfront create-invalidation --distribution-id $CF_DISTRO --paths /aselo-webchat-react-app/${{ steps.helpline_code.outputs.lowercase }}/* + + # Send Slack notifying success + - name: Slack Aselo channel + id: slack + uses: slackapi/slack-github-action@v2.1.1 + if: ${{ inputs.send-slack-message != 'false' }} + with: + method: chat.postMessage + token: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} + payload: | + channel: ${{ env.ASELO_DEPLOYS_CHANNEL_ID }} + text: '`[WEBCHAT]` Deployment to `${{inputs.helpline_code}}-${{inputs.environment}}` of ${{ github.ref_type }} `${{ github.ref_name }}` requested by `${{ github.triggering_actor }}` completed using workflow `${{ github.workflow }}` with SHA ${{ github.sha }} :rocket:.' + # Update deployment matrix + - name: Update deployment matrix + uses: ./.github/actions/deployment-matrix + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + identifier: ${{ inputs.helpline_code }} + environment: ${{ inputs.environment}} + service_repo: 'aselo-webchat-react-app' + version_tag: ${{ github.ref_name }} + diff --git a/.github/workflows/deploy-multiple-helplines.yml b/.github/workflows/deploy-multiple-helplines.yml index dc2af43df4..4c485f9705 100644 --- a/.github/workflows/deploy-multiple-helplines.yml +++ b/.github/workflows/deploy-multiple-helplines.yml @@ -82,6 +82,22 @@ jobs: send-slack-message: ${{ inputs.send_slack_message_per_deploy }} invalidate-cache: ${{ inputs.invalidate-cache }} + deploy-aselo-webchat-react-app-helplines: + if: inputs.project == 'aselo-webchat-react-app' + strategy: + fail-fast: false + matrix: + short_helplines: ${{ fromJson(inputs.helplines) }} + environment: ${{ fromJson(inputs.environments) }} + uses: ./.github/workflows/aselo-webchat-react-app-deploy.yml + secrets: inherit + with: + helpline_code: ${{ matrix.short_helplines }} + environment: ${{ matrix.environment }} + recaptcha_verify_url: ${{ inputs.recaptcha_verify_url }} + send-slack-message: ${{ inputs.send_slack_message_per_deploy }} + invalidate-cache: ${{ inputs.invalidate-cache }} + # Send Slack notifying success send-slack-message: # The type of runner that the job will run on diff --git a/.github/workflows/global-production-release-tag.yml b/.github/workflows/global-production-release-tag.yml index 9c46e3636e..6a0642abba 100644 --- a/.github/workflows/global-production-release-tag.yml +++ b/.github/workflows/global-production-release-tag.yml @@ -57,6 +57,16 @@ jobs: source-git-reference: ${{ github.ref_name }} target-git-tag: ${{ inputs.tag-name }} + - name: Copy aselo webchat react app to S3 release folders + uses: ./.github/actions/copy-aselo-webchat-react-app-to-release-location + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + source-git-reference-type: ${{ github.ref_type }} + source-git-reference: ${{ github.ref_name }} + target-git-tag: ${{ steps.create_pre_release.outputs.generated-pre-release-tag }} + tag-images: needs: generate-release runs-on: ubuntu-latest @@ -128,4 +138,4 @@ jobs: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} slack-message: "`[Flex]` Release from ${{ github.ref_type }} `${{ github.ref_name }}` requested by `${{ github.triggering_actor }}` completed with SHA ${{ github.sha }}. Release tag is `${{ needs.generate-release.outputs.qa-version }}` :rocket:." env: - SLACK_BOT_TOKEN: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} \ No newline at end of file + SLACK_BOT_TOKEN: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} diff --git a/.github/workflows/global-qa-release-tag.yml b/.github/workflows/global-qa-release-tag.yml index 5a2d810dd4..786b24d520 100644 --- a/.github/workflows/global-qa-release-tag.yml +++ b/.github/workflows/global-qa-release-tag.yml @@ -99,6 +99,16 @@ jobs: source-git-reference: ${{ github.ref_name }} target-git-tag: ${{ steps.create_pre_release.outputs.generated-pre-release-tag }} + - name: Copy aselo webchat react app to S3 release folders + uses: ./.github/actions/copy-aselo-webchat-react-app-to-release-location + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + source-git-reference-type: ${{ github.ref_type }} + source-git-reference: ${{ github.ref_name }} + target-git-tag: ${{ steps.create_pre_release.outputs.generated-pre-release-tag }} + tag-images: needs: generate-pre-release runs-on: ubuntu-latest @@ -170,4 +180,4 @@ jobs: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} slack-message: "`[Flex]` Release from ${{ github.ref_type }} `${{ github.ref_name }}` requested by `${{ github.triggering_actor }}` completed with SHA ${{ github.sha }}. Release tag is `${{ needs.generate-pre-release.outputs.qa-version }}` :rocket:." env: - SLACK_BOT_TOKEN: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} \ No newline at end of file + SLACK_BOT_TOKEN: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} diff --git a/aselo-webchat-react-app/.gitignore b/aselo-webchat-react-app/.gitignore index ffdf34ebc2..5ebfb6a067 100644 --- a/aselo-webchat-react-app/.gitignore +++ b/aselo-webchat-react-app/.gitignore @@ -15,3 +15,4 @@ dist artifacts coverage src/coverage +mergedConfigs diff --git a/aselo-webchat-react-app/configSrc/as/staging.json b/aselo-webchat-react-app/configSrc/as/staging.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/aselo-webchat-react-app/configSrc/as/staging.json @@ -0,0 +1 @@ +{} diff --git a/aselo-webchat-react-app/scripts/mergeConfigs.js b/aselo-webchat-react-app/scripts/mergeConfigs.js index d013c6833f..1eb56e403e 100644 --- a/aselo-webchat-react-app/scripts/mergeConfigs.js +++ b/aselo-webchat-react-app/scripts/mergeConfigs.js @@ -14,68 +14,107 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -const fs = require("fs/promises"); -const merge = require("lodash.merge"); +const fs = require('fs/promises'); +const merge = require('lodash.merge'); const DEFAULT_LANGUAGE = 'en'; const generateMergedConfigs = async (environment, helplineCode) => { - const defaults = JSON.parse(await fs.readFile('./configSrc/defaults.json', { encoding: 'utf8' })); - const defaultTranslations = JSON.parse(await fs.readFile(`./translationsSrc/${DEFAULT_LANGUAGE}.json`, { encoding: 'utf8' })); - const helplineCodes = []; - if (helplineCode) { - helplineCodes.push(helplineCode); + const defaults = JSON.parse(await fs.readFile('./configSrc/defaults.json', { encoding: 'utf8' })); + const defaultTranslations = JSON.parse( + await fs.readFile(`./translationsSrc/${DEFAULT_LANGUAGE}.json`, { encoding: 'utf8' }), + ); + + // Build an array of the target helplines. If not specified, use the fs structure, where a folder represents a helpline + const helplineCodes = []; + if (helplineCode) { + helplineCodes.push(helplineCode); + } else { + const contents = await fs.readdir('./configSrc/', { recursive: false, withFileTypes: true }); + const directories = contents.filter(ent => ent.isDirectory()).map(({ name }) => name); + helplineCodes.push(...directories); + } + + for (const shortCode of helplineCodes) { + const helplineCommon = JSON.parse(await fs.readFile(`./configSrc/${shortCode}/common.json`, { encoding: 'utf8' })); + + // Build an array of the target environments. If not specified, use the fs structure, where all the .json files other than "common" represent an environment + const environments = []; + if (environment) { + environments.push(environment); } else { - const contents = await fs.readdir('./configSrc/', { recursive: false, withFileTypes: true }); - const directories = contents.filter((ent) => ent.isDirectory()).map(({ name }) => name); - helplineCodes.push(...directories); + const contents = await fs.readdir(`./configSrc/${shortCode}/`, { recursive: false, withFileTypes: true }); + const envs = contents + .filter(ent => !ent.isDirectory()) + .filter(ent => ent.isFile() && ent.name.endsWith('.json') && ent.name !== 'common.json') + .map(({ name }) => name.slice(0, -5)); + environments.push(...envs); } - for (const shortCode of helplineCodes) { - const helplineCommon = JSON.parse(await fs.readFile(`./configSrc/${shortCode}/common.json`, { encoding: 'utf8' })); - const environmentSpecific = JSON.parse(await fs.readFile(`./configSrc/${shortCode}/${environment}.json`, { encoding: 'utf8' })); - const mergedConfig = merge(defaults, helplineCommon, environmentSpecific); - // Assume the languages required by the helpline based on the translation files that exist in the helplines 'translations' directory under their entry in configSrc - // e.g. ./configSrc/as/translations/en-US.json - // Should a helpline need to support a language but require no helpline specific translations, add a file with an empty json object in it - const contents = await fs.readdir(`./configSrc/${shortCode}/translations`, { recursive: false, withFileTypes: true }); - const mergedTranslations = {}; - const helplineTranslationFilenames = contents.filter((ent) => ent.isFile() && ent.name.endsWith('.json')).map(({ name }) => name) - for (const translationFilename of helplineTranslationFilenames) { - const helplineTranslations = JSON.parse(await fs.readFile(`./configSrc/${shortCode}/translations/${translationFilename}`, { encoding: 'utf8' })); - let localeTranslations = {}; - const localeTranslationsPath = `./translationsSrc/${translationFilename}` - try { - localeTranslations = JSON.parse(await fs.readFile(localeTranslationsPath, { encoding: 'utf8' })); - } catch (err) { - if (err.code === 'ENOENT') { - console.info(`No locale translations at ${localeTranslationsPath}, skipping`); - } else { - console.info(`Failed to read locale translations at ${localeTranslationsPath}, assuming there are none`, err); - } - } - let languageTranslations = {}; - const [localeName] = translationFilename.split('.'); - const [languageName] = localeName.split('-'); - const languageTranslationsPath = `./translationsSrc/${languageName}.json`; - try { - languageTranslations = JSON.parse(await fs.readFile(languageTranslationsPath, { encoding: 'utf8' })); - } catch (err) { - if (err.code === 'ENOENT') { - console.info(`No language translations at ${languageTranslationsPath}, skipping`); - } else { - console.info(`Failed to read language translations at ${languageTranslationsPath}, assuming there are none`, err); - } + // Assume the languages required by the helpline based on the translation files that exist in the helplines 'translations' directory under their entry in configSrc + // e.g. ./configSrc/as/translations/en-US.json + // Should a helpline need to support a language but require no helpline specific translations, add a file with an empty json object in it + const contents = await fs.readdir(`./configSrc/${shortCode}/translations`, { + recursive: false, + withFileTypes: true, + }); - } - mergedTranslations[translationFilename.split('.')[0]] = merge(defaultTranslations, languageTranslations, localeTranslations, helplineTranslations); + const mergedTranslations = {}; + const helplineTranslationFilenames = contents + .filter(ent => ent.isFile() && ent.name.endsWith('.json')) + .map(({ name }) => name); + for (const translationFilename of helplineTranslationFilenames) { + const helplineTranslations = JSON.parse( + await fs.readFile(`./configSrc/${shortCode}/translations/${translationFilename}`, { encoding: 'utf8' }), + ); + let localeTranslations = {}; + const localeTranslationsPath = `./translationsSrc/${translationFilename}`; + try { + localeTranslations = JSON.parse(await fs.readFile(localeTranslationsPath, { encoding: 'utf8' })); + } catch (err) { + if (err.code === 'ENOENT') { + console.info(`No locale translations at ${localeTranslationsPath}, skipping`); + } else { + console.info(`Failed to read locale translations at ${localeTranslationsPath}, assuming there are none`, err); + } + } + let languageTranslations = {}; + const [localeName] = translationFilename.split('.'); + const [languageName] = localeName.split('-'); + const languageTranslationsPath = `./translationsSrc/${languageName}.json`; + try { + languageTranslations = JSON.parse(await fs.readFile(languageTranslationsPath, { encoding: 'utf8' })); + } catch (err) { + if (err.code === 'ENOENT') { + console.info(`No language translations at ${languageTranslationsPath}, skipping`); + } else { + console.info( + `Failed to read language translations at ${languageTranslationsPath}, assuming there are none`, + err, + ); } - mergedConfig.translations = mergedTranslations; - await fs.mkdir(`./mergedConfigs/${shortCode}`, { recursive: true }); - await fs.writeFile(`./mergedConfigs/${shortCode}/${environment}.json`, JSON.stringify(mergedConfig, null, 2)) + } + mergedTranslations[translationFilename.split('.')[0]] = merge( + defaultTranslations, + languageTranslations, + localeTranslations, + helplineTranslations, + ); + } + + for (const env of environments) { + const environmentSpecific = JSON.parse( + await fs.readFile(`./configSrc/${shortCode}/${env}.json`, { encoding: 'utf8' }), + ); + + const mergedConfig = merge(defaults, helplineCommon, environmentSpecific, { translations: mergedTranslations }); + await fs.mkdir(`./mergedConfigs/${shortCode}`, { recursive: true }); + await fs.writeFile(`./mergedConfigs/${shortCode}/${env}.json`, JSON.stringify(mergedConfig, null, 2)); + console.info(`Merged configs generated for ${shortCode}/${env}`); } -} + } +}; generateMergedConfigs(process.argv[2], process.argv[3]).then( - () => console.info(`Merged configs generated for ${process.argv[2]}.`), - (err) => console.error(`Error generating merged configs for ${process.argv[2]}.`, err) -); \ No newline at end of file + () => console.info(`Merged configs completed.`), + err => console.error(`Error generating merged configs for ${process.argv[2]} ${process.argv[3]}.`, err), +); diff --git a/aselo-webchat-react-app/scripts/outputHelplinesForEnvironment.js b/aselo-webchat-react-app/scripts/outputHelplinesForEnvironment.js new file mode 100644 index 0000000000..4ee57c5cbc --- /dev/null +++ b/aselo-webchat-react-app/scripts/outputHelplinesForEnvironment.js @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2021-2026 Technology Matters + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +import { readdirSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const CONFIGURATIONS_PATH = join(__dirname, '../configSrc'); + +const main = environment => { + const helplineCodes = []; + const contents = readdirSync(CONFIGURATIONS_PATH, { recursive: false, withFileTypes: true }); + const directories = contents.filter(ent => ent.isDirectory()).map(({ name }) => name); + helplineCodes.push(...directories); + + const targetHelplines = []; + for (const shortCode of helplineCodes) { + const contents = readdirSync(`${CONFIGURATIONS_PATH}/${shortCode}`, { recursive: false, withFileTypes: true }); + const isTargetHelpline = contents.some(ent => ent.isFile() && ent.name === `${environment}.json`); + if (isTargetHelpline) { + targetHelplines.push(shortCode.toUpperCase()); + } + } + + console.log(JSON.stringify(targetHelplines)); +}; + +main(process.argv[2]);