{"id":18110990,"url":"https://github.com/simongolms/ods-quickstarter-fe-ionic-react-vite-playwright","last_synced_at":"2025-04-14T01:22:05.460Z","repository":{"id":37417961,"uuid":"497924343","full_name":"SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright","owner":"SimonGolms","description":"An advanced OpenDevStack Frontend Quickstarter to build mobile and desktop apps with the ionic framework, react, vite and playwright","archived":false,"fork":false,"pushed_at":"2023-04-22T07:03:19.000Z","size":1781,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T15:21:22.902Z","etag":null,"topics":["azure","capacitor","docker","frontend","helm","ionic","jenkins","nginx","opendevstack","openshift","playwright","quickstarter","react","semantic-release","sso","typescript","vite"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SimonGolms.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-30T12:01:04.000Z","updated_at":"2024-10-25T03:52:02.000Z","dependencies_parsed_at":"2024-11-01T00:12:46.831Z","dependency_job_id":"11e0dc13-16fd-4c8f-a5f4-1a9cfa319e3a","html_url":"https://github.com/SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonGolms%2Fods-quickstarter-fe-ionic-react-vite-playwright","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonGolms%2Fods-quickstarter-fe-ionic-react-vite-playwright/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonGolms%2Fods-quickstarter-fe-ionic-react-vite-playwright/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonGolms%2Fods-quickstarter-fe-ionic-react-vite-playwright/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SimonGolms","download_url":"https://codeload.github.com/SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248805443,"owners_count":21164332,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure","capacitor","docker","frontend","helm","ionic","jenkins","nginx","opendevstack","openshift","playwright","quickstarter","react","semantic-release","sso","typescript","vite"],"created_at":"2024-11-01T00:12:41.129Z","updated_at":"2025-04-14T01:22:05.429Z","avatar_url":"https://github.com/SimonGolms.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenDevStack - Quickstarter - Frontend Ionic React Vite Playwright\n\n\u003e An advanced OpenDevStack Frontend Quickstarter to build mobile and desktop apps with the ionic framework, react, vite and playwright.\n\n![Version](https://img.shields.io/badge/version-3.0.0-blue.svg?style=for-the-badge\u0026cacheSeconds=2592000)\n[![License: Apache-2.0](https://img.shields.io/github/license/simongolms/ods-quickstarter-fe-ionic-react-vite-playwright?style=for-the-badge)](https://github.com/simongolms/ods-quickstarter-fe-ionic-react-vite-playwright/blob/master/LICENSE)\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge)](https://github.com/simongolms/ods-quickstarter-fe-ionic-react-vite-playwright/graphs/commit-activity)\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg?style=for-the-badge)](https://conventionalcommits.org)\n![Prerequisite Npm](https://img.shields.io/badge/npm-%3E%3D9-blue.svg?style=for-the-badge)\n![Prerequisite Node](https://img.shields.io/badge/node-%3E%3D18-blue.svg?style=for-the-badge)\n\n## Features ✨\n\n- Ionic/React with Typescript for building cross-platform native and web app\n- Single-Sign-On (SSO) for user authentication and authorization with Azure Active Directory\n- OpenDevStack (ODS) CI/CD configuration out of the box with the basic setup for Docker (incl. injecting Runtime Variables), Jenkins (incl. feature environments, release-manager rollout) and OpenShift (managed with Helm)\n  - Ionic Appflow (coming soon)\n- Setup for Vite, Playwright, ESlint, Stylelint, Prettier, commitlint, Husky (git hooks) and semantic versioning for a better developer experience\n\n---\n\n\u003c!-- Feel free to delete the section 'Provision Quickstarter' after you successfully provisioned the quickstarter --\u003e\n\n## Provision Quickstarter 🚀\n\nFor an official ODS Quickstarter, the provisioning app takes care of the provisioning in combination with the Jenkinsfile in the associated Quickstarter template.\nHowever, since this is an extended Quickstarter, which is developed independently and decoupled from ODS, the necessary steps of the provisioning app and Jenkins itself must be performed, which are covered in this section.\n\n### Prerequisites\n\nTo provision this Quickstarter, you need a deployed ODS project with the corresponding `*-cd`, `*-dev` and `*-test` projects in the OpenShift 4 dev cluster, the `*-prod` project in the OpenShift 4 prod cluster and the associated Bitbucket project.\n\n### Setup Quickstarter\n\n#### 1. Get Source Code\n\n- Option 1 (recommended): Clone the repository\n\n  ```sh\n  git clone https://github.com/SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright.git\n  cd ods-quickstarter-fe-ionic-react-vite-playwright\n  ```\n\n- Option 2: Download the repository\n\n  ```sh\n  curl --location --remote-name https://github.com/SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright/archive/refs/heads/main.tar.gz \u0026\u0026 \\\n  tar -xvzf main.tar.gz \u0026\u0026 \\\n  rm main.tar.gz\n  cd ods-quickstarter-fe-ionic-react-vite-playwright-main\n  ```\n\n#### 2. Set Project Id\n\nTo make the Quickstarter available in your project, the corresponding project id is required. With the following command all files are checked. The placeholder `PROJECTID` is searched and replaced by the actual project id.\n\nReplace `YOUR_PROJECT_ID` with your project id, e.g. `foo`\n\n```sh\n# IMPORTANT: Keep your project id in lowercase.\nfind . -type f -exec sed --expression 's/PROJECTID/YOUR_PROJECT_ID/g' --in-place {} +\n```\n\n🛑 **IMPORTANT:** This and the other commands also replace the placeholders in the other sections of the documentation. It is therefore recommended to continue with the README in the downloaded source code. Otherwise, please be aware that you have to replace the placeholder `PROJECTID` with your project id for each further command.\n\n#### 3. Set Component Id\n\nReplace `YOUR_COMPONENT_ID` with your component id, e.g. `app`, `frontend`, etc.\n\n```sh\n# IMPORTANT: Keep your component id in lowercase.\nfind . -type f -exec sed --expression 's/COMPONENTID/YOUR_COMPONENT_ID/g' --in-place {} +\n```\n\n#### 4. Set OpenShift dev domain URLs\n\nReplace `YOUR_OPENSHIFT_DOMAIN_DEV` with your OpenShift 4 Dev Cluster Domain, e.g. `dev.ocp.company.com`\n\n\u003cdetails\u003e\u003csummary\u003eHow do I find out the \u003ccode\u003eYOUR_OPENSHIFT_DOMAIN_DEV\u003c/code\u003e value?\u003c/summary\u003e\n\nGo to your `*-cd` project of your OpenShift 4 Dev Cluster in the browser and look at the URL properly. Extract the `YOUR_OPENSHIFT_DOMAIN_DEV` as follows:\n\n```txt\nhttps://console-openshift-console.apps.dev.ocp.company.com/topology/ns/PROJECTID-cd ◄── Your url\n\nhttps://console-openshift-console.apps.   dev.ocp.company.com   /topology/ns/PROJECTID-cd\n                  ▲                                 ▲                     ▲\n                  │                                 │                     └─── Pathname\n                  │                                 └── YOUR_OPENSHIFT_DOMAIN_DEV\n                  └─ Application Sub Domain\n```\n\n\u003c/details\u003e\n\n```sh\nfind . -type f -exec sed --expression 's/OPENSHIFT_DOMAIN_DEV/YOUR_OPENSHIFT_DOMAIN_DEV/g' --in-place {} +\n```\n\n#### 5. Set OpenShift prod domain URLs\n\nReplace `YOUR_OPENSHIFT_DOMAIN_PROD` with your OpenShift 4 Dev Cluster Domain, e.g. `prod.ocp.company.com`\n\n\u003cdetails\u003e\u003csummary\u003eHow do I find out the \u003ccode\u003eYOUR_OPENSHIFT_DOMAIN_PROD\u003c/code\u003e value?\u003c/summary\u003e\n\nGo to your `*-prod` project of your OpenShift 4 Dev Cluster in the browser and look at the URL properly. Extract the `YOUR_OPENSHIFT_DOMAIN_PROD` as follows:\n\n```txt\nhttps://console-openshift-console.apps.dev.ocp.company.com/topology/ns/PROJECTID-cd ◄── Your url\n\nhttps://console-openshift-console.apps.   dev.ocp.company.com   /topology/ns/PROJECTID-cd\n                  ▲                                 ▲                     ▲\n                  │                                 │                     └─── Pathname\n                  │                                 └── YOUR_OPENSHIFT_DOMAIN_PROD\n                  └─ Application Sub Domain\n```\n\n\u003c/details\u003e\n\n```sh\nfind . -type f -exec sed --expression 's/OPENSHIFT_DOMAIN_PROD/YOUR_OPENSHIFT_DOMAIN_PROD/g' --in-place {} +\n```\n\n#### 6. Set BitBucket domain URL\n\nReplace `YOUR_BITBUCKET_DOMAIN` with your BitBucket Domain, e.g. `bitbucket.company.com`\n\n\u003cdetails\u003e\u003csummary\u003eHow do I find out the \u003ccode\u003eYOUR_BITBUCKET_DOMAIN\u003c/code\u003e value?\u003c/summary\u003e\n\nGo to your BitBucket project in the browser and look at the URL properly. Extract the `YOUR_BITBUCKET_DOMAIN` as follows:\n\n```txt\nhttps://bitbucket.company.com/projects/PROJECTID ◄── Your url\n\nhttps://   bitbucket.company.com   /projects/PROJECTID\n  ▲                ▲                     ▲\n  │                │                     └─── Pathname\n  │                └── YOUR_BITBUCKET_DOMAIN\n  └─ Protocol\n```\n\n\u003c/details\u003e\n\n```sh\nfind . -type f -exec sed --expression 's/BITBUCKET_DOMAIN/YOUR_BITBUCKET_DOMAIN/g' --in-place {} +\n```\n\n#### 7. Remove template resources\n\n```sh\nrm -rf .git .github CHANGELOG.md\n```\n\n#### 8. Install `helm` cli\n\n[Helm](https://helm.sh/) is a package manager for Kubernetes that configures and deploys applications and services on a Kubernetes/OpenShift cluster. Think of it like `apt`/`yum`/`homebrew` for Kubernetes. It uses Helm charts to simplify the development and deployment process.\n\n`helm` will be used later in the [`pre-commit`](./.husky/pre-commit) git hook for linting the application [Helm charts](./chart/).\n\n```sh\n# Install helm cli\ncurl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash\n\n# Verify successfully installed helm version\nhelm version\n```\n\nMore Information: \u003chttps://helm.sh/docs/intro/install/\u003e\n\n#### 9. Install `oc` cli\n\nWith the OpenShift command-line interface (CLI), the `oc` command, you can create applications and manage OpenShift Container Platform projects from a terminal.\n\n`oc` will be used by helm and for further configuration and housekeeping tasks.\n\nTo be compatible with the latest OpenShift Container Platform (OCP) version, the binary is downloaded and installed from the associated OpenShift Container Platform.\n\n```sh\n# Install oc cli\ncurl -O https://downloads-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/amd64/linux/oc.tar \u0026\u0026 \\\ntar -xvf oc.tar \u0026\u0026 rm oc.tar \u0026\u0026 \\\nsudo mv oc /usr/local/bin/\n\n# Verify successfully installed oc version\noc version\n```\n\nMore Information: \u003chttps://docs.openshift.com/container-platform/4.9/cli_reference/openshift_cli/getting-started-cli.html\u003e\n\n#### 10. Setup Bitbucket Code Repository\n\n1. Create BitBucket Repository\n\n   - Replace `USER@COMPANY.COM` with an authorized (administrative) user with access to the Bitbucket project\n\n   ```sh\n   # For security reasons (e.g. terminal history) --user 'USERNAME:PASSWORD' should be avoided.\n   # Instead, a prompt will show up for the password if --user 'USERNAME' is used!\n   curl --data '{\"defaultBranch\":\"master\",\"description\":\"📱 Repo of COMPONENTID from PROJECTID that is build with ionic and react\",\"name\":\"PROJECTID-COMPONENTID\"}' \\\n     --header \"Content-Type: application/json\" \\\n     --request POST \\\n     --url https://BITBUCKET_DOMAIN/rest/api/1.0/projects/PROJECTID/repos/ \\\n     --user USER@COMPANY.COM\n   ```\n\n2. Get trigger secret from the webhook proxy\n\n   ```sh\n   # Login to OpenShift dev instance\n   oc login --server=https://api.OPENSHIFT_DOMAIN_DEV:6443 --token=123...456\n\n   # Get trigger secret 'webhook-proxy' in plaintext\n   oc get secret webhook-proxy --namespace PROJECTID-cd --output jsonpath='{.data.trigger-secret}' | base64 -d | xargs\n   ```\n\n3. Create Webhook\n\n   - Replace `TRIGGER_SECRET` with the obtained trigger secret from previous step.\n   - Replace `USER@COMPANY.COM` with an authorized (administrative) user with access to the Bitbucket project\n\n   ```sh\n   # For security reasons (e.g. terminal history) --user 'USERNAME:PASSWORD' should be avoided.\n   # Instead, a prompt will show up for the password if --user 'USERNAME' is used!\n   curl --data '{\"active\":true,\"configuration\":{},\"events\":[\"pr:merged\",\"repo:refs_changed\",\"pr:declined\",\"pr:deleted\"],\"name\":\"Jenkins\",\"url\":\"https://webhook-proxy-PROJECTID-cd.apps.OPENSHIFT_DOMAIN_DEV?trigger_secret=TRIGGER_SECRET\"}' \\\n     --header \"Content-Type: application/json\" \\\n     --request POST \\\n     --url https://BITBUCKET_DOMAIN/rest/api/1.0/projects/PROJECTID/repos/PROJECTID-COMPONENTID/webhooks \\\n     --user USER@COMPANY.COM\n   ```\n\n4. Publish to Bitbucket Code Repository\n\n   ```sh\n   # Requires git v2.31.1\n   git init --initial-branch=master\n   git add --all\n   git commit -m \"chore: initial version\"\n   git remote add origin https://BITBUCKET_DOMAIN/scm/PROJECTID/PROJECTID-COMPONENTID.git\n   # Before you push your first commit, make sure that no credentials are in the README as a result of the previous steps.\n   # You might also delete unnecessary content in this context, like the 'Provision Quickstarter' section of this README.\n   git push -u origin HEAD:master\n   ```\n\n### Verify successfully provision\n\nIf the provisioning was successful, the previous push of the first commit should have triggered the first build process in Jenkins in the meantime, which can be viewed under the following link: \u003chttps://jenkins-PROJECTID-cd.apps.OPENSHIFT_DOMAIN_DEV/job/PROJECTID-cd/job/PROJECTID-cd-COMPONENTID-master/\u003e\n\n#### Feature Environment\n\nA new feature environment is created by the associated git branch name, e.g. `feature/next`.\n\n```sh\n# Creates and switch to new branch from the current branch\ngit checkout -b feature/next\n\n# Add empty commit in case the previous commit includes '[skip ci]'\ngit commit -m \"chore: create feature-next environment\" --allow-empty\n\n# Push the new branch to the remote repository\ngit push -u origin feature/next\n```\n\nA new Jenkins build should have been created and can be followed under the following link: \u003chttps://jenkins-PROJECTID-cd.apps.OPENSHIFT_DOMAIN_DEV/job/PROJECTID-cd/job/PROJECTID-cd-COMPONENTID-feature-next/\u003e\n\nAssuming the Jenkins build has been successfully completed, the application should have been created in the OpenShift 4 project [`PROJECTID-dev`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/topology/ns/PROJECTID-dev) as a new [`HelmRelease`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/helm-releases/ns/PROJECTID-dev/release/PROJECTID-COMPONENTID-feature-next) resource and should be accessible under the following link: \u003chttps://PROJECTID-COMPONENTID-feature-next.apps.OPENSHIFT_DOMAIN_DEV\u003e\n\n#### Release to `dev`, `test` and `prod`\n\n1. Update your `metadata.yml` in your release manager repository\n\n   ```yaml\n   # Example metadata.yml\n   id: PROJECTID\n   name: Project PROJECTID\n   description: Description of PROJECTID\n\n   environments:\n     prod:\n       apiUrl: api.OPENSHIFT_DOMAIN_PROD:6443\n       credentialsId: PROJECTID-cd-PROJECTID-prod\n\n   repositories:\n     - id: COMPONENTID\n       branch: master\n       url: https://bitbucket.biscrum.com/scm/PROJECTID/PROJECTID-COMPONENTID.git\n\n   services:\n     bitbucket:\n       credentials:\n         id: PROJECTID-cd-cd-user-with-password\n     # jira:\n     #   credentials:\n     #     id: PROJECTID-cd-cd-user-with-password\n     nexus:\n       repository:\n         name: leva-documentation\n   ```\n\n2. Go to the Jenkins build of the release manager and start a new build process in the `dev` environment. Assuming the release has been successfully completed, the application should have been created in the OpenShift 4 project [`PROJECTID-dev`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/topology/ns/PROJECTID-dev) as a new [`HelmRelease`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/helm-releases/ns/PROJECTID-dev/release/COMPONENTID) resource and should be accessible under the following link: \u003chttps://PROJECTID-COMPONENTID-dev.apps.OPENSHIFT_DOMAIN_DEV\u003e\n\n3. Before you can deploy a release into `qa`/`test` environment, you need to merge the release branch into your master branch to pass the checks in the Jenkins shared library stage [`odsOrchestrationPipeline`](https://github.com/opendevstack/ods-jenkins-shared-library/blob/4.x/vars/odsOrchestrationPipeline.groovy), see comment in [`metadata.yml`](./metadata.yml) for more details:\n\n   ```sh\n   # Switch to master branch\n   git checkout master\n\n   # Merge the remote release branch into master without opening a text editor and accept the auto-generated message\n   git merge origin/release/\u003cVERSION\u003e --no-edit\n\n   # Push the changes to the remote repository\n   git push\n   ```\n\n4. Go to the Jenkins build of the release manager and start a new build process in the `qa`/`test` environment. Assuming the release has been successfully completed, the application should have been created in the OpenShift 4 project [`PROJECTID-test`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/topology/ns/PROJECTID-test) as a new [`HelmRelease`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_DEV/helm-releases/ns/PROJECTID-test/release/COMPONENTID) resource and should be accessible under the following link: \u003chttps://PROJECTID-COMPONENTID-test.apps.OPENSHIFT_DOMAIN_DEV\u003e\n\n5. Go to the Jenkins build of the release manager and start a new build process in the `prod` environment. Assuming the release has been successfully completed, the application should have been created in the OpenShift 4 project [`PROJECTID-prod`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_PROD/topology/ns/PROJECTID-prod) as a new [`HelmRelease`](https://console-openshift-console.apps.OPENSHIFT_DOMAIN_PROD/helm-releases/ns/PROJECTID-prod/release/COMPONENTID) resource and should be accessible under the following link: \u003chttps://PROJECTID-COMPONENTID.apps.OPENSHIFT_DOMAIN_DEV\u003e\n\n---\n\n## Technology Stack 💻\n\n### Programming Language\n\n[![TypeScript](https://img.shields.io/badge/typescript-3178C6.svg?style=for-the-badge\u0026logo=typescript\u0026logoColor=white)](https://www.typescriptlang.org/)\n[![JavaScript](https://img.shields.io/badge/javascript-F7DF1E.svg?style=for-the-badge\u0026logo=javascript\u0026logoColor=black)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)\n[![HTML](https://img.shields.io/badge/html5-E34F26.svg?style=for-the-badge\u0026logo=html5\u0026logoColor=white)](https://developer.mozilla.org/en-US/docs/Web/HTML)\n[![CSS3](https://img.shields.io/badge/css3-1572B6.svg?style=for-the-badge\u0026logo=css3\u0026logoColor=white)](https://developer.mozilla.org/en-US/docs/Web/CSS)\n[![npm](https://img.shields.io/badge/npm-CB3837.svg?style=for-the-badge\u0026logo=npm\u0026logoColor=white)](https://www.npmjs.com/)\n[![Node.js](https://img.shields.io/badge/node.js-339933.svg?style=for-the-badge\u0026logo=nodedotjs\u0026logoColor=white)](https://nodejs.org/)\n[![Markdown](https://img.shields.io/badge/markdown-000000.svg?style=for-the-badge\u0026logo=markdown\u0026logoColor=white)](https://daringfireball.net/projects/markdown/)\n\n### Frameworks and libraries\n\n[![Ionic](https://img.shields.io/badge/ionic-3880FF.svg?style=for-the-badge\u0026logo=ionic\u0026logoColor=white)](https://ionicframework.com/)\n[![React](https://img.shields.io/badge/react-20232a.svg?style=for-the-badge\u0026logo=react\u0026logoColor=61DAFB)](https://reactjs.org/)\n[![React Router](https://img.shields.io/badge/React%20Router-CA4245.svg?style=for-the-badge\u0026logo=react-router\u0026logoColor=white)](https://reactjs.org/)\n[![Redux Toolkit](https://img.shields.io/badge/Redux%20Toolkit-593d88.svg?style=for-the-badge\u0026logo=redux\u0026logoColor=white)](https://redux-toolkit.js.org/)\n[![RTK Query](https://img.shields.io/badge/RTK%20Query-593d88.svg?style=for-the-badge\u0026logo=redux\u0026logoColor=white)](https://redux-toolkit.js.org/rtk-query/overview)\n[![Azure](https://img.shields.io/badge/Azure%20SSO-0078D4.svg?style=for-the-badge\u0026logo=microsoft-azure\u0026logoColor=white)](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react)\n[![msal-react](https://img.shields.io/badge/MSAL%20React-5E5E5E.svg?style=for-the-badge\u0026logo=microsoft\u0026logoColor=white)](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react)\n\n**Testing**:\n\n[![Playwright](https://img.shields.io/badge/playwright-2EAD33.svg?style=for-the-badge\u0026logo=playwright\u0026logoColor=white)](https://playwright.dev/)\n\n**Tracking**:\n\nN/A\n\n**Linter**:\n\n[![ESLint](https://img.shields.io/badge/ESlint-4B32C3.svg?style=for-the-badge\u0026logo=eslint\u0026logoColor=white)](https://eslint.org/)\n[![Stylelint](https://img.shields.io/badge/stylelint-263238.svg?style=for-the-badge\u0026logo=stylelint\u0026logoColor=white)](https://stylelint.io/)\n[![Prettier](https://img.shields.io/badge/Prettier-F7B93E.svg?style=for-the-badge\u0026logo=prettier\u0026logoColor=black)](https://prettier.io/)\n[![Commitlint](https://img.shields.io/badge/commitlint-121212.svg?style=for-the-badge\u0026logo=commitlint\u0026logoColor=white)](https://commitlint.js.org/)\n\n**Compiler**:\n\n[![vitejs](https://img.shields.io/badge/vite-646CFF.svg?style=for-the-badge\u0026logo=vite\u0026logoColor=white)](https://vitejs.dev/)\n\n### CI/CD\n\n[![OpenDevStack](https://img.shields.io/badge/OpenDevStack-222.svg?style=for-the-badge\u0026logoColor=white)](https://www.opendevstack.org/)\n[![Jenkins](https://img.shields.io/badge/Jenkins-D24939.svg?style=for-the-badge\u0026logo=jenkins\u0026logoColor=white)](https://nginx.org/)\n[![Helm](https://img.shields.io/badge/Helm-0F1689.svg?style=for-the-badge\u0026logo=helm\u0026logoColor=white)](https://www.opendevstack.org/)\n[![Semantic Release](https://img.shields.io/badge/Semantic%20Release-494949.svg?style=for-the-badge\u0026logo=semantic-release\u0026logoColor=white)](https://semantic-release.gitbook.io/semantic-release/)\n[![Husky](https://img.shields.io/badge/%F0%9F%90%B6%20Husky-42b983.svg?style=for-the-badge)](https://typicode.github.io/husky/#/)\n\n### Hosting\n\n[![RedHat OpenShift v4.11](https://img.shields.io/badge/RedHat%20OpenShift-EE0000.svg?style=for-the-badge\u0026logo=red-hat-open-shift\u0026logoColor=white)](https://docs.openshift.com/container-platform/4.10/welcome/index.html)\n[![Docker](https://img.shields.io/badge/Docker-2496ED.svg?style=for-the-badge\u0026logo=docker\u0026logoColor=white)](https://docs.docker.com/)\n[![Nginx](https://img.shields.io/badge/Nginx-009639.svg?style=for-the-badge\u0026logo=nginx\u0026logoColor=white)](https://nginx.org/)\n\n### Dev Environment\n\n[![WSL2](https://img.shields.io/badge/Windows%20Subsystem%20for%20Linux%20%28WSL2%29-4D4D4D.svg?style=for-the-badge\u0026logo=windows-terminal\u0026logoColor=white)](https://docs.microsoft.com/en-us/windows/wsl/)\n\n**IDEs/Editors**:\n\n[![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge\u0026logo=visual-studio-code\u0026logoColor=white)](https://code.visualstudio.com/)\n\n### Version Control\n\n[![Git](https://img.shields.io/badge/git-F05033.svg?style=for-the-badge\u0026logo=git\u0026logoColor=white)](https://git-scm.com/doc)\n[![Bitbucket](https://img.shields.io/badge/bitbucket-0047B3.svg?style=for-the-badge\u0026logo=bitbucket\u0026logoColor=white)](https://bitbucket.org/)\n\n### Social\n\n[![MS Teams](https://img.shields.io/badge/microsoft%20teams-6264a7.svg?style=for-the-badge\u0026logo=microsoft-teams\u0026logoColor=white)](https://www.microsoft.com/en-us/microsoft-teams/)\n\n---\n\n## Prerequisites ☝️\n\n1. **Azure App Registration**\n\n   Make sure you have an existing Azure App registry that has a single-page application (SPA) redirect to `http://localhost/`.\n\n   Ideally you have one Azure App registry per environment (`dev`/`test`/`prod`) with at least the following SPA redirects:\n\n   - Dev: `http://localhost/`, `https://PROJECTID-COMPONENTID-dev.apps.OPENSHIFT_DOMAIN_DEV`\n   - Test: `http://localhost/`, `https://PROJECTID-COMPONENTID-test.apps.OPENSHIFT_DOMAIN_DEV`\n   - Prod: `http://localhost/`, `https://PROJECTID-COMPONENTID.apps.OPENSHIFT_DOMAIN_PROD`\n\n   Update the following entries with the `Application (client) ID` and `Directory (tenant) ID` from the corresponding app registry environment\n\n   1. Replace `YOUR_CLIENT_ID_DEV` with the `Application (client) ID` from your app registration for the `dev` environment\n\n      ```sh\n      find \\( -wholename \"./.env\" -or -wholename \"./chart/values.dev.yaml\" -or -wholename \"./Jenkinsfile\" \\) -exec sed --expression 's/11111111-2222-3333-4444-555555555dev/YOUR_CLIENT_ID_DEV/g' --in-place {} +\n      ```\n\n   2. Replace `YOUR_CLIENT_ID_TEST` with the `Application (client) ID` from your app registration for the `test` environment\n\n      ```sh\n      find -wholename \"./chart/values.test.yaml\" -exec sed --expression 's/11111111-2222-3333-4444-55555555test/YOUR_CLIENT_ID_TEST/g' --in-place {} +\n      ```\n\n   3. Replace `YOUR_CLIENT_ID_PROD` with the `Application (client) ID` from your app registration for the `prod` environment\n\n      ```sh\n      find -wholename \"./chart/values.prod.yaml\" -exec sed --expression 's/11111111-2222-3333-4444-55555555prod/YOUR_CLIENT_ID_PROD/g' --in-place {} +\n      ```\n\n   4. Replace `YOUR_TENANT_ID` with the `Directory (tenant) ID` from your app registration, which is basically the same for per environment (`dev`/`test`/`prod`)\n\n      ```sh\n      find \\( -wholename \"./.env\" -or -wholename \"./chart/values.*.yaml\" -or -wholename \"./Jenkinsfile\" \\) -exec sed --expression 's/common/YOUR_TENANT_ID/g' --in-place {} +\n      ```\n\nMore information: \u003chttps://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration\u003e\n\n## Local Development 👨‍💻\n\n### Requirements\n\n- Helm v3+\n- Node.js v16+\n- NPM v8+\n- oc v4.9+\n\n#### Using [`NVM`](https://github.com/nvm-sh/nvm)\n\n```sh\n# Install latest LTS Version (v16+) with the latest npm version (v8+)\nnvm install --lts --latest-npm\n```\n\n### Set Environment Variables\n\nCreate appropriate `.env` file from `.env.template`.\n\n```sh\ncp .env.template .env\n```\n\nAsk your colleagues which values are currently necessary!\n\n### Install Dependencies\n\n```sh\nnpm install\n```\n\n### Start Development Server\n\n```sh\nnpm run start\n```\n\nStarts the development server and makes your application accessible at `localhost:8100`. Changes in the application code will be hot-reloaded.\n\n### Create Production Build (Web)\n\n```sh\nnpm run build\n```\n\nThe app is built for optimal performance: assets are minified and served gzipped.\n\n### Run tests\n\n```sh\nnpm run test\n```\n\n### Docker\n\n#### Create Docker Image\n\n```sh\nnpm run build\nmv build docker\ndocker build -t PROJECTID-COMPONENTID -f docker/Dockerfile docker\n```\n\nIn case the command `RUN apk update \u0026\u0026 apk upgrade` cannot be executed (e.g. working behind a proxy), uncomment it for the moment.\n\n#### Start Docker Image\n\n```sh\ndocker run -p 8080:8080 --env-file .env PROJECTID-COMPONENTID\n```\n\nStarts the [nginx](https://nginx.org) server and makes your application accessible at `localhost:8080`.\n\n## Continuous Integration/Continuous Delivery (CI/CD) ♾️\n\nThis CI/CD setup has been developed for the 'trunk-based development' approach.\n\n\u003e _[...] Trunk-based development is a version control management practice where developers merge small, frequent updates to a core “trunk” or main branch. [...] Trunk-based development is far more simplified since it focuses on the main branch as the source of fixes and releases. In trunk-based development the main branch is assumed to always be stable, without issues, and ready to deploy [...]._ - \u003chttps://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development\u003e\n\n### Master Branch\n\nThe `master` branch is your only real **source-of-true** 📜 and should always reflect the state as found in all three environments.\n\nIt is recommended not to merge any changes into the `master`-branch before a new release is triggered. Otherwise there is the risk of not being able to perform a hotfix immediately. The only solution remains a corresponding Git Strategy to restore the old state, import the necessary hotfix changes, release and then add the reset changes again.\n\nWith each commit, the source code in the master branch is checked for its releasability and tagged at the end with a new semantic version based on the git commit history.\n\n```mermaid\n%% If the Mermaid Diagram is not rendered (as is the case on BitBucket), it can be viewed at https://mermaid.live/\nflowchart TB\n    subgraph openshift-dev[\"OpenShift (DEV)\"]\n        subgraph ods\n            subgraph sonarqube[\"SonarQube\"]\n                direction TB\n                ods-DC-sonarqube[\"sonarqube (DeploymentConfig)\"]:::classDeploymentConfig \u003c-. Port 9000 .-\u003e ods-S-sonarqube[\"sonarqube (Service)\"]:::classService \u003c-. Port 9000 .-\u003e ods-RT-sonarqube[\"sonarqube (Route)\\nhttps://sonarqube-ods.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n        subgraph aqua\n            aqua-aqua[\"Aqua Container Security\"]\n        end\n        subgraph PROJECTID-cd\n            subgraph webhook-proxy\n                direction TB\n                cd-DC-webhook-proxy[\"webhook-proxy (DeploymentConfig)\"]:::classDeploymentConfig \u003c-. Port 8080 .-\u003e cd-S-webhook-proxy[\"webhook-proxy (Service)\"]:::classService \u003c-. Port 8080 .-\u003e cd-RT-webhook-proxy[\"webhook-proxy (Route)\\nhttps://webhook-proxy-PROJECTID-cd.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n            subgraph Jenkins\n                subgraph Jenkinsfile\n                    stageInitialize--\u003estageInstallDependency--\u003estageVersioning--\u003estageWorkaroundFindOpenShiftImageOrElse --\u003e stageAnalyzeCode--\u003eodsComponentStageScanWithSonar--\u003estageBuild--\u003estageDeploy--\u003eodsComponentStageBuildOpenShiftImage--\u003estageWorkaroundUnitTest--\u003estageWorkaroundRolloutDeployment--\u003estageRelease\n                end\n            end\n            subgraph cd-IS-PROJECTID-COMPONENTID-master[\"PROJECTID-COMPONENTID-master (Image Stream)\"]\n                cd-IST-PROJECTID-COMPONENTID-master[\"PROJECTID-COMPONENTID-master:v1.4.2\"]:::classImageStreamTag\n            end\n        end\n    end\n    subgraph BitBucket\n        subgraph bb-project-PROJECTID[\"PROJECTID (Project)\"]\n            subgraph bb-project-PROJECTID-COMPONENTID[\"PROJECTID-COMPONENTID (Repo)\"]\n                bb-project-PROJECTID-COMPONENTID-branch-master[\"master (Branch)\"]\n            end\n        end\n    end\n\n\naqua-aqua -- scan --\u003e cd-IST-PROJECTID-COMPONENTID-master\nbb-project-PROJECTID-COMPONENTID -- trigger --\u003e webhook-proxy -- trigger --\u003e Jenkins\nbb-project-PROJECTID-COMPONENTID-branch-master -- pull --\u003e Jenkinsfile\nodsComponentStageBuildOpenShiftImage -- push --\u003e cd-IST-PROJECTID-COMPONENTID-master\nodsComponentStageBuildOpenShiftImage -. trigger .-\u003e aqua-aqua -. result .-\u003e odsComponentStageBuildOpenShiftImage\nodsComponentStageScanWithSonar -. trigger .-\u003e sonarqube -. result .-\u003e odsComponentStageScanWithSonar\nstageRelease -- \"push (v1.4.2)\" --\u003e bb-project-PROJECTID-COMPONENTID-branch-master\n\n%% stlyes\nclassDef classBitBucket fill:#2684FF22,stroke:#2684FF,stroke-width:4px\nclassDef classBitBucketProject fill:#2684FF22,stroke:#2684FF\nclassDef classBuildConfig fill:#00408022,stroke:#004080\nclassDef classDeployment fill:#00408022,stroke:#004080\nclassDef classDeploymentConfig fill:#00408022,stroke:#004080\nclassDef classHelmRelease fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStream fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStreamTag fill:#2b9af322,stroke:#2b9af3\nclassDef classOcpProject fill:#ffffff00,stroke:#f00,stroke-width:2px\nclassDef classOcpResource fill:#ffffff00,stroke:#06c,stroke-width:2px\nclassDef classOpenShift fill:#ffffff00,stroke:#f00,stroke-width:4px\nclassDef classRoute fill:#2b9af322,stroke:#2b9af3\nclassDef classService fill:#6ca10022,stroke:#6ca100\n\nclass BitBucket classBitBucket\nclass bb-project-PROJECTID,bb-project-PROJECTID-COMPONENTID classBitBucketProject\nclass cd-IS-PROJECTID-COMPONENTID-master classImageStream\nclass aqua,ods,PROJECTID-cd,PROJECTID-dev classOcpProject\nclass aqua-aqua,Jenkins,Jenkinsfile,sonarqube,webhook-proxy classOcpResource\nclass openshift-dev classOpenShift\n```\n\n### Feature Environments\n\nWith each new `feature/*` branch created, a new environment is created in the OpenShift Project `PROJECTID-cd`.\nDifferent stages are processed in the Jenkinsfile and finally rolled out and managed via Helm.\n\nPlease be aware that a new route (e.g. \u003chttps://PROJECTID-COMPONENTID-feature-next.apps.OPENSHIFT_DOMAIN_DEV\u003e) is created for each new feature environment. If this is required for the SSO login, it must be specified as a new valid redirect URL in the app registration.\n\n```mermaid\n%% If the Mermaid Diagram is not rendered (as is the case on BitBucket), it can be viewed at https://mermaid.live/\nflowchart TB\n    subgraph openshift-dev[\"OpenShift (DEV)\"]\n        subgraph ods\n            subgraph sonarqube[\"SonarQube\"]\n                direction TB\n                ods-DC-sonarqube[\"sonarqube (DeploymentConfig)\"]:::classDeploymentConfig \u003c-. Port 9000 .-\u003e ods-S-sonarqube[\"sonarqube (Service)\"]:::classService \u003c-. Port 9000 .-\u003e ods-RT-sonarqube[\"sonarqube (Route)\\nhttps://sonarqube-ods.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n        subgraph PROJECTID-cd\n            subgraph webhook-proxy\n                direction TB\n                cd-DC-webhook-proxy[\"webhook-proxy (DeploymentConfig)\"]:::classDeploymentConfig \u003c-. Port 8080 .-\u003e cd-S-webhook-proxy[\"webhook-proxy (Service)\"]:::classService \u003c-. Port 8080 .-\u003e cd-RT-webhook-proxy[\"webhook-proxy (Route)\\nhttps://webhook-proxy-PROJECTID-cd.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n            subgraph Jenkins\n                subgraph Jenkinsfile\n                    stageInitialize--\u003estageInstallDependency--\u003estageVersioning--\u003estageWorkaroundFindOpenShiftImageOrElse --\u003e|orElse| stageAnalyzeCode--\u003eodsComponentStageScanWithSonar--\u003estageBuild--\u003estageDeploy--\u003eodsComponentStageBuildOpenShiftImage--\u003estageWorkaroundUnitTest--\u003estageWorkaroundRolloutDeployment--\u003estageRelease\n                end\n            end\n            subgraph cd-IS-PROJECTID-COMPONENTID-feature-next[\"PROJECTID-COMPONENTID-feature-next (Image Stream)\"]\n                cd-IST-PROJECTID-COMPONENTID-feature-next[\"PROJECTID-COMPONENTID-feature-next:hash\"]:::classImageStreamTag\n            end\n        end\n        subgraph PROJECTID-dev\n            subgraph dev-HR-PROJECTID-COMPONENTID-feature-next[\"PROJECTID-COMPONENTID-feature-next (Helm Release)\"]\n                dev-D-COMPONENTID[\"COMPONENTID (Deployment)\"]:::classDeployment \u003c-. Port 8080 .-\u003e dev-S-COMPONENTID[\"COMPONENTID (Service)\"]:::classService \u003c-. Port 8080 .-\u003e dev-RT-COMPONENTID[\"COMPONENTID (Route)\\nhttps://PROJECTID-COMPONENTID-feature-next.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n    end\n    subgraph bitbucket[\"BitBucket\"]\n        subgraph bitbucket-PROJECTID[\"PROJECTID (Project)\"]\n            subgraph bitbucket-PROJECTID-COMPONENTID[\"PROJECTID-COMPONENTID (Repo)\"]\n                bitbucket-PROJECTID-COMPONENTID-branch-feature-next[\"feature-next (Branch)\"]\n            end\n        end\n    end\n\nbitbucket-PROJECTID-COMPONENTID -- trigger --\u003e webhook-proxy -- trigger --\u003e Jenkins\nbitbucket-PROJECTID-COMPONENTID-branch-feature-next -- pull --\u003e Jenkinsfile\ncd-IST-PROJECTID-COMPONENTID-feature-next --\u003e dev-D-COMPONENTID\nodsComponentStageBuildOpenShiftImage -- push --\u003e cd-IST-PROJECTID-COMPONENTID-feature-next\nodsComponentStageScanWithSonar \u003c-.-\u003e sonarqube\nstageWorkaroundRolloutDeployment -- stageRolloutWithHelm --\u003e dev-HR-PROJECTID-COMPONENTID-feature-next\n\n%% stlyes\nclassDef classBitBucket fill:#2684FF22,stroke:#2684FF,stroke-width:4px\nclassDef classBitBucketProject fill:#2684FF22,stroke:#2684FF\nclassDef classBuildConfig fill:#00408022,stroke:#004080\nclassDef classDeployment fill:#00408022,stroke:#004080\nclassDef classDeploymentConfig fill:#00408022,stroke:#004080\nclassDef classHelmRelease fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStream fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStreamTag fill:#2b9af322,stroke:#2b9af3\nclassDef classOcpProject fill:#ffffff00,stroke:#f00,stroke-width:2px\nclassDef classOcpResource fill:#ffffff00,stroke:#06c,stroke-width:2px\nclassDef classOpenShift fill:#ffffff00,stroke:#f00,stroke-width:4px\nclassDef classRoute fill:#2b9af322,stroke:#2b9af3\nclassDef classService fill:#6ca10022,stroke:#6ca100\n\nclass BitBucket classBitBucket\nclass bitbucket-PROJECTID,bitbucket-PROJECTID-COMPONENTID classBitBucketProject\nclass dev-HR-PROJECTID-COMPONENTID-feature-next classHelmRelease\nclass cd-IS-PROJECTID-COMPONENTID-feature-next classImageStream\nclass ods,PROJECTID-cd,PROJECTID-dev classOcpProject\nclass Jenkins,Jenkinsfile,sonarqube,webhook-proxy classOcpResource\nclass openshift-dev classOpenShift\n```\n\n### Release Manager\n\n#### Release to `dev` environment\n\n```mermaid\n%% If the Mermaid Diagram is not rendered (as is the case on BitBucket), it can be viewed at https://mermaid.live/\nflowchart TB\n    subgraph openshift-dev[\"OpenShift (DEV)\"]\n\n        subgraph PROJECTID-cd\n            subgraph jenkins[\"Jenkins\"]\n                subgraph jenkinsfile-PROJECTID-COMPONENTID[\"Jenkinsfile (PROJECTID-COMPONENTID)\"]\n                    stageInitialize--\u003estageInstallDependency--\u003estageVersioning--\u003estageWorkaroundFindOpenShiftImageOrElse --\u003e stageAnalyzeCode--\u003eodsComponentStageScanWithSonar--\u003estageBuild--\u003estageDeploy--\u003eodsComponentStageBuildOpenShiftImage--\u003estageWorkaroundUnitTest--\u003estageWorkaroundRolloutDeployment--\u003estageRelease\n                end\n                build-with-parameters(\"\u003cstrong\u003eBuild with Parameters\u003c/strong\u003e\\nenvironment: dev\\nversion: 20220518.001\"):::classManualTask\n                subgraph jenkinsfile-releasemanager[\"Jenkinsfile (Release Manager)\"]\n                    InitStage --\u003e BuildStage --\u003e DeployStage --\u003e TestStage --\u003e ReleaseStage --\u003e FinalizeStage\n                end\n            end\n            subgraph cd-IS-COMPONENTID[\"COMPONENTID (Image Stream)\"]\n                cd-IST-COMPONENTID[\"COMPONENTID:20220518.001\"]:::classImageStreamTag\n            end\n        end\n        subgraph ods\n            subgraph sonarqube[\"SonarQube\"]\n                direction TB\n                ods-DC-sonarqube[\"sonarqube (DeploymentConfig)\"]:::classDeploymentConfig \u003c-. Port 9000 .-\u003e ods-S-sonarqube[\"sonarqube (Service)\"]:::classService \u003c-. Port 9000 .-\u003e ods-RT-sonarqube[\"sonarqube (Route)\\nhttps://sonarqube-ods.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n        subgraph aqua\n            aqua-aqua[\"Aqua Container Security\"]\n        end\n        subgraph PROJECTID-dev\n            subgraph dev-HR-COMPONENTID[\"COMPONENTID (Helm Release)\"]\n                direction TB\n                dev-D-COMPONENTID[\"COMPONENTID (Deployment)\"]:::classDeployment \u003c-. Port 8080 .-\u003e dev-S-COMPONENTID[\"COMPONENTID (Service)\"]:::classService \u003c-. Port 8080 .-\u003e dev-RT-COMPONENTID[\"COMPONENTID (Route)\\nhttps://PROJECTID-dev.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n    end\n    subgraph BitBucket\n        subgraph bitbucket-PROJECTID[\"PROJECTID (Project)\"]\n            subgraph bitbucket-PROJECTID-COMPONENTID[\"PROJECTID-COMPONENTID (Repo)\"]\n                bitbucket-PROJECTID-COMPONENTID-branch-master[\"master (Branch)\"]\n                bitbucket-PROJECTID-COMPONENTID-branch-release[\"release/20220518.001 (Branch)\"]\n            end\n            subgraph bitbucket-PROJECTID-releasemanager[\"PROJECTID-releasemanager (Repo)\"]\n                bitbucket-PROJECTID-releasemanager-branch-master[\"master (Branch)\"]\n            end\n        end\n    end\n\naqua-aqua -- scan --\u003e cd-IST-COMPONENTID\ncd-IST-COMPONENTID --\u003e dev-D-COMPONENTID\nbitbucket-PROJECTID-COMPONENTID-branch-master -- pull --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbitbucket-PROJECTID-releasemanager-branch-master -- pull --\u003e jenkinsfile-releasemanager\nBuildStage -- trigger --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbuild-with-parameters -. trigger .-\u003e InitStage\nFinalizeStage -- push --\u003e bitbucket-PROJECTID-COMPONENTID-branch-release\nFinalizeStage -- push --\u003e bitbucket-PROJECTID-releasemanager-branch-master\nodsComponentStageBuildOpenShiftImage -- push --\u003e cd-IST-COMPONENTID\nodsComponentStageBuildOpenShiftImage -. trigger .-\u003e aqua-aqua -. result .-\u003e odsComponentStageBuildOpenShiftImage\nodsComponentStageScanWithSonar -. trigger .-\u003e sonarqube -. result .-\u003e odsComponentStageScanWithSonar\nstageWorkaroundRolloutDeployment -- \"Rollout with Helm\" --\u003e dev-HR-COMPONENTID\n\n%% stlyes\nclassDef classBitBucket fill:#2684FF22,stroke:#2684FF,stroke-width:4px\nclassDef classBitBucketProject fill:#2684FF22,stroke:#2684FF\nclassDef classBuildConfig fill:#00408022,stroke:#004080\nclassDef classDeployment fill:#00408022,stroke:#004080\nclassDef classDeploymentConfig fill:#00408022,stroke:#004080\nclassDef classHelmRelease fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStream fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStreamTag fill:#2b9af322,stroke:#2b9af3\nclassDef classOcpProject fill:#ffffff00,stroke:#f00,stroke-width:2px\nclassDef classOcpResource fill:#ffffff00,stroke:#06c,stroke-width:2px\nclassDef classOpenShift fill:#ffffff00,stroke:#f00,stroke-width:4px\nclassDef classRoute fill:#2b9af322,stroke:#2b9af3\nclassDef classService fill:#6ca10022,stroke:#6ca100\nclassDef classManualTask fill:#65bd1022,stroke:#65bd10,stroke-width:4px\n\nclass BitBucket classBitBucket\nclass bitbucket-PROJECTID,bitbucket-PROJECTID-COMPONENTID,bitbucket-PROJECTID-releasemanager classBitBucketProject\nclass dev-HR-COMPONENTID classHelmRelease\nclass cd-IS-COMPONENTID classImageStream\nclass aqua,ods,PROJECTID-cd,PROJECTID-dev classOcpProject\nclass aqua-aqua,jenkins,jenkinsfile-PROJECTID-COMPONENTID,jenkinsfile-releasemanager,sonarqube,webhook-proxy classOcpResource\nclass openshift-dev classOpenShift\n```\n\n#### Release to `test` environment\n\n```mermaid\n%% If the Mermaid Diagram is not rendered (as is the case on BitBucket), it can be viewed at https://mermaid.live/\nflowchart TB\n    subgraph openshift-dev[\"OpenShift (DEV)\"]\n        subgraph PROJECTID-cd\n            subgraph jenkins[\"Jenkins\"]\n                build-with-parameters{{\"\u003cstrong\u003eBuild with Parameters\u003c/strong\u003e\\nenvironment: qa\\nversion: 20220518.001\"}}:::classManualTask\n                subgraph jenkinsfile-releasemanager[\"Jenkinsfile (Release Manager)\"]\n                    InitStage --\u003e BuildStage --\u003e DeployStage --\u003e TestStage --\u003e ReleaseStage --\u003e FinalizeStage\n                end\n                subgraph jenkinsfile-PROJECTID-COMPONENTID[\"Jenkinsfile (PROJECTID-COMPONENTID)\"]\n                    stageInitialize --\u003e stageInstallDependency --\u003e stageVersioning --\u003e stageWorkaroundFindOpenShiftImageOrElse --\u003e stageWorkaroundUnitTest --\u003e stageWorkaroundRolloutDeployment --\u003e stageRelease\n                end\n            end\n            subgraph cd-IS-COMPONENTID[\"COMPONENTID (Image Stream)\"]\n                cd-IST-COMPONENTID[\"COMPONENTID:20220518.001\"]:::classImageStreamTag\n            end\n        end\n        subgraph PROJECTID-test\n            subgraph test-HR-COMPONENTID[\"COMPONENTID (Helm Release)\"]\n                test-D-COMPONENTID[\"COMPONENTID (Deployment)\"]:::classDeployment \u003c-. Port 8080 .-\u003e test-S-COMPONENTID[\"COMPONENTID (Service)\"]:::classService \u003c-. Port 8080 .-\u003e test-RT-COMPONENTID[\"COMPONENTID (Route)\\nhttps://PROJECTID-test.apps.OPENSHIFT_DOMAIN_DEV\"]:::classRoute\n            end\n        end\n    end\n    subgraph BitBucket\n        subgraph bitbucket-PROJECTID[\"PROJECTID (Project)\"]\n            subgraph bitbucket-PROJECTID-COMPONENTID[\"PROJECTID-COMPONENTID (Repo)\"]\n                bitbucket-PROJECTID-COMPONENTID-branch-master[\"master (Branch)\"]\n                merge{{\"\u003cstrong\u003eMerge into master\u003c/strong\u003e\\nrelease/20220518.001 (Branch)\"}}:::classManualTask\n            end\n            subgraph bitbucket-PROJECTID-releasemanager[\"PROJECTID-releasemanager (Repo)\"]\n                bitbucket-PROJECTID-releasemanager-branch-master[\"master (Branch)\"]\n            end\n        end\n    end\n\ncd-IST-COMPONENTID \u003c-.-\u003e stageWorkaroundFindOpenShiftImageOrElse\ncd-IST-COMPONENTID --\u003e test-D-COMPONENTID\nbitbucket-PROJECTID-COMPONENTID-branch-master -- pull --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbitbucket-PROJECTID-releasemanager-branch-master -- pull --\u003e jenkinsfile-releasemanager\nmerge --\u003e bitbucket-PROJECTID-COMPONENTID-branch-master[\"master (Branch)\"]\nBuildStage -- trigger --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbuild-with-parameters -. trigger .-\u003e InitStage\nFinalizeStage -- push --\u003e bitbucket-PROJECTID-releasemanager-branch-master\nstageWorkaroundRolloutDeployment -- \"Rollout with Helm\" --\u003e test-HR-COMPONENTID\n\n%% stlyes\nclassDef classBitBucket fill:#2684FF22,stroke:#2684FF,stroke-width:4px\nclassDef classBitBucketProject fill:#2684FF22,stroke:#2684FF\nclassDef classBuildConfig fill:#00408022,stroke:#004080\nclassDef classDeployment fill:#00408022,stroke:#004080\nclassDef classDeploymentConfig fill:#00408022,stroke:#004080\nclassDef classHelmRelease fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStream fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStreamTag fill:#2b9af322,stroke:#2b9af3\nclassDef classOcpProject fill:#ffffff00,stroke:#f00,stroke-width:2px\nclassDef classOcpResource fill:#ffffff00,stroke:#06c,stroke-width:2px\nclassDef classOpenShift fill:#ffffff00,stroke:#f00,stroke-width:4px\nclassDef classRoute fill:#2b9af322,stroke:#2b9af3\nclassDef classService fill:#6ca10022,stroke:#6ca100\nclassDef classManualTask fill:#65bd1022,stroke:#65bd10,stroke-width:4px\n\nclass BitBucket classBitBucket\nclass bitbucket-PROJECTID,bitbucket-PROJECTID-COMPONENTID,bitbucket-PROJECTID-releasemanager classBitBucketProject\nclass test-HR-COMPONENTID classHelmRelease\nclass cd-IS-COMPONENTID classImageStream\nclass aqua,ods,PROJECTID-cd,PROJECTID-test classOcpProject\nclass aqua-aqua,jenkins,jenkinsfile-PROJECTID-COMPONENTID,jenkinsfile-releasemanager,sonarqube,webhook-proxy classOcpResource\nclass openshift-dev classOpenShift\n```\n\n#### Release to `prod` environment\n\n```mermaid\n%% If the Mermaid Diagram is not rendered (as is the case on BitBucket), it can be viewed at https://mermaid.live/\nflowchart TB\n    subgraph openshift-dev[\"OpenShift (DEV)\"]\n        subgraph PROJECTID-cd\n            subgraph jenkins[\"Jenkins\"]\n                build-with-parameters{{\"\u003cstrong\u003eBuild with Parameters\u003c/strong\u003e\\nenvironment: prod\\nversion: 20220518.001\"}}:::classManualTask\n                subgraph jenkinsfile-releasemanager[\"Jenkinsfile (Release Manager)\"]\n                    InitStage --\u003e BuildStage --\u003e DeployStage --\u003e TestStage --\u003e ReleaseStage --\u003e FinalizeStage\n                end\n                subgraph jenkinsfile-PROJECTID-COMPONENTID[\"Jenkinsfile (PROJECTID-COMPONENTID)\"]\n                    stageInitialize --\u003e stageInstallDependency --\u003e stageVersioning --\u003e stageWorkaroundFindOpenShiftImageOrElse --\u003e stageWorkaroundUnitTest --\u003e stageWorkaroundRolloutDeployment --\u003e stageRelease\n                end\n            end\n            subgraph cd-IS-COMPONENTID[\"COMPONENTID (Image Stream)\"]\n                cd-IST-COMPONENTID[\"COMPONENTID:20220518.001\"]:::classImageStreamTag\n            end\n        end\n    end\n    subgraph openshift-prod[\"OpenShift (PROD)\"]\n        subgraph PROJECTID-prod\n            subgraph prod-HR-COMPONENTID[\"COMPONENTID (Helm Release)\"]\n                prod-D-COMPONENTID[\"COMPONENTID (Deployment)\"]:::classDeployment \u003c-. Port 8080 .-\u003e prod-S-COMPONENTID[\"COMPONENTID (Service)\"]:::classService \u003c-. Port 8080 .-\u003e prod-RT-COMPONENTID[\"COMPONENTID (Route)\\nhttps://PROJECTID.apps.OPENSHIFT_DOMAIN_PROD\"]:::classRoute\n            end\n        end\n    end\n    subgraph BitBucket\n        subgraph bitbucket-PROJECTID[\"PROJECTID (Project)\"]\n            subgraph bitbucket-PROJECTID-COMPONENTID[\"PROJECTID-COMPONENTID (Repo)\"]\n                bitbucket-PROJECTID-COMPONENTID-branch-master[\"master (Branch)\"]\n            end\n            subgraph bitbucket-PROJECTID-releasemanager[\"PROJECTID-releasemanager (Repo)\"]\n                bitbucket-PROJECTID-releasemanager-branch-master[\"master (Branch)\"]\n            end\n        end\n    end\n\ncd-IST-COMPONENTID --\u003e prod-D-COMPONENTID\ncd-IST-COMPONENTID \u003c-.-\u003e stageWorkaroundFindOpenShiftImageOrElse\nbitbucket-PROJECTID-COMPONENTID-branch-master -- pull --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbitbucket-PROJECTID-releasemanager-branch-master -- pull --\u003e jenkinsfile-releasemanager\nBuildStage -- trigger --\u003e jenkinsfile-PROJECTID-COMPONENTID\nbuild-with-parameters -. trigger .-\u003e InitStage\nFinalizeStage -- push --\u003e bitbucket-PROJECTID-releasemanager-branch-master\nstageWorkaroundRolloutDeployment -- \"Rollout with Helm\" --\u003e prod-HR-COMPONENTID\n\n%% stlyes\nclassDef classBitBucket fill:#2684FF22,stroke:#2684FF,stroke-width:4px\nclassDef classBitBucketProject fill:#2684FF22,stroke:#2684FF\nclassDef classBuildConfig fill:#00408022,stroke:#004080\nclassDef classDeployment fill:#00408022,stroke:#004080\nclassDef classDeploymentConfig fill:#00408022,stroke:#004080\nclassDef classHelmRelease fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStream fill:#2b9af322,stroke:#2b9af3\nclassDef classImageStreamTag fill:#2b9af322,stroke:#2b9af3\nclassDef classOcpProject fill:#ffffff00,stroke:#f00,stroke-width:2px\nclassDef classOcpResource fill:#ffffff00,stroke:#06c,stroke-width:2px\nclassDef classOpenShift fill:#ffffff00,stroke:#f00,stroke-width:4px\nclassDef classRoute fill:#2b9af322,stroke:#2b9af3\nclassDef classService fill:#6ca10022,stroke:#6ca100\nclassDef classManualTask fill:#65bd1022,stroke:#65bd10,stroke-width:4px\n\nclass BitBucket classBitBucket\nclass bitbucket-PROJECTID,bitbucket-PROJECTID-COMPONENTID,bitbucket-PROJECTID-releasemanager classBitBucketProject\nclass prod-HR-COMPONENTID classHelmRelease\nclass cd-IS-COMPONENTID classImageStream\nclass aqua,ods,PROJECTID-cd,PROJECTID-prod classOcpProject\nclass aqua-aqua,jenkins,jenkinsfile-PROJECTID-COMPONENTID,jenkinsfile-releasemanager,sonarqube,webhook-proxy classOcpResource\nclass openshift-dev,openshift-prod classOpenShift\n```\n\n## Housekeeping 🧹\n\n💡 From time to time, obsolete resources should be cleaned up. It would be best to have this automated at a later time. However, at the moment, this is not yet possible, because the webhook-proxy captures the `deleted` event and cannot be further customized, see: \u003chttps://github.com/opendevstack/ods-core/blob/99d26527df60fbb4d72ba15a8c233e325ff37fe1/jenkins/webhook-proxy/main.go#L541-L556\u003e\n\n### Git Branches\n\n```sh\ngit checkout master\n\n# Cleaning outdated branches\ngit fetch --prune\n\n# Delete all local branches except current branch (e.g. master)\ngit branch | grep --invert-match '^*' | xargs git branch -D\n\n# Delete all remote branches except master (may take some time)\n# Skip git hooks with '--no-verify'\ngit branch -r | grep 'origin' | grep --invert-match 'master$' | grep --invert-match HEAD | cut -d/ -f2- | while read line; do git push --no-verify origin :heads/$line; done;\n```\n\n### Git Tags\n\n```sh\n# Delete all local tags that do NOT match a pattern of a semantic version (MAJOR.MINOR.PATCH), e.g. ods-generated-v20220518.001, v1.0.0-next.5\ngit tag -l | grep --invert-match '^v[[:digit:]]*.[[:digit:]]*.[[:digit:]]*$' | xargs git tag -d\n\n# Delete all remote tags that do NOT match a pattern of a semantic version (MAJOR.MINOR.PATCH), e.g. ods-generated-v20220518.001, v1.0.0-next.5\n# Skip git hooks with '--no-verify'\ngit ls-remote --tags origin | cut -d/ -f3- | grep --invert-match '^v[[:digit:]]*.[[:digit:]]*.[[:digit:]]*$' | grep -v '}$' | xargs git push --delete --no-verify origin\n```\n\n### OpenShift\n\n#### Feature Environments\n\nWith the approach of making each feature available as a new deployed environment for testing before it is merged into the master branch, a number of environments are created in OpenShift over time. The easiest way to delete these is to use the following command:\n\n```sh\n# Login\noc login --server=https://api.OPENSHIFT_DOMAIN_DEV:6443 --token=123...456\n\n# Switch to Project\noc project PROJECTID-dev\n\n# Delete/Uninstall all feature charts\nhelm list | grep -e 'COMPONENTID' | cut -f1 | xargs helm uninstall\n\n# Delete all other feature resources\noc get all --output jsonpath='{range .items[*]}{\"oc delete \"}{.kind}{\" \"}{.metadata.name}{\" \"}{\"\\n\"}{end}' | grep -- \"COMPONENTID-feature-\" | while read -r line; do eval $line; done\n```\n\n#### Jenkins Pipelines\n\n**Feature Pipelines**: Since OpenShift 4, Jenkins pipelines are treated as `BuildConfig`. Unfortunately, with `ODS@4.x` in the Jenkins stage [`odsComponentStageBuildOpenShiftImage`](https://www.opendevstack.org/ods-documentation/opendevstack/4.x/jenkins-shared-library/component-pipeline.html#_odscomponentstagebuildopenshiftimage), all builds are also created as a `BuildConfig` in the `cd` project without any further information filled labels. A distinction is not obvious at first view, but can be figured out via the configuration parameter `.spec.strategy.type` (`JenkinsPipeline` vs `Docker`).\n\n**Release Pipelines**: Can be deleted without any problems, since they do not create any further resources, instead they are directly cancelled due to `[skip ci]` in the commit message.\n\n**ODS Quickstarter**: Can be deleted after successful creation without any problems, as there is no further need for them and they only take up resources unnecessarily.\n\n```sh\n# Login\noc login --server=https://api.OPENSHIFT_DOMAIN_DEV:6443 --token=123...456\n\n# Switch Project\noc project PROJECTID-cd\n\n# Delete all feature pipelines (may take some time)\noc get bc --output jsonpath='{range .items[*]}{.metadata.name}{\"\\t\"}{.spec.strategy.type}{\"\\n\"}{end}' | grep -e \"JenkinsPipeline\" | cut -f1 | grep -e \"COMPONENTID-feature-\" | while read -r line; do oc delete bc $line \u0026\u0026 sleep 10s; done\n\n# Delete all release pipelines (may take some time)\noc get bc --output custom-columns=NAME:.metadata.name | grep -e \"COMPONENTID-release-\" | while read -r line; do oc delete bc $line \u0026\u0026 sleep 10s; done\n\n# Delete all ods quickstarter pipelines (may take some time)\noc get bc --output custom-columns=NAME:.metadata.name | grep -e \"ods-qs-\" | while read -r line; do oc delete bc $line \u0026\u0026 sleep 10s; done\n```\n\n## Roadmap 🛣️\n\n- [x] Improve Documentation\n- [ ] Implement Android\n- [ ] Implement iOS\n- [ ] Implement Ionic Appflow\n- [x] Improve Testing\n\n## FAQ ❓❗\n\n### Jenkins\n\n\u003cdetails\u003e\u003csummary\u003eHow do I find out which a Jenkins Agent with Node.js are available in my \u003ccode\u003eODS@4.x\u003c/code\u003e setup?\u003c/summary\u003e\n\nGo to \u003chttps://oauth-openshift.apps.OPENSHIFT_DOMAIN_DEV/k8s/ns/ods/build.openshift.io~v1~BuildConfig?name=jenkins-agent-nodejs\u003e\n\n\u003c/details\u003e\n\n### Openshift\n\n\u003cdetails\u003e\u003csummary\u003eHow to find the \u003ccode\u003eoc\u003c/code\u003e login token\u003c/summary\u003e\n\n1. Go to \u003chttps://oauth-openshift.apps.OPENSHIFT_DOMAIN_DEV/oauth/token/display\u003e\n2. Click on `Display token` or `Request another token`\n\n\u003c/details\u003e\n\n## Known Issues 🚧\n\n### Bitbucket\n\n\u003cdetails\u003e\u003csummary\u003eA Pull Request shows a merge conflict on \u003ccode\u003echart/Chart.yaml\u003c/code\u003e, \u003ccode\u003echart/values.yaml\u003c/code\u003e, \u003ccode\u003eCHANGELOG.md\u003c/code\u003e, \u003ccode\u003emetadata.yml\u003c/code\u003e, \u003ccode\u003epackage-lock.json\u003c/code\u003e, \u003ccode\u003epackage.json\u003c/code\u003e, \u003ccode\u003eREADME.md\u003c/code\u003e\u003c/summary\u003e\n\nThis happens mainly when e.g. a new `feature` branch has already been created from `master` branch before the Jenkins job has been successfully completed with a release commit.\n\nTo avoid resolving all merge conflicts manually, this can already be specified in the `merge` command by the [`--strategy-option=theirs`](https://www.git-scm.com/docs/git-merge#Documentation/git-merge.txt---strategy-optionltoptiongt) option to automatically accept all incoming changes.\n\n**Solution:**\n\n```sh\n# Merge the remote master branch into the current one without opening a text editor (accept the auto-generated message) and accept all incoming changes on merge conflicts\ngit merge origin/master --no-edit --strategy-option=theirs\n\n# (Optional) Add `skip ci` command to the previous merge commit\ngit commit --amend -m \"$(git log --format=%s --max-count=1) [skip ci]\"\n\n# Push the changes to the remote repository\ngit push\n```\n\n\u003c/details\u003e\n\n### Jenkins\n\n\u003cdetails\u003e\u003csummary\u003eThe Jenkins pipeline does not start and shows the following error message:\u003ccode\u003e[Failed] Failed to pull image \"image-registry.openshift-image-registry.svc:5000/ods/jenkins-agent-nodejs16:4.x\" ... [Failed] Error: ImagePullBackOff\u003c/code\u003e\u003c/summary\u003e\n\nIt might happen that your `ODS@4.x` setup only provides a Jenkins agent with Node.js `12.x`. However, in order to be able to work with the latest version and to have potential security holes closed, a Jenkins agent with the latest Node.js version is required for the build process in the CI/CD process.\n\n[FAQ: How do I find out which a Jenkins Agent with Node.js are available in my `ODS@4.x` setup?](#faq)\n\nIn case it does not exist yet, it can be easily created with the following commands\n\n[FAQ: How to find the `oc` login token](#faq)\n\n**Solution:**\n\n```sh\n# Login to OpenShift dev instance\noc login --server=https://api.OPENSHIFT_DOMAIN_DEV:6443 --token=123...456\n\n# Switch project\noc project PROJECTID-cd\n\n# Provision jenkins-agent-nodejs-16\noc process -f https://raw.githubusercontent.com/SimonGolms/ods-jenkins-agent-nodejs/main/jenkins-agent-nodejs-16-template.yaml | oc create -f -\n```\n\nFor more information about the Jenkins agent, see: \u003chttps://github.com/SimonGolms/ods-jenkins-agent-nodejs\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eThe Release Manager finishes the release to the \u003ccode\u003eqa\u003c/code\u003e/\u003ccode\u003etest\u003c/code\u003e environment with the following yellow message: \u003ccode\u003eFinished: UNSTABLE\u003c/code\u003e\u003c/summary\u003e\n\nThis state is already set at the beginning by the following message in the Jenkins log: `WARN: app@\u003cGIT-HASH-1\u003e is NOT a descendant of \u003cGIT-HASH-2\u003e, which has previously been promoted to 'Q'. If \u003cGIT-HASH-2\u003e has been promoted to 'P' as well, promotion to 'P' will fail. Proceed with caution.`\n\nBefore you can deploy a release into `qa`/`test` environment, you need to merge the release branch into your master branch to pass the checks in the Jenkins shared library stage [`odsOrchestrationPipeline`](https://github.com/opendevstack/ods-jenkins-shared-library/blob/4.x/vars/odsOrchestrationPipeline.groovy), see comment in [`metadata.yml`](./metadata.yml) for more details:\n\n**Solution:**\n\n1. Merge release branch into master\n\n   ```sh\n   # Switch to master branch\n   git checkout master\n\n   # Merge the remote release branch into master without opening a text editor and accept the auto-generated message\n   git merge origin/release/\u003cVERSION\u003e --no-edit\n\n   # Push the changes to the remote repository\n   git push\n   ```\n\n2. Repeat step 1 for all other relevant code repositories which are also specified in the `metadata.yml` of the Release Manager code repository and are rolled out with helm, like a backend.\n\n3. In the Release Manager code repository fix the inconsistent ods state by deleting the `./ods-state` folder\n\n   ```sh\n   # Switch to master branch\n   git checkout master\n\n   # Fetch latest state to match the remote branch\n   git pull\n\n   # Remove inconsistent ods state\n   rm -rf ods-state\n\n   # Commit all changes\n   git commit --all --message=\"chore(ods): remove inconsistent state\"\n\n   # Push the changes to the remote repository\n   git push\n   ```\n\n4. Re-run the Release Manager pipeline with a new version. This time, be sure to merge the release branch into `master` before the further rollout towards `qa`/`test` environment!\n\n\u003c/details\u003e\n\n### WSL\n\n\u003cdetails\u003e\u003csummary\u003eWhen committing via the VS Code interface, the following error message appears \u003ccode\u003e.husky/commit-msg: 4: npx: not found\u003c/code\u003e\u003c/summary\u003e\n\nSince we are using [`nvm`](https://github.com/nvm-sh/nvm) as our versions manager for Node.js, we first need to tell husky where to find the appropriate binaries. This is done by creating and configuring the file `~/.huskyrc`. Further Information: \u003chttps://typicode.github.io/husky/#/?id=command-not-found\u003e\n\n**Solution:**\n\n1. Create `~/.huskyrc` file with `nvm` configuration\n\n   ```sh\n   cat \u003e ~/.huskyrc \u003c\u003c EOF\n   # This loads nvm.sh and sets the correct PATH before running hook\n   export NVM_DIR=\"$HOME/.nvm\"\n   [ -s \"$NVM_DIR/nvm.sh\" ] \u0026\u0026 \\. \"$NVM_DIR/nvm.sh\"\n   EOF\n   ```\n\n2. Restart VS Code\n\n\u003c/details\u003e\n\n## Author 🖊\n\n**Simon Golms:**\n\n- Digital Card: `npx simongolms`\n- Github: [@SimonGolms](https://github.com/SimonGolms)\n- Website: [gol.ms](https://gol.ms)\n\n## Contributing 🤝\n\nContributions, issues and feature requests are welcome!\n\n## Show your support 👏\n\nGive a ⭐️ if this project helped you!\n\n## License 📜\n\nCopyright © 2022 [Simon Golms](https://github.com/SimonGolms).\u003cbr /\u003e\nThis project is [Apache-2.0](https://github.com/SimonGolms/ods-quickstarter-fe-ionic-react-vite-playwright/blob/master/LICENSE) licensed.\n\n## Further Resources 📖\n\n- \u003chttps://docs.atlassian.com/bitbucket-server/rest/7.6.12/bitbucket-rest.html\u003e\n- \u003chttps://helm.sh/\u003e\n- \u003chttps://ionicframework.com\u003e\n- \u003chttps://reactjs.org\u003e\n- \u003chttps://www.opendevstack.org/\u003e\n- \u003chttps://github.com/typescript-cheatsheets/react\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimongolms%2Fods-quickstarter-fe-ionic-react-vite-playwright","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimongolms%2Fods-quickstarter-fe-ionic-react-vite-playwright","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimongolms%2Fods-quickstarter-fe-ionic-react-vite-playwright/lists"}