diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da6ed777..241c398b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,16 @@ jobs: with: java-version: 17 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: firebase-devservices/deployment/src/test/functions/package-lock.json + + - uses: bahmutov/npm-install@v1 + with: + working-directory: firebase-devservices/deployment/src/test/functions + - name: Cache local Maven repository uses: actions/cache@v2 with: @@ -33,8 +43,14 @@ jobs: ${{ runner.os }}-maven- - name: Build with Maven - run: mvn -B formatter:validate install --file pom.xml + run: | + export CURRENT_USER=$(id -u) + export CURRENT_GROUP=$(id -g) + mvn -B formatter:validate install --file pom.xml - name: Build in native - run: mvn -B -Pnative package --file integration-tests/main/pom.xml + run: | + export CURRENT_USER=$(id -u) + export CURRENT_GROUP=$(id -g) + mvn -B -Pnative package --file integration-tests/main/pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index 223f145b..b0d4b19d 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -19,6 +19,7 @@ UTF-8 26.50.0 + 9.4.2 0.31.1 @@ -37,6 +38,11 @@ pom import + + com.google.firebase + firebase-admin + ${firebase-admin-sdk.version} + @@ -138,6 +144,16 @@ quarkus-google-cloud-firebase-admin-deployment ${project.version} + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices + ${project.version} + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices-deployment + ${project.version} + io.quarkiverse.googlecloudservices quarkus-google-cloud-firestore diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 984e79fb..3d528b84 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -4,6 +4,7 @@ ** xref:index.adoc#examples[Example applications] * xref:bigquery.adoc[BigQuery] * xref:bigtable.adoc[BigTable] +* xref:firebase-devservices.adoc[Firebase Admin] * xref:firebase-admin.adoc[Firebase Admin] * xref:firestore.adoc[Firestore] * xref:logging.adoc[Logging] diff --git a/docs/modules/ROOT/pages/firebase-devservices.adoc b/docs/modules/ROOT/pages/firebase-devservices.adoc new file mode 100644 index 00000000..47fa12e3 --- /dev/null +++ b/docs/modules/ROOT/pages/firebase-devservices.adoc @@ -0,0 +1,146 @@ += Google Cloud Services - Firebase Dev Services + +This extension implements DevServices for applications developed on with the Google Firebase platform. The DevService runs the appropriate emulators of the Firebase platform based on your configuration. + +Be sure to have read the https://quarkiverse.github.io/quarkiverse-docs/quarkus-google-cloud-services/main/index.html[Google Cloud Services extension pack global documentation] before this one, it contains general configuration and information. + +== Current status + +The following emulators have been verified to work: + +* Firebase Auth +* Firebase Firestore +* Firebase Emulator UI +* Realtime Database +* PubSub +* Cloud Storage +* Firebase Hosting +* Functions + +The following emulators are currently not supported: +* EventArc + +Currently you can specify a custom `firebase.json` file but suport for this is limited. A +future version will support reading the configuration from the `firebase.json` file +instead of from the Quarkus configuration. + +== Bootstrapping the project + +First, we need a new project. Create a new project with the following command (replace the version placeholder with the correct one): + +[source, shell script] +---- +mvn io.quarkus:quarkus-maven-plugin:${quarkusVersion}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=firebase-admin-quickstart \ + -Dextensions="quarkus-google-cloud-firebase-devservices" +cd firebase-admin-quickstart +---- + +This command generates a Maven project, importing the Google Cloud Firebase extension. + +If you already have your Quarkus project configured, you can add the `quarkus-google-cloud-firebase` extension to your project by running the following command in your project base directory: + +[source, shell script] +---- +./mvnw quarkus:add-extension -Dextensions="quarkus-google-cloud-firebase" +---- + +This will add the following to your pom.xml: + +[source, xml] +---- + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase + +---- + +If you already have a firebase project (which you can create using the firebase tools by running `firebase init`), you need to make the following changes to get going: +* Add `"host" : "0.0.0.0"` to all emulator entries +* Add the hub, logging and UI emulators to the emulators configuration + +[source,json] +---- +{ + + "emulators" : { + "ui": { + "port": 4000, + "enabled": true, + "host": "0.0.0.0" + }, + "hub": { + "port": 4400, + "host": "0.0.0.0" + }, + "logging": { + "port": 4500, + "host": "0.0.0.0" + } + } +} +---- + * If you use Firestore, also set the Firestore Websocket port +[source,json] +---- +{ + "emulators" : { + "firestore": { + "port": 8080, + "websocketPort" : 9150, + "host": "0.0.0.0" + } + } +} +---- + * For both entries, you can of course use your own custom ports, where needed. + +The extension will try to read the `firebase.json` file from the current working directory the +process was started in (it will not attempt to traverse the directory upwards to try to find the +file). In some cases you may need to specify this working directory. E.g. when using Gradle: + +[source,text] +---- +quarkusDev { + workingDirectory = rootProject.projectDir +} +---- + +== Custom Docker image + +To run the emulators, a custom Docker image is build on the fly to run the Firebase emulators. This image is based on a NodeJS based image (refer to the configuration of the default value of `quarkus.google.cloud.firebase.devservice.image-name` to see the base image). + +You can configure a custom image if needed as base image to run the Firebase Emulators in. This image has the following requirements: + +* The image must support NodeJS in a version compatible with the required Firebase Tools +* The image must be `alpine` based (or at least able to install the following packages using `apk`: ) +** openjdk17-jre +** bash +** curl +** openssl +** gettext +** nano +** nginx +** sudo + +== Custom Firebase JSON + +If emulators are configured via the configuration options, a `firebase.json` file is generated inside the image to configure the various emulators. You can configure the Dev Services to use your own custom firebase.json file (e.g generated using the Firebase tools CLI). The following requirements are defined for this file: + +* Each of the emulators must be exposed on `0.0.0.0` as host as described https://firebase.google.com/docs/emulator-suite/use_hosting#emulators-no-local-host[here]. If this is not done, the Emulators will not be reachable from the Docker host. +* Emulators need to be configured to use the default ports. Customizing the ports on which they run is currently not supported (this might change in a future version). + +== Interaction with other extensions + +The following extensions support Dev Services which conflicts with the Dev Services exposed by the Firebase Emulators. + +* Firestore +* PubSub +* TODO: Verify Storage + +When including this module, these Dev Services will automatically be disabled, as the Firebase emulator should feature wise be on-par or more extensive than the individual emulators. + +== Configuration Reference + +include::./includes/quarkus-google-cloud-firebase-devservices.adoc[] diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin.adoc index 9cb12f85..6d062718 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin.adoc @@ -24,5 +24,42 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a| [[quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-emulator-host]] [.property-path]##link:#quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-emulator-host[`quarkus.google.cloud.firebase.auth.emulator-host`]## + +[.description] +-- +Sets the emulator host to use. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_EMULATOR_HOST+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_EMULATOR_HOST+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a| [[quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-use-emulator-credentials]] [.property-path]##link:#quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-use-emulator-credentials[`quarkus.google.cloud.firebase.auth.use-emulator-credentials`]## + +[.description] +-- +Forces the usage of emulator credentials. The logic automatically uses emulator credentials in case the emulatorHost is set. + + - If true: force usage of emulator credentials + - If false: force not using emulator credentials + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_USE_EMULATOR_CREDENTIALS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_USE_EMULATOR_CREDENTIALS+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + |=== diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin_quarkus.google.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin_quarkus.google.adoc index 9cb12f85..6d062718 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin_quarkus.google.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-admin_quarkus.google.adoc @@ -24,5 +24,42 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a| [[quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-emulator-host]] [.property-path]##link:#quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-emulator-host[`quarkus.google.cloud.firebase.auth.emulator-host`]## + +[.description] +-- +Sets the emulator host to use. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_EMULATOR_HOST+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_EMULATOR_HOST+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a| [[quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-use-emulator-credentials]] [.property-path]##link:#quarkus-google-cloud-firebase-admin_quarkus-google-cloud-firebase-auth-use-emulator-credentials[`quarkus.google.cloud.firebase.auth.use-emulator-credentials`]## + +[.description] +-- +Forces the usage of emulator credentials. The logic automatically uses emulator credentials in case the emulatorHost is set. + + - If true: force usage of emulator credentials + - If false: force not using emulator credentials + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_USE_EMULATOR_CREDENTIALS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_AUTH_USE_EMULATOR_CREDENTIALS+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + |=== diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc new file mode 100644 index 00000000..d180a2b2 --- /dev/null +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc @@ -0,0 +1,684 @@ +[.configuration-legend] +icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime +[.configuration-reference.searchable, cols="80,.^10,.^10"] +|=== + +h|[.header-title]##Configuration property## +h|Type +h|Default + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-project-id]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-project-id[`quarkus.google.cloud.project-id`]## + +[.description] +-- +Google Cloud project ID. The project is required to be set if you use the Firebase Auth Dev service. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_PROJECT_ID+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_PROJECT_ID+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-prefer-firebase-dev-services[`quarkus.google.cloud.devservices.firebase.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is true. This indicator is used to detect the Firebase DevService and disable the DevServices for extensions which conflict with the Firebase DevService. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-firebase-version]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-firebase-version[`quarkus.google.cloud.devservices.firebase.emulator.firebase-version`]## + +[.description] +-- +The version of the firebase tools to use. Default is to use the latest available version. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_FIREBASE_VERSION+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_FIREBASE_VERSION+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`latest` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-image-name]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-image-name[`quarkus.google.cloud.devservices.firebase.emulator.docker.image-name`]## + +[.description] +-- +Sets the Docker image name for the Google Cloud SDK. This image is used to emulate the Pub/Sub service in the development environment. The default value is 'node:23-alpine'. + +See also the documentation on Custom Docker images for more info about this image. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_IMAGE_NAME+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_IMAGE_NAME+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`node:20-alpine` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-user`]## + +[.description] +-- +Id of the docker user to run the firebase executable. This is needed in environments where Docker does not perform a mapping to the user running Docker. In a Docker Desktop setup, Docker automatically performs this mapping and the data written by the emulator can be read by the user running the build. This is not the case in a regular (non-Desktop) setup, so you may need to set the user id and `docker-group()`. This option is often needed in CI environments. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-group`]## + +[.description] +-- +Id of the group to which the `docker-user()` belongs. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user-env]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user-env[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-user-env`]## + +[.description] +-- +Try to read the `docker-user()` from an environment variable + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER_ENV+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER_ENV+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group-env]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group-env[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-group-env`]## + +[.description] +-- +Try to read the `docker-group()` from an environment variable + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP_ENV+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP_ENV+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-out]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-out[`quarkus.google.cloud.devservices.firebase.emulator.docker.follow-std-out`]## + +[.description] +-- +Pipe Stdout of the container to the Quarkus logging + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_OUT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_OUT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-err]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-err[`quarkus.google.cloud.devservices.firebase.emulator.docker.follow-std-err`]## + +[.description] +-- +Pipe Stedd of the container to the Quarkus logging + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_ERR+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_ERR+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-token]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-token[`quarkus.google.cloud.devservices.firebase.emulator.cli.token`]## + +[.description] +-- +The token to use for firebase authentication. Run `firebase login:ci` locally to get a new token. This option is mandatory if you use firebase hosting. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_TOKEN+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_TOKEN+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-java-tool-options]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-java-tool-options[`quarkus.google.cloud.devservices.firebase.emulator.cli.java-tool-options`]## + +[.description] +-- +Sets the JAVA tool options for emulators based on the Java runtime environment like -Xmx. See also link:https://firebase.google.com/docs/emulator-suite/install_and_configure#specifying_java_options[here] + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_JAVA_TOOL_OPTIONS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_JAVA_TOOL_OPTIONS+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-emulator-data]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-emulator-data[`quarkus.google.cloud.devservices.firebase.emulator.cli.emulator-data`]## + +[.description] +-- +Allow to import and export data. Specify a path relative to the current working directory of the executable (for most unit tests, this is the root of the build directory) to be used for import and export of emulator data. The data will be written to a subdirectory called "emulator-data" of this directory. See also link:https://firebase.google.com/docs/emulator-suite/install_and_configure#export_and_import_emulator_data[here] + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EMULATOR_DATA+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EMULATOR_DATA+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-import-export]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-import-export[`quarkus.google.cloud.devservices.firebase.emulator.cli.import-export`]## + +[.description] +-- +Indicate whether to import, export or both the data specified in `emulator-data()` + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_IMPORT_EXPORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_IMPORT_EXPORT+++` +endif::add-copy-button-to-env-var[] +-- +a|`import-only`, `export-only`, `import-export` +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug[`quarkus.google.cloud.devservices.firebase.emulator.cli.debug`]## + +[.description] +-- +Enable firebase emulators debugging. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_DEBUG+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_DEBUG+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-custom-firebase-json]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-custom-firebase-json[`quarkus.google.cloud.devservices.firebase.emulator.custom-firebase-json`]## + +[.description] +-- +Indicate to use a custom firebase.json file instead of the automatically generated one. The custom firebase.json file MUST include a setting of + +``` +"host" : "0.0.0.0" +``` + +to ensure the ports of the emulator are exposed correctly at the docker container level. + +See the section on Custom Firebase Json in the docs for more info. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CUSTOM_FIREBASE_JSON+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CUSTOM_FIREBASE_JSON+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-enabled[`quarkus.google.cloud.devservices.firebase.emulator.ui.enabled`]## + +[.description] +-- +Indicates whether the service should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-emulator-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-logging-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-logging-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.logging-port`]## + +[.description] +-- +Port on which to expose the logging endpoint port. This is needed in case you want to view the logging via the Emulator UI. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_LOGGING_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_LOGGING_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-hub-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-hub-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.hub-port`]## + +[.description] +-- +Port on which to expose the hub endpoint port. This is needed if you want to use the hub API of the Emulator UI. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_HUB_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_HUB_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-enabled[`quarkus.google.cloud.devservices.firebase.auth.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-emulator-port[`quarkus.google.cloud.devservices.firebase.auth.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-enabled[`quarkus.google.cloud.devservices.firebase.hosting.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-emulator-port[`quarkus.google.cloud.devservices.firebase.hosting.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-hosting-path]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-hosting-path[`quarkus.google.cloud.devservices.firebase.hosting.hosting-path`]## + +[.description] +-- +Path to the hosting files. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_HOSTING_PATH+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_HOSTING_PATH+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-enabled[`quarkus.google.cloud.devservices.firebase.database.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-emulator-port[`quarkus.google.cloud.devservices.firebase.database.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-enabled[`quarkus.google.cloud.devservices.firebase.firestore.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-emulator-port[`quarkus.google.cloud.devservices.firebase.firestore.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-websocket-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-websocket-port[`quarkus.google.cloud.devservices.firebase.firestore.websocket-port`]## + +[.description] +-- +Port on which to expose the websocket port. This is needed in case the Firestore Emulator UI needs is used. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_WEBSOCKET_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_WEBSOCKET_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-rules-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-rules-file[`quarkus.google.cloud.devservices.firebase.firestore.rules-file`]## + +[.description] +-- +Path to the firestore.rules file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_RULES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_RULES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-indexes-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-indexes-file[`quarkus.google.cloud.devservices.firebase.firestore.indexes-file`]## + +[.description] +-- +Path to the firestore.indexes.json file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_INDEXES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_INDEXES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-enabled[`quarkus.google.cloud.devservices.functions.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-emulator-port[`quarkus.google.cloud.devservices.functions.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-enabled[`quarkus.google.cloud.devservices.pubsub.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-emulator-port[`quarkus.google.cloud.devservices.pubsub.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-enabled[`quarkus.google.cloud.devservices.storage.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-emulator-port[`quarkus.google.cloud.devservices.storage.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-rules-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-rules-file[`quarkus.google.cloud.devservices.storage.rules-file`]## + +[.description] +-- +Path to the storage.rules file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_RULES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_RULES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +|=== + diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc new file mode 100644 index 00000000..d180a2b2 --- /dev/null +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc @@ -0,0 +1,684 @@ +[.configuration-legend] +icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime +[.configuration-reference.searchable, cols="80,.^10,.^10"] +|=== + +h|[.header-title]##Configuration property## +h|Type +h|Default + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-project-id]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-project-id[`quarkus.google.cloud.project-id`]## + +[.description] +-- +Google Cloud project ID. The project is required to be set if you use the Firebase Auth Dev service. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_PROJECT_ID+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_PROJECT_ID+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-prefer-firebase-dev-services[`quarkus.google.cloud.devservices.firebase.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is true. This indicator is used to detect the Firebase DevService and disable the DevServices for extensions which conflict with the Firebase DevService. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-firebase-version]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-firebase-version[`quarkus.google.cloud.devservices.firebase.emulator.firebase-version`]## + +[.description] +-- +The version of the firebase tools to use. Default is to use the latest available version. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_FIREBASE_VERSION+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_FIREBASE_VERSION+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`latest` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-image-name]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-image-name[`quarkus.google.cloud.devservices.firebase.emulator.docker.image-name`]## + +[.description] +-- +Sets the Docker image name for the Google Cloud SDK. This image is used to emulate the Pub/Sub service in the development environment. The default value is 'node:23-alpine'. + +See also the documentation on Custom Docker images for more info about this image. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_IMAGE_NAME+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_IMAGE_NAME+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`node:20-alpine` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-user`]## + +[.description] +-- +Id of the docker user to run the firebase executable. This is needed in environments where Docker does not perform a mapping to the user running Docker. In a Docker Desktop setup, Docker automatically performs this mapping and the data written by the emulator can be read by the user running the build. This is not the case in a regular (non-Desktop) setup, so you may need to set the user id and `docker-group()`. This option is often needed in CI environments. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-group`]## + +[.description] +-- +Id of the group to which the `docker-user()` belongs. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user-env]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-user-env[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-user-env`]## + +[.description] +-- +Try to read the `docker-user()` from an environment variable + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER_ENV+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_USER_ENV+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group-env]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-docker-group-env[`quarkus.google.cloud.devservices.firebase.emulator.docker.docker-group-env`]## + +[.description] +-- +Try to read the `docker-group()` from an environment variable + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP_ENV+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_DOCKER_GROUP_ENV+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-out]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-out[`quarkus.google.cloud.devservices.firebase.emulator.docker.follow-std-out`]## + +[.description] +-- +Pipe Stdout of the container to the Quarkus logging + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_OUT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_OUT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-err]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-docker-follow-std-err[`quarkus.google.cloud.devservices.firebase.emulator.docker.follow-std-err`]## + +[.description] +-- +Pipe Stedd of the container to the Quarkus logging + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_ERR+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_DOCKER_FOLLOW_STD_ERR+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-token]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-token[`quarkus.google.cloud.devservices.firebase.emulator.cli.token`]## + +[.description] +-- +The token to use for firebase authentication. Run `firebase login:ci` locally to get a new token. This option is mandatory if you use firebase hosting. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_TOKEN+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_TOKEN+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-java-tool-options]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-java-tool-options[`quarkus.google.cloud.devservices.firebase.emulator.cli.java-tool-options`]## + +[.description] +-- +Sets the JAVA tool options for emulators based on the Java runtime environment like -Xmx. See also link:https://firebase.google.com/docs/emulator-suite/install_and_configure#specifying_java_options[here] + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_JAVA_TOOL_OPTIONS+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_JAVA_TOOL_OPTIONS+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-emulator-data]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-emulator-data[`quarkus.google.cloud.devservices.firebase.emulator.cli.emulator-data`]## + +[.description] +-- +Allow to import and export data. Specify a path relative to the current working directory of the executable (for most unit tests, this is the root of the build directory) to be used for import and export of emulator data. The data will be written to a subdirectory called "emulator-data" of this directory. See also link:https://firebase.google.com/docs/emulator-suite/install_and_configure#export_and_import_emulator_data[here] + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EMULATOR_DATA+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EMULATOR_DATA+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-import-export]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-import-export[`quarkus.google.cloud.devservices.firebase.emulator.cli.import-export`]## + +[.description] +-- +Indicate whether to import, export or both the data specified in `emulator-data()` + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_IMPORT_EXPORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_IMPORT_EXPORT+++` +endif::add-copy-button-to-env-var[] +-- +a|`import-only`, `export-only`, `import-export` +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug[`quarkus.google.cloud.devservices.firebase.emulator.cli.debug`]## + +[.description] +-- +Enable firebase emulators debugging. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_DEBUG+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_DEBUG+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-custom-firebase-json]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-custom-firebase-json[`quarkus.google.cloud.devservices.firebase.emulator.custom-firebase-json`]## + +[.description] +-- +Indicate to use a custom firebase.json file instead of the automatically generated one. The custom firebase.json file MUST include a setting of + +``` +"host" : "0.0.0.0" +``` + +to ensure the ports of the emulator are exposed correctly at the docker container level. + +See the section on Custom Firebase Json in the docs for more info. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CUSTOM_FIREBASE_JSON+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CUSTOM_FIREBASE_JSON+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-enabled[`quarkus.google.cloud.devservices.firebase.emulator.ui.enabled`]## + +[.description] +-- +Indicates whether the service should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-emulator-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-logging-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-logging-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.logging-port`]## + +[.description] +-- +Port on which to expose the logging endpoint port. This is needed in case you want to view the logging via the Emulator UI. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_LOGGING_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_LOGGING_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-hub-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-ui-hub-port[`quarkus.google.cloud.devservices.firebase.emulator.ui.hub-port`]## + +[.description] +-- +Port on which to expose the hub endpoint port. This is needed if you want to use the hub API of the Emulator UI. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_HUB_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_UI_HUB_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-enabled[`quarkus.google.cloud.devservices.firebase.auth.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-auth-emulator-port[`quarkus.google.cloud.devservices.firebase.auth.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_AUTH_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-enabled[`quarkus.google.cloud.devservices.firebase.hosting.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-emulator-port[`quarkus.google.cloud.devservices.firebase.hosting.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-hosting-path]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-hosting-hosting-path[`quarkus.google.cloud.devservices.firebase.hosting.hosting-path`]## + +[.description] +-- +Path to the hosting files. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_HOSTING_PATH+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_HOSTING_HOSTING_PATH+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-enabled[`quarkus.google.cloud.devservices.firebase.database.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-database-emulator-port[`quarkus.google.cloud.devservices.firebase.database.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_DATABASE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-enabled[`quarkus.google.cloud.devservices.firebase.firestore.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-emulator-port[`quarkus.google.cloud.devservices.firebase.firestore.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-websocket-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-websocket-port[`quarkus.google.cloud.devservices.firebase.firestore.websocket-port`]## + +[.description] +-- +Port on which to expose the websocket port. This is needed in case the Firestore Emulator UI needs is used. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_WEBSOCKET_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_WEBSOCKET_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-rules-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-rules-file[`quarkus.google.cloud.devservices.firebase.firestore.rules-file`]## + +[.description] +-- +Path to the firestore.rules file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_RULES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_RULES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-indexes-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-firestore-indexes-file[`quarkus.google.cloud.devservices.firebase.firestore.indexes-file`]## + +[.description] +-- +Path to the firestore.indexes.json file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_INDEXES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_FIRESTORE_INDEXES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-enabled[`quarkus.google.cloud.devservices.functions.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-functions-emulator-port[`quarkus.google.cloud.devservices.functions.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FUNCTIONS_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-enabled[`quarkus.google.cloud.devservices.pubsub.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-pubsub-emulator-port[`quarkus.google.cloud.devservices.pubsub.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_PUBSUB_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-enabled]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-enabled[`quarkus.google.cloud.devservices.storage.enabled`]## + +[.description] +-- +Indicates whether the DevService should be enabled or not. The default value is 'false'. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_ENABLED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-emulator-port]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-emulator-port[`quarkus.google.cloud.devservices.storage.emulator-port`]## + +[.description] +-- +Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_EMULATOR_PORT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_EMULATOR_PORT+++` +endif::add-copy-button-to-env-var[] +-- +|int +| + +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-rules-file]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-storage-rules-file[`quarkus.google.cloud.devservices.storage.rules-file`]## + +[.description] +-- +Path to the storage.rules file. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_RULES_FILE+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_STORAGE_RULES_FILE+++` +endif::add-copy-button-to-env-var[] +-- +|string +| + +|=== + diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore.adoc index f64c005f..99003066 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore.adoc @@ -7,6 +7,23 @@ h|[.header-title]##Configuration property## h|Type h|Default +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firestore_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-firestore_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services[`quarkus.google.cloud.firebase.devservice.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module is included. In that case, the Firebase devservices will by default be preferred and the DevService for PubSub will be disabled. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firestore_quarkus-google-cloud-firestore-devservice-enabled]] [.property-path]##link:#quarkus-google-cloud-firestore_quarkus-google-cloud-firestore-devservice-enabled[`quarkus.google.cloud.firestore.devservice.enabled`]## [.description] diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore_quarkus.google.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore_quarkus.google.adoc index f64c005f..99003066 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore_quarkus.google.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firestore_quarkus.google.adoc @@ -7,6 +7,23 @@ h|[.header-title]##Configuration property## h|Type h|Default +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firestore_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-firestore_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services[`quarkus.google.cloud.firebase.devservice.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module is included. In that case, the Firebase devservices will by default be preferred and the DevService for PubSub will be disabled. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firestore_quarkus-google-cloud-firestore-devservice-enabled]] [.property-path]##link:#quarkus-google-cloud-firestore_quarkus-google-cloud-firestore-devservice-enabled[`quarkus.google.cloud.firestore.devservice.enabled`]## [.description] diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub.adoc index fe0e1300..22dae261 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub.adoc @@ -58,6 +58,23 @@ endif::add-copy-button-to-env-var[] |int | +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-pubsub_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-pubsub_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services[`quarkus.google.cloud.firebase.devservice.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module is included. In that case, the Firebase devservices will by default be preferred and the DevService for PubSub will be disabled. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + a| [[quarkus-google-cloud-pubsub_quarkus-google-cloud-pubsub-emulator-host]] [.property-path]##link:#quarkus-google-cloud-pubsub_quarkus-google-cloud-pubsub-emulator-host[`quarkus.google.cloud.pubsub.emulator-host`]## [.description] diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub_quarkus.google.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub_quarkus.google.adoc index fe0e1300..22dae261 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub_quarkus.google.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-pubsub_quarkus.google.adoc @@ -58,6 +58,23 @@ endif::add-copy-button-to-env-var[] |int | +a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-pubsub_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services]] [.property-path]##link:#quarkus-google-cloud-pubsub_quarkus-google-cloud-firebase-devservice-prefer-firebase-dev-services[`quarkus.google.cloud.firebase.devservice.prefer-firebase-dev-services`]## + +[.description] +-- +Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module is included. In that case, the Firebase devservices will by default be preferred and the DevService for PubSub will be disabled. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_GOOGLE_CLOUD_FIREBASE_DEVSERVICE_PREFER_FIREBASE_DEV_SERVICES+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +| + a| [[quarkus-google-cloud-pubsub_quarkus-google-cloud-pubsub-emulator-host]] [.property-path]##link:#quarkus-google-cloud-pubsub_quarkus-google-cloud-pubsub-emulator-host[`quarkus.google.cloud.pubsub.emulator-host`]## [.description] diff --git a/firebase-admin/runtime/pom.xml b/firebase-admin/runtime/pom.xml index 03aeb3a8..37db70ae 100644 --- a/firebase-admin/runtime/pom.xml +++ b/firebase-admin/runtime/pom.xml @@ -13,7 +13,6 @@ Use Google Cloud Firebase Admin to perform privileged actions - 9.4.2 2.1 @@ -25,7 +24,6 @@ com.google.firebase firebase-admin - ${firebase-admin-sdk.version} diff --git a/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAdminProducer.java b/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAdminProducer.java index bb95956d..f3246012 100644 --- a/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAdminProducer.java +++ b/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAdminProducer.java @@ -2,6 +2,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -11,23 +12,35 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.internal.Utils; +import com.google.firebase.internal.EmulatorCredentials; +import com.google.firebase.internal.FirebaseProcessEnvironment; import io.quarkiverse.googlecloudservices.common.GcpBootstrapConfiguration; import io.quarkiverse.googlecloudservices.common.GcpConfigHolder; +import io.smallrye.config.ConfigMapping; @ApplicationScoped public class FirebaseAdminProducer { @Inject - Credentials googleCredentials; + Instance googleCredentials; @Inject GcpConfigHolder gcpConfigHolder; + @Inject + FirebaseAuthConfig firebaseAuthConfig; + @Produces @Singleton @Default - public FirebaseAuth firestoreAuth(FirebaseApp firebaseApp) { + public FirebaseAuth firestoreAuth(@ConfigMapping FirebaseAuthConfig firebaseAuthConfig, FirebaseApp firebaseApp) { + + // Configure the Firebase emulator to use. + firebaseAuthConfig.auth().emulatorHost() + .ifPresent(host -> FirebaseProcessEnvironment.setenv(Utils.AUTH_EMULATOR_HOST, host)); + return FirebaseAuth.getInstance(firebaseApp); } @@ -38,7 +51,7 @@ public FirebaseApp getFirebaseApp() { GcpBootstrapConfiguration gcpConfiguration = gcpConfigHolder.getBootstrapConfig(); FirebaseOptions firebaseOptions = FirebaseOptions.builder() - .setCredentials((GoogleCredentials) googleCredentials) + .setCredentials(googleCredentials()) .setProjectId(gcpConfiguration.projectId().orElse(null)) .build(); @@ -48,6 +61,14 @@ public FirebaseApp getFirebaseApp() { .orElseGet(() -> initializeFirebaseApp(gcpConfiguration, firebaseOptions)); } + private GoogleCredentials googleCredentials() { + if (firebaseAuthConfig.auth().emulatorHost().isPresent() && firebaseAuthConfig.auth().useEmulatorCredentials()) { + return new EmulatorCredentials(); + } else { + return (GoogleCredentials) googleCredentials.get(); + } + } + private FirebaseApp initializeFirebaseApp(GcpBootstrapConfiguration gcpBootstrapConfiguration, FirebaseOptions firebaseOptions) { return gcpBootstrapConfiguration.projectId() diff --git a/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAuthConfig.java b/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAuthConfig.java new file mode 100644 index 00000000..03dddb46 --- /dev/null +++ b/firebase-admin/runtime/src/main/java/io/quarkiverse/googlecloudservices/firebase/admin/runtime/FirebaseAuthConfig.java @@ -0,0 +1,45 @@ +package io.quarkiverse.googlecloudservices.firebase.admin.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +/** + * Root configuration class for Google Cloud Firebase Auth setup. + *

+ * This interface mostly provides access to validate whether the Firebase Auth + * Emulator is running. + * + */ +@ConfigMapping(prefix = "quarkus.google.cloud.firebase") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public interface FirebaseAuthConfig { + + /** + * Returns the auth configuration + */ + AuthConfig auth(); + + public interface AuthConfig { + /** + * Sets the emulator host to use. + */ + Optional emulatorHost(); + + /** + * Forces the usage of emulator credentials. The logic automatically uses emulator credentials in case + * the emulatorHost is set. + *

    + *
  • If true: force usage of emulator credentials
  • + *
  • If false: force not using emulator credentials
  • + *
+ */ + @WithDefault("true") + boolean useEmulatorCredentials(); + + } + +} diff --git a/firebase-devservices/README.md b/firebase-devservices/README.md new file mode 100644 index 00000000..d45ed50c --- /dev/null +++ b/firebase-devservices/README.md @@ -0,0 +1,5 @@ +# Quarkus - Google Cloud Services - Firebase + +This extension provides a DevService for the Firebase. + +You can find the documentation in the [Firebase Quarkiverse documentation site](https://quarkiverse.github.io/quarkiverse-docs/quarkus-google-cloud-services/main/firebase.html). diff --git a/firebase-devservices/deployment/pom.xml b/firebase-devservices/deployment/pom.xml new file mode 100644 index 00000000..f8f5b649 --- /dev/null +++ b/firebase-devservices/deployment/pom.xml @@ -0,0 +1,158 @@ + + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices-parent + 2.14.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-google-cloud-firebase-devservices-deployment + Quarkus - Google Cloud Services - Firebase Dev Services - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-common-deployment + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices + + + org.testcontainers + testcontainers + + + + + + org.testcontainers + junit-jupiter + test + + + com.google.firebase + firebase-admin + test + + + + io.grpc + grpc-netty-shaded + + + + + io.quarkus + quarkus-grpc-common + test + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-common-grpc + test + + + com.google.cloud + google-cloud-firestore + test + + + commons-logging + commons-logging + + + javax.annotation + javax.annotation-api + + + org.checkerframework + checker-qual + + + + io.grpc + grpc-netty-shaded + + + org.codehaus.mojo + animal-sniffer-annotations + + + + + com.google.cloud + google-cloud-pubsub + test + + + commons-logging + commons-logging + + + javax.annotation + javax.annotation-api + + + org.checkerframework + checker-qual + + + + io.grpc + grpc-netty-shaded + + + org.codehaus.mojo + animal-sniffer-annotations + + + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + org.jsonschema2pojo + jsonschema2pojo-maven-plugin + 1.2.2 + + ${basedir}/src/main/resources/META-INF/schema + io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.json + jackson2 + true + + + + + + generate + + + + + + + + \ No newline at end of file diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseBuildSteps.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseBuildSteps.java new file mode 100644 index 00000000..96bbc13c --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseBuildSteps.java @@ -0,0 +1,14 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +public class FirebaseBuildSteps { + + protected static final String FEATURE = "google-cloud-firebase"; + + @BuildStep + public FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java new file mode 100644 index 00000000..08b58ab6 --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java @@ -0,0 +1,293 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import java.util.Optional; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +/** + * Root configuration class for Google Cloud Firebase that operates at build time. + * This class provides a nested structure for configuration, including + * a separate group for the development service configuration. + *

+ * Here is an example of how to configure these properties: + *

+ * + *

+ * quarkus.google.cloud.firebase.devservice.enabled = true
+ * quarkus.google.cloud.pubsub.devservice.image-name = gcr.io/google.com/cloudsdktool/google-cloud-cli # optional
+ * quarkus.google.cloud.pubsub.devservice.emulatorPort = 8085 # optional
+ * 
+ */ +@ConfigMapping(prefix = "quarkus.google.cloud.devservices") +@ConfigRoot +public interface FirebaseDevServiceConfig { + + /** + * Configure the Firebase-based services + */ + Firebase firebase(); + + /** + * Configuration for the Functions emulator + */ + GenericDevService functions(); + + /** + * Configuration for the Google Cloud PubSub emulator + */ + GenericDevService pubsub(); + + /** + * Configuration for the storage emulator + */ + StorageDevService storage(); + + interface Firebase { + + /** + * Indicates to use the dev service for Firebase. The default value is true. This indicator is used + * to detect the Firebase DevService and disable the DevServices for extensions which conflict with the + * Firebase DevService. + */ + @WithDefault("true") + boolean preferFirebaseDevServices(); + + /** + * Configuration for the firebase emulator devservice. This is the generic configuration for the firebase + * emulator. THe specifics are handled in each of the other dev services. + */ + Emulator emulator(); + + /** + * Configuration for the firebase auth emulator + */ + GenericDevService auth(); + + /** + * Configure Firebase Hosting + */ + HostingDevService hosting(); + + /** + * Configuration for the realtime database emulator + */ + GenericDevService database(); + + /** + * Configure the firestore + */ + FirestoreDevService firestore(); + + interface Emulator { + + /** + * The version of the firebase tools to use. Default is to use the latest available version. + */ + @WithDefault(FirebaseEmulatorContainer.DEFAULT_FIREBASE_VERSION) + String firebaseVersion(); + + /** + * Docker specific settings + */ + Docker docker(); + + /** + * The ClI settings + */ + Cli cli(); + + /** + * Indicate to use a custom firebase.json file instead of the automatically generated one. The custom + * firebase.json file MUST include a setting of + * + *
+             * "host" : "0.0.0.0"
+             * 
+ * + * to ensure the ports of the + * emulator are exposed correctly at the docker container level. + *

+ * See the section on Custom Firebase Json in the docs for more info. + */ + Optional customFirebaseJson(); + + /** + * Settings for the emulator UI + */ + UI ui(); + + interface Docker { + /** + * Sets the Docker image name for the Google Cloud SDK. + * This image is used to emulate the Pub/Sub service in the development environment. + * The default value is 'node:23-alpine'. + *

+ * See also the documentation on Custom Docker images for more info about this image. + */ + @WithDefault(FirebaseEmulatorContainer.DEFAULT_IMAGE_NAME) + String imageName(); + + /** + * Id of the docker user to run the firebase executable. This is needed in environments where Docker + * does not perform a mapping to the user running Docker. In a Docker Desktop setup, Docker + * automatically performs this mapping and the data written by the emulator can be read by the user + * running the build. This is not the case in a regular (non-Desktop) setup, + * so you may need to set the user id and {@link #dockerGroup()}. This option is often needed in CI + * environments. + */ + Optional dockerUser(); + + /** + * Id of the group to which the {@link #dockerUser()} belongs. + */ + Optional dockerGroup(); + + /** + * Try to read the {@link #dockerUser()} from an environment variable + */ + Optional dockerUserEnv(); + + /** + * Try to read the {@link #dockerGroup()} from an environment variable + */ + Optional dockerGroupEnv(); + + /** + * Pipe Stdout of the container to the Quarkus logging + */ + Optional followStdOut(); + + /** + * Pipe Stedd of the container to the Quarkus logging + */ + Optional followStdErr(); + + } + + /** + * Configuration options related to the Firebase emulators CLI + */ + interface Cli { + /** + * The token to use for firebase authentication. Run `firebase login:ci` locally to get a new token. This + * option is mandatory if you use firebase hosting. + */ + Optional token(); + + /** + * Sets the JAVA tool options for emulators based on the Java runtime environment like -Xmx. + * See also + * here + */ + Optional javaToolOptions(); + + /** + * Allow to import and export data. Specify a path relative to the current working directory of the executable + * (for most unit tests, this is the root of the build directory) to be used for import and export of emulator + * data. The data will be written to a subdirectory called "emulator-data" of this directory. + * See also here + */ + Optional emulatorData(); + + /** + * Indicate whether to import, export or both the data specified in {@link #emulatorData()} + */ + Optional importExport(); + + /** + * Enable firebase emulators debugging. + */ + Optional debug(); + + } + + interface UI extends GenericDevService { + + /** + * Indicates whether the service should be enabled or not. + * The default value is 'false'. + */ + @WithDefault("true") + @Override + boolean enabled(); + + /** + * Port on which to expose the logging endpoint port. This is needed in case you want to view the logging + * via the Emulator UI. + */ + Optional loggingPort(); + + /** + * Port on which to expose the hub endpoint port. This is needed if you want to use the hub API of + * the Emulator UI. + */ + Optional hubPort(); + } + + } + + interface HostingDevService extends GenericDevService { + + /** + * Path to the hosting files. + */ + Optional hostingPath(); + } + + /** + * Extension for the Firestore dev service. This service can also configure the websocket port. + */ + interface FirestoreDevService extends GenericDevService { + + /** + * Port on which to expose the websocket port. This is needed in case the Firestore Emulator UI needs is + * used. + */ + Optional websocketPort(); + + /** + * Path to the firestore.rules file. + */ + Optional rulesFile(); + + /** + * Path to the firestore.indexes.json file. + */ + Optional indexesFile(); + } + } + + interface StorageDevService extends GenericDevService { + + /** + * Path to the storage.rules file. + */ + Optional rulesFile(); + } + + /** + * Internal interface representing a dev service for each of the different emulators part of the Firebase + * platform. + */ + interface GenericDevService { + + /** + * Indicates whether the DevService should be enabled or not. + * The default value is 'false'. + */ + @WithDefault("false") + boolean enabled(); + + /** + * Specifies the emulatorPort on which the service should run in the development environment. The default + * is to expose the service on a random port. + */ + Optional emulatorPort(); + } + +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java new file mode 100644 index 00000000..3f39cf72 --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java @@ -0,0 +1,196 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; + +import org.jboss.logging.Logger; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.*; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; +import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; + +/** + * Processor responsible for managing Firebase Dev Services. + *

+ * The processor starts the Firebase service in case it's not running. + */ +@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) +public class FirebaseDevServiceProcessor { + + private static final Logger LOGGER = Logger.getLogger(FirebaseDevServiceProcessor.class.getName()); + + // Running dev service instance + private static volatile DevServicesResultBuildItem.RunningDevService devService; + // Configuration for the Firebase Dev service + private static volatile FirebaseDevServiceConfig config; + + private static final Map CONFIG_PROPERTIES = Map.of( + FirebaseEmulatorContainer.Emulator.AUTHENTICATION, "quarkus.google.cloud.firebase.auth.emulator-host", + FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI, "quarkus.google.cloud.firebase.emulator-host", + FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING, "quarkus.google.cloud.firebase.hosting.emulator-host", + FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS, "quarkus.google.cloud.functions.emulator-host", + FirebaseEmulatorContainer.Emulator.EVENT_ARC, "quarkus.google.cloud.eventarc.emulator-host", + FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE, "quarkus.google.cloud.database.emulator-host", + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE, "quarkus.google.cloud.firestore.host-override", + FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE, "quarkus.google.cloud.storage.host-override", + FirebaseEmulatorContainer.Emulator.PUB_SUB, "quarkus.google.cloud.pubsub.emulator-host"); + + @BuildStep + public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildItem, + FirebaseDevServiceProjectConfig projectConfig, + FirebaseDevServiceConfig firebaseBuildTimeConfig, + List devServicesSharedNetworkBuildItem, + Optional consoleInstalledBuildItem, + CuratedApplicationShutdownBuildItem closeBuildItem, + LaunchModeBuildItem launchMode, + LoggingSetupBuildItem loggingSetupBuildItem, + GlobalDevServicesConfig globalDevServicesConfig) { + // If dev service is running and config has changed, stop the service + if (devService != null && !firebaseBuildTimeConfig.equals(config)) { + stopContainer(); + } else if (devService != null) { + return devService.toBuildItem(); + } + + // Set up log compressor for startup logs + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Google Cloud Firebase Dev Services Starting:", + consoleInstalledBuildItem, + loggingSetupBuildItem); + + // Try starting the container if conditions are met + try { + devService = startContainerIfAvailable(dockerStatusBuildItem, projectConfig, firebaseBuildTimeConfig, + globalDevServicesConfig.timeout); + } catch (Throwable t) { + LOGGER.warn("Unable to start Firebase dev service", t); + // Dump captured logs in case of an error + compressor.closeAndDumpCaptured(); + return null; + } finally { + compressor.close(); + } + + return devService == null ? null : devService.toBuildItem(); + } + + /** + * Start the container if conditions are met. + * + * @param dockerStatusBuildItem, Docker status + * @param config, Configuration for the Firebase service + * @param timeout, Optional timeout for starting the service + * @return Running service item, or null if the service couldn't be started + */ + private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(DockerStatusBuildItem dockerStatusBuildItem, + FirebaseDevServiceProjectConfig projectConfig, + FirebaseDevServiceConfig config, + Optional timeout) { + + if (!config.firebase().preferFirebaseDevServices()) { + // Firebase service explicitly disabled + LOGGER.info("Not starting Dev Services for Firebase as it has been disabled in the config."); + return null; + } + + if (!isEnabled(config)) { + // Firebase service implicitly disabled, no emulators enabled. + LOGGER.info("Not starting Dev Services for Firebase as no emulators are enabled."); + return null; + } + + if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) { + LOGGER.info("Not starting DevService because docker is not available"); + return null; + } + + return startContainer(dockerStatusBuildItem, projectConfig, config, timeout); + } + + private boolean isEnabled(FirebaseDevServiceConfig config) { + return FirebaseEmulatorConfigBuilder.devServices(config) + .values() + .stream() + .map(FirebaseDevServiceConfig.GenericDevService::enabled) + .reduce(Boolean.FALSE, Boolean::logicalOr); + } + + /** + * Starts the Pub/Sub emulator container with provided configuration. + * + * @param dockerStatusBuildItem, Docker status + * @param config, Configuration for the Firebase service + * @param timeout, Optional timeout for starting the service + * @return Running service item, or null if the service couldn't be started + */ + private DevServicesResultBuildItem.RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuildItem, + FirebaseDevServiceProjectConfig projectConfig, + FirebaseDevServiceConfig config, + Optional timeout) { + + // Create and configure Firebase emulator container + var emulatorContainer = new FirebaseEmulatorConfigBuilder(projectConfig, config).build(); + + // Set container startup timeout if provided + timeout.ifPresent(emulatorContainer::withStartupTimeout); + emulatorContainer.start(); + + // Set the config for the started container + FirebaseDevServiceProcessor.config = config; + + var emulatorContainerConfig = emulatorContainerConfig(emulatorContainer); + + if (LOGGER.isInfoEnabled()) { + var runningPorts = emulatorContainer.emulatorPorts(); + runningPorts.forEach((e, p) -> LOGGER.info("Google Cloud Emulator " + e + " reachable on port " + p)); + + emulatorContainerConfig + .forEach((e, h) -> LOGGER.info("Google Cloud emulator config property " + e + " set to " + h)); + } + + // Return running service item with container details + return new DevServicesResultBuildItem.RunningDevService(FirebaseBuildSteps.FEATURE, + emulatorContainer.getContainerId(), + emulatorContainer::close, + emulatorContainerConfig); + } + + private Map emulatorContainerConfig(FirebaseEmulatorContainer emulatorContainer) { + return emulatorContainer.emulatorEndpoints() + .entrySet() + .stream() + .filter(e -> CONFIG_PROPERTIES.containsKey(e.getKey())) + .collect( + Collectors.toMap( + e -> configPropertyForEmulator(e.getKey()), + Map.Entry::getValue)); + } + + private String configPropertyForEmulator(FirebaseEmulatorContainer.Emulator emulator) { + return CONFIG_PROPERTIES.get(emulator); + } + + /** + * Stops the running Firebase emulator container. + */ + private void stopContainer() { + if (devService != null && devService.isOwner()) { + try { + // Try closing the running dev service + devService.close(); + } catch (Throwable e) { + LOGGER.error("Failed to stop firebase container", e); + } finally { + devService = null; + } + } + } + +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProjectConfig.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProjectConfig.java new file mode 100644 index 00000000..5ab503dd --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProjectConfig.java @@ -0,0 +1,20 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +/** + * Temporary Config root to retrieve the project id for the Firebase Emulator Container. We will remove this interface + * in the future in favour of using the common setup. + */ +@ConfigMapping(prefix = "quarkus.google.cloud.devservices") +@ConfigRoot +public interface FirebaseDevServiceProjectConfig { + + /** + * Google Cloud project ID. The project is required to be set if you use the Firebase Auth Dev service. + */ + Optional projectId(); +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java new file mode 100644 index 00000000..e3ed09aa --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java @@ -0,0 +1,176 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer; + +/** + * This class translates the Quarkus Firebase extension configuration to the {@link FirebaseEmulatorContainer} + * instance. + */ +public class FirebaseEmulatorConfigBuilder { + + private final FirebaseDevServiceProjectConfig projectConfig; + private final FirebaseDevServiceConfig config; + + public static Map devServices( + FirebaseDevServiceConfig config) { + return Map.of( + FirebaseEmulatorContainer.Emulator.AUTHENTICATION, config.firebase().auth(), + FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI, config.firebase().emulator().ui(), + FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS, config.functions(), + FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE, config.firebase().database(), + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE, config.firebase().firestore(), + FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE, config.storage(), + FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING, config.firebase().hosting(), + FirebaseEmulatorContainer.Emulator.PUB_SUB, config.pubsub()); + } + + public FirebaseEmulatorConfigBuilder(FirebaseDevServiceProjectConfig projectConfig, FirebaseDevServiceConfig config) { + this.projectConfig = projectConfig; + this.config = config; + } + + public FirebaseEmulatorContainer build() { + return configureBuilder().build(); + } + + FirebaseEmulatorContainer.EmulatorConfig buildConfig() { + return configureBuilder().buildConfig(); + } + + private FirebaseEmulatorContainer.Builder configureBuilder() { + var builder = FirebaseEmulatorContainer.builder(); + + builder.withFirebaseVersion(config.firebase().emulator().firebaseVersion()); + + handleDockerConfig(config.firebase().emulator().docker(), builder); + handleCliConfig(config.firebase().emulator().cli(), builder); + handleEmulators(builder); + + return builder; + } + + private void handleDockerConfig(FirebaseDevServiceConfig.Firebase.Emulator.Docker docker, + FirebaseEmulatorContainer.Builder builder) { + var dockerConfig = builder.withDockerConfig(); + + dockerConfig.withImage(docker.imageName()); + docker.dockerUser().ifPresent(dockerConfig::withUserId); + docker.dockerGroup().ifPresent(dockerConfig::withGroupId); + docker.dockerUserEnv().ifPresent(dockerConfig::withUserIdFromEnv); + docker.dockerGroupEnv().ifPresent(dockerConfig::withGroupIdFromEnv); + docker.followStdOut().ifPresent(dockerConfig::followStdOut); + docker.followStdErr().ifPresent(dockerConfig::followStdErr); + + dockerConfig.done(); + } + + private void handleCliConfig(FirebaseDevServiceConfig.Firebase.Emulator.Cli cli, + FirebaseEmulatorContainer.Builder builder) { + var cliConfig = builder.withCliArguments(); + + projectConfig.projectId().ifPresent(cliConfig::withProjectId); + + cli.token().ifPresent(cliConfig::withToken); + cli.javaToolOptions().ifPresent(cliConfig::withJavaToolOptions); + cli.emulatorData().map(FirebaseEmulatorConfigBuilder::asPath).ifPresent(cliConfig::withEmulatorData); + cli.importExport().ifPresent(cliConfig::withImportExport); + cli.debug().ifPresent(cliConfig::withDebug); + + cliConfig.done(); + } + + private void handleEmulators(FirebaseEmulatorContainer.Builder builder) { + config.firebase().emulator().customFirebaseJson().ifPresentOrElse( + (firebaseJson) -> configureCustomFirebaseJson(builder, firebaseJson), + () -> configureEmulators(builder)); + } + + private void configureCustomFirebaseJson(FirebaseEmulatorContainer.Builder builder, String firebaseJson) { + try { + builder.readFromFirebaseJson(asPath(firebaseJson)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void configureEmulators(FirebaseEmulatorContainer.Builder builder) { + var devServices = devServices(config); + + var noEmulatorsConfigured = devServices + .entrySet() + .stream() + .filter(e -> e.getValue().enabled()) + // Emulator Suite UI is enabled by default, so ignore it. + .filter(e -> !e.getKey().equals(FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI)) + .findAny() + .isEmpty(); + + // No emulators configured via configuration, we will fallback to the automatic detection of a firebase.json file. + if (noEmulatorsConfigured) { + return; + } + + var firebaseConfigBuilder = builder.withFirebaseConfig(); + devServices + .entrySet() + .stream() + .filter(e -> e.getValue().enabled()) + .forEach(e -> e.getValue().emulatorPort().ifPresentOrElse( + p -> firebaseConfigBuilder.withEmulatorOnFixedPort(e.getKey(), p), + () -> firebaseConfigBuilder.withEmulator(e.getKey()))); + + config.firebase() + .hosting() + .hostingPath() + .map(FirebaseEmulatorConfigBuilder::asPath) + .ifPresent(firebaseConfigBuilder::withHostingPath); + + config.firebase() + .firestore() + .indexesFile() + .map(FirebaseEmulatorConfigBuilder::asPath) + .ifPresent(firebaseConfigBuilder::withFirestoreIndexes); + + config.firebase() + .firestore() + .rulesFile() + .map(FirebaseEmulatorConfigBuilder::asPath) + .ifPresent(firebaseConfigBuilder::withFirestoreRules); + + config.firebase() + .firestore() + .websocketPort() + .ifPresent(p -> firebaseConfigBuilder + .withEmulatorOnFixedPort(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE_WS, p)); + + config.firebase() + .emulator() + .ui() + .hubPort() + .ifPresent( + p -> firebaseConfigBuilder.withEmulatorOnFixedPort(FirebaseEmulatorContainer.Emulator.EMULATOR_HUB, p)); + + config.firebase() + .emulator() + .ui() + .loggingPort() + .ifPresent(p -> firebaseConfigBuilder.withEmulatorOnFixedPort(FirebaseEmulatorContainer.Emulator.LOGGING, p)); + + config.storage() + .rulesFile() + .map(FirebaseEmulatorConfigBuilder::asPath) + .ifPresent(firebaseConfigBuilder::withStorageRules); + + firebaseConfigBuilder.done(); + } + + private static Path asPath(String path) { + return new File(path).toPath(); + } + +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java new file mode 100644 index 00000000..a7050bd9 --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java @@ -0,0 +1,217 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.json.Emulators; +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.json.FirebaseConfig; + +/** + * Reader for the firebase.json file to convert it to the + * {@link io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer.FirebaseConfig} + */ +class CustomFirebaseConfigReader { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Read the firebase config from a firebase.json file + * + * @param customFirebaseJson The path to the file + * @return The configuration + * @throws IOException In case the file could not be read + */ + public FirebaseEmulatorContainer.FirebaseConfig readFromFirebase(Path customFirebaseJson) throws IOException { + var root = readCustomFirebaseJson(customFirebaseJson); + + return new FirebaseEmulatorContainer.FirebaseConfig( + readHosting(root.getHosting(), customFirebaseJson), + readStorage(root.getStorage(), customFirebaseJson), + readFirestore(root.getFirestore(), customFirebaseJson), + readFunctions(root.getFunctions(), customFirebaseJson), + readEmulators(root.getEmulators())); + } + + private record EmulatorMergeStrategy( + FirebaseEmulatorContainer.Emulator emulator, + Supplier configObjectSupplier, + Function> portSupplier) { + } + + private Map readEmulators(Emulators em) { + var mergeStrategies = new EmulatorMergeStrategy[] { + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.AUTHENTICATION, + em::getAuth, + a -> a::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI, + em::getUi, + u -> u::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.EMULATOR_HUB, + em::getHub, + h -> h::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.LOGGING, + em::getLogging, + l -> l::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS, + em::getFunctions, + f -> f::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.EVENT_ARC, + em::getEventarc, + e -> e::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE, + em::getDatabase, + d -> d::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE, + em::getFirestore, + d -> d::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE, + em::getStorage, + s -> s::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING, + em::getHosting, + h -> h::getPort), + new EmulatorMergeStrategy<>( + FirebaseEmulatorContainer.Emulator.PUB_SUB, + em::getPubsub, + h -> h::getPort) + }; + + var map = Arrays.stream(mergeStrategies) + .map(this::mergeEmulator) + .filter(e -> !Objects.isNull(e)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (em.getFirestore() != null && em.getFirestore().getWebsocketPort() != null) { + map = new HashMap<>(map); + + map.put( + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE_WS, + new FirebaseEmulatorContainer.ExposedPort(em.getFirestore().getWebsocketPort())); + + map = Map.copyOf(map); + } + + return map; + } + + private Map.Entry mergeEmulator( + EmulatorMergeStrategy emulatorMergeStrategy) { + + var configObject = emulatorMergeStrategy.configObjectSupplier.get(); + if (configObject != null) { + var port = emulatorMergeStrategy.portSupplier.apply(configObject).get(); + return Map.entry(emulatorMergeStrategy.emulator, new FirebaseEmulatorContainer.ExposedPort(port)); + } else { + return null; + } + } + + private FirebaseEmulatorContainer.FirestoreConfig readFirestore(Object firestore, Path customFirebaseJson) { + if (firestore instanceof Map) { + @SuppressWarnings("unchecked") + Map firestoreMap = (Map) firestore; + + var rulesFile = Optional + .ofNullable(firestoreMap.get("rules")) + .map(f -> this.resolvePath(f, customFirebaseJson)); + var indexesFile = Optional + .ofNullable(firestoreMap.get("indexes")) + .map(f -> this.resolvePath(f, customFirebaseJson)); + + return new FirebaseEmulatorContainer.FirestoreConfig( + rulesFile, + indexesFile); + } else { + return FirebaseEmulatorContainer.FirestoreConfig.DEFAULT; + } + } + + private FirebaseEmulatorContainer.HostingConfig readHosting(Object hosting, Path customFirebaseJson) { + if (hosting instanceof Map) { + @SuppressWarnings("unchecked") + Map hostingMap = (Map) hosting; + + var publicDir = Optional + .ofNullable(hostingMap.get("public")) + .map(f -> this.resolvePath(f, customFirebaseJson)); + + return new FirebaseEmulatorContainer.HostingConfig( + publicDir); + } else { + return FirebaseEmulatorContainer.HostingConfig.DEFAULT; + } + } + + private FirebaseEmulatorContainer.StorageConfig readStorage(Object storage, Path customFirebaseJson) { + if (storage instanceof Map) { + @SuppressWarnings("unchecked") + Map storageMap = (Map) storage; + + var rulesFile = Optional + .ofNullable(storageMap.get("rules")) + .map(f -> this.resolvePath(f, customFirebaseJson)); + + return new FirebaseEmulatorContainer.StorageConfig( + rulesFile); + } else { + return FirebaseEmulatorContainer.StorageConfig.DEFAULT; + } + } + + private FirebaseEmulatorContainer.FunctionsConfig readFunctions(Object functions, Path customFirebaseJson) { + if (functions instanceof Map) { + @SuppressWarnings("unchecked") + Map functionsMap = (Map) functions; + + var functionsPath = Optional + .ofNullable(functionsMap.get("source")) + .map(String.class::cast) + .map(f -> this.resolvePath(f, customFirebaseJson)); + + var ignores = Optional + .ofNullable(functionsMap.get("ignores")) + .map(String[].class::cast) + .orElse(new String[0]); + + return new FirebaseEmulatorContainer.FunctionsConfig( + functionsPath, + ignores); + } else { + return FirebaseEmulatorContainer.FunctionsConfig.DEFAULT; + } + } + + private Path resolvePath(String filename, Path customFirebaseJson) { + File firebaseJson = customFirebaseJson.toFile(); + File firebaseDir = firebaseJson.getParentFile(); + return new File(firebaseDir, filename).toPath(); + } + + private FirebaseConfig readCustomFirebaseJson(Path customFirebaseJson) throws IOException { + var customFirebaseFile = customFirebaseJson.toFile(); + var customFirebaseStream = new BufferedInputStream(new FileInputStream(customFirebaseFile)); + + return objectMapper.readerFor(FirebaseConfig.class) + .readValue(customFirebaseStream); + } + +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java new file mode 100644 index 00000000..31c96003 --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java @@ -0,0 +1,1389 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.OutputFrame; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.images.builder.dockerfile.DockerfileBuilder; + +/** + * Testcontainers container to run Firebase emulators from Docker. + */ +public class FirebaseEmulatorContainer extends GenericContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(FirebaseEmulatorContainer.class); + + private static final String FIREBASE_ROOT = "/srv/firebase"; + private static final String FIREBASE_HOSTING_PATH = FIREBASE_ROOT + "/" + FirebaseJsonBuilder.FIREBASE_HOSTING_SUBPATH; + private static final String EMULATOR_DATA_PATH = FIREBASE_ROOT + "/data"; + private static final String EMULATOR_EXPORT_PATH = EMULATOR_DATA_PATH + "/emulator-data"; + + /** + * Set of possible emulators (or components/services). + */ + public enum Emulator { + /** + * Firebase Auth emulator + */ + AUTHENTICATION( + 9099, + "auth", + "auth"), + /** + * Emulator UI, not a real emulator, but allows exposing the UI on a predefined port + */ + EMULATOR_SUITE_UI( + 4000, + "ui", + "ui"), + /** + * Emulator Hub API port + */ + EMULATOR_HUB( + 4400, + "hub", + null), + /** + * Emulator UI Logging endpoint + */ + LOGGING( + 4500, + "logging", + null), + /** + * CLoud functions emulator + */ + CLOUD_FUNCTIONS( + 5001, + "functions", + "functions"), + /** + * Event Arc emulator + */ + EVENT_ARC( + 9299, + "eventarc", + "eventarc"), + /** + * Realtime database emulator + */ + REALTIME_DATABASE( + 9000, + "database", + "database"), + /** + * Firestore emulator + */ + CLOUD_FIRESTORE( + 8080, + "firestore", + "firestore"), + /** + * Firestore websocket port, This emulator always need to be specified in conjunction with CLOUD_FIRESTORE. + */ + CLOUD_FIRESTORE_WS( + 9150, + null, + null), + /** + * Cloud storage emulator + */ + CLOUD_STORAGE( + 9199, + "storage", + "storage"), + /** + * Firebase hosting emulator + */ + FIREBASE_HOSTING( + 5000, + "hosting", + "hosting"), + /** + * Pub/sub emulator + */ + PUB_SUB( + 8085, + "pubsub", + "pubsub"); + + /** + * The default port on which the emulator is running. + */ + public final int internalPort; + final String configProperty; + final String emulatorName; + + Emulator(int internalPort, String configProperty, String onlyArgument) { + this.internalPort = internalPort; + this.configProperty = configProperty; + this.emulatorName = onlyArgument; + } + + } + + /** + * Record to hold an exposed port of an emulator. + * + * @param fixedPort The exposed port or null in case it is a random port + */ + public record ExposedPort(Integer fixedPort) { + + public static final ExposedPort RANDOM_PORT = new ExposedPort(null); + + boolean isFixed() { + return fixedPort != null; + } + } + + /** + * The docker image configuration + * + * @param imageName The name of the docker image + * @param userId The user id to run the docker image + * @param groupId The group id to run the docker image + * @param followStdOut Pipe stdout of the container to stdout of the host + * @param followStdErr Pipe stderr of the container to stderr of the host + * @param afterStart Callback to handle additional logic after the container has started. + */ + public record DockerConfig( + String imageName, + Optional userId, + Optional groupId, + boolean followStdOut, + boolean followStdErr, + Consumer afterStart) { + + /** + * Default settings + */ + public static final DockerConfig DEFAULT = new DockerConfig( + DEFAULT_IMAGE_NAME, + Optional.empty(), + Optional.empty(), + true, + true, + null); + } + + /** + * Record to hold the argument for the CLI executable. + * + * @param projectId The project ID, needed when running with the auth emulator + * @param token The Google Cloud CLI token to use for authentication. Needed for firebase hosting + * @param javaToolOptions The options to pass to the java based emulators + * @param emulatorData The path to the directory where to store the emulator data + * @param importExport Specify whether to import, export or do both with the emulator data + * @param debug Whether to run with the --debug flag + */ + public record CliArgumentsConfig( + Optional projectId, + Optional token, + Optional javaToolOptions, + Optional emulatorData, + ImportExport importExport, + boolean debug) { + public static final CliArgumentsConfig DEFAULT = new CliArgumentsConfig( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + ImportExport.IMPORT_EXPORT, + false); + } + + /** + * Behaviour of the import/export data. + */ + public enum ImportExport { + /** + * Only import the data + */ + IMPORT_ONLY(true, false), + + /** + * Only export the data + */ + EXPORT_ONLY(false, true), + + /** + * Both import and export the data. + */ + IMPORT_EXPORT(true, true); + + private final boolean doImport; + private final boolean doExport; + + ImportExport(boolean doImport, boolean doExport) { + this.doImport = doImport; + this.doExport = doExport; + } + + boolean isDoImport() { + return doImport; + } + + boolean isDoExport() { + return doExport; + } + } + + /** + * Firebase hosting configuration + * + * @param hostingContentDir The path to the directory containing the hosting content + */ + public record HostingConfig( + Optional hostingContentDir) { + + public static final HostingConfig DEFAULT = new HostingConfig( + Optional.empty()); + } + + /** + * Cloud storage configuration + * + * @param rulesFile Cloud storage rules file + */ + public record StorageConfig( + Optional rulesFile) { + + public static final StorageConfig DEFAULT = new StorageConfig( + Optional.empty()); + } + + /** + * Firestore configuration + * + * @param rulesFile The rules file + * @param indexesFile The indexes file + */ + public record FirestoreConfig( + Optional rulesFile, + Optional indexesFile) { + + public static final FirestoreConfig DEFAULT = new FirestoreConfig( + Optional.empty(), + Optional.empty()); + } + + /** + * Functions configuration + * + * @param functionsPath The location for the functions sources + * @param ignores The files to ignore when creating the function + */ + public record FunctionsConfig( + Optional functionsPath, + String[] ignores) { + + public static FunctionsConfig DEFAULT = new FunctionsConfig( + Optional.empty(), + new String[0]); + } + + /** + * The firebase configuration, this record mimics the various items which can be configured using the + * firebase.json file. + * + * @param hostingConfig The firebase hosting configuration + * @param storageConfig The storage configuration + * @param firestoreConfig The firestore configuration + * @param functionsConfig The functions configuration + * @param services The exposed services configuration + */ + public record FirebaseConfig( + HostingConfig hostingConfig, + StorageConfig storageConfig, + FirestoreConfig firestoreConfig, + FunctionsConfig functionsConfig, + Map services) { + } + + /** + * Describes the Firebase emulator configuration. + * + * @param dockerConfig The docker configuration + * @param firebaseVersion The firebase version to use + * @param cliArguments The arguments to the CLI + * @param customFirebaseJson The path to a custom firebase + * @param firebaseConfig The firebase configuration + */ + public record EmulatorConfig( + DockerConfig dockerConfig, + String firebaseVersion, + CliArgumentsConfig cliArguments, + Optional customFirebaseJson, + FirebaseConfig firebaseConfig) { + } + + // Use node:20 for now because of https://github.com/firebase/firebase-tools/issues/7173 + /** + * The default image to use for building the docker image. + */ + public static final String DEFAULT_IMAGE_NAME = "node:20-alpine"; + /** + * The default version of the firebase tools to install. + */ + public static final String DEFAULT_FIREBASE_VERSION = "latest"; + + /** + * Builder for the {@link FirebaseEmulatorContainer} configuration. + */ + public static class Builder { + + private DockerConfig dockerConfig = DockerConfig.DEFAULT; + private String firebaseVersion = DEFAULT_FIREBASE_VERSION; + private CliArgumentsConfig cliArguments = CliArgumentsConfig.DEFAULT; + + private Path customFirebaseJson; + private FirebaseConfig firebaseConfig; + + private Builder() { + } + + /** + * Configure the docker options + * + * @return THe docker config builder + */ + public DockerConfigBuilder withDockerConfig() { + return new DockerConfigBuilder(); + } + + /** + * Configure the firebase version + * + * @param firebaseVersion The firebase version + * @return The builder + */ + public Builder withFirebaseVersion(String firebaseVersion) { + this.firebaseVersion = firebaseVersion; + return this; + } + + /** + * Configure the CLI argument options + * + * @return The CLI Builder + */ + public CliBuilder withCliArguments() { + return new CliBuilder(); + } + + /** + * Read the configuration from the custom firebase.json file. + * + * @param customFirebaseJson The path to the custom firebase json + * @return The builder + * @throws IOException In case the file could not be read. + */ + public Builder readFromFirebaseJson(Path customFirebaseJson) throws IOException { + var reader = new CustomFirebaseConfigReader(); + this.firebaseConfig = reader.readFromFirebase(customFirebaseJson); + this.customFirebaseJson = customFirebaseJson; + return this; + } + + /** + * Configure the firebase emulators + * + * @return The firebase config builder + */ + public FirebaseConfigBuilder withFirebaseConfig() { + return new FirebaseConfigBuilder(); + } + + /** + * Build the configuration + * + * @return The emulator configuration. + */ + public EmulatorConfig buildConfig() { + if (firebaseConfig == null) { + // Try to autoload the firebase.json configuration + var defaultFirebaseJson = new File("firebase.json").getAbsoluteFile().toPath(); + + LOGGER.info("Trying to automatically read firebase config from {}", defaultFirebaseJson); + + try { + readFromFirebaseJson(defaultFirebaseJson); + } catch (IOException e) { + throw new IllegalStateException( + "Firebase was not configured and could not auto-read from " + defaultFirebaseJson); + } + } + + return new EmulatorConfig( + dockerConfig, + firebaseVersion, + cliArguments, + Optional.ofNullable(customFirebaseJson), + firebaseConfig); + } + + /** + * Build the final configuration + * + * @return the final configuration. + */ + public FirebaseEmulatorContainer build() { + return new FirebaseEmulatorContainer(buildConfig()); + } + + /** + * Builder for the docker configuration. + */ + public class DockerConfigBuilder { + + private DockerConfigBuilder() { + } + + /** + * Configure the base image to use + * + * @param imageName The image name + * @return The builder + */ + public DockerConfigBuilder withImage(String imageName) { + Builder.this.dockerConfig = new DockerConfig( + imageName, + Builder.this.dockerConfig.userId(), + Builder.this.dockerConfig.groupId(), + Builder.this.dockerConfig.followStdOut(), + Builder.this.dockerConfig.followStdErr(), + Builder.this.dockerConfig.afterStart()); + return this; + } + + /** + * Configure the user id to use within docker + * + * @param userId The user id + * @return The builder + */ + public DockerConfigBuilder withUserId(int userId) { + return withUserId(Optional.of(userId)); + } + + /** + * Try to configure the user id to use within docker from an environment variable. + * + * @param env The environment variable + * @return The builder + */ + public DockerConfigBuilder withUserIdFromEnv(String env) { + return withUserId(readIdFromEnv(env)); + } + + private DockerConfigBuilder withUserId(Optional userId) { + Builder.this.dockerConfig = new DockerConfig( + Builder.this.dockerConfig.imageName(), + userId, + Builder.this.dockerConfig.groupId(), + Builder.this.dockerConfig.followStdOut(), + Builder.this.dockerConfig.followStdErr(), + Builder.this.dockerConfig.afterStart()); + return this; + } + + /** + * Configure the group id to use within docker + * + * @param groupId The group id + * @return The builder + */ + public DockerConfigBuilder withGroupId(int groupId) { + return withGroupId(Optional.of(groupId)); + } + + /** + * Try to configure the group id to use within docker from an environment variable. + * + * @param env The environment variable + * @return The builder + */ + public DockerConfigBuilder withGroupIdFromEnv(String env) { + return withGroupId(readIdFromEnv(env)); + } + + private DockerConfigBuilder withGroupId(Optional groupId) { + Builder.this.dockerConfig = new DockerConfig( + Builder.this.dockerConfig.imageName(), + Builder.this.dockerConfig.userId(), + groupId, + Builder.this.dockerConfig.followStdOut(), + Builder.this.dockerConfig.followStdErr(), + Builder.this.dockerConfig.afterStart()); + return this; + } + + /** + * Pipe the container stdout to the host stdout. This can ease debugging of container issues. + * + * @param followStdOut Whether to pipe the container stdout to the host stdout + * @return The builder + */ + public DockerConfigBuilder followStdOut(boolean followStdOut) { + Builder.this.dockerConfig = new DockerConfig( + Builder.this.dockerConfig.imageName(), + Builder.this.dockerConfig.userId(), + Builder.this.dockerConfig.groupId(), + followStdOut, + Builder.this.dockerConfig.followStdErr(), + Builder.this.dockerConfig.afterStart()); + return this; + } + + /** + * Pipe the container stdout to the host stderr. This can ease debugging of container issues. + * + * @param followStdErr Whether to pipe the container stderr to the host stdout + * @return The builder + */ + public DockerConfigBuilder followStdErr(boolean followStdErr) { + Builder.this.dockerConfig = new DockerConfig( + Builder.this.dockerConfig.imageName(), + Builder.this.dockerConfig.userId(), + Builder.this.dockerConfig.groupId(), + Builder.this.dockerConfig.followStdOut(), + followStdErr, + Builder.this.dockerConfig.afterStart()); + return this; + } + + /** + * Set a callback to run after the container has started. + * + * @param afterStart Callback to be executed after the container has started + * @return The builder + */ + public DockerConfigBuilder afterStart(Consumer afterStart) { + Builder.this.dockerConfig = new DockerConfig( + Builder.this.dockerConfig.imageName(), + Builder.this.dockerConfig.userId(), + Builder.this.dockerConfig.groupId(), + Builder.this.dockerConfig.followStdOut(), + Builder.this.dockerConfig.followStdErr(), + afterStart); + return this; + } + + /** + * Finish the docker configuration + * + * @return The primary builder + */ + public Builder done() { + return Builder.this; + } + + private Optional readIdFromEnv(String env) { + try { + return Optional + .ofNullable(System.getenv(env)) + .map(Integer::valueOf); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + } + + /** + * Builder for the CLI Arguments configuration + */ + public class CliBuilder { + private String projectId; + private String token; + private String javaToolOptions; + private Path emulatorData; + private ImportExport importExport; + private boolean debug; + + /** + * The CLI Builder constructor + */ + private CliBuilder() { + this.projectId = Builder.this.cliArguments.projectId.orElse(null); + this.token = Builder.this.cliArguments.token.orElse(null); + this.javaToolOptions = Builder.this.cliArguments.javaToolOptions.orElse(null); + this.emulatorData = Builder.this.cliArguments.emulatorData.orElse(null); + this.importExport = Builder.this.cliArguments.importExport; + this.debug = Builder.this.cliArguments.debug; + } + + /** + * Configure the project id + * + * @param projectId The project id + * @return The builder + */ + public CliBuilder withProjectId(String projectId) { + this.projectId = projectId; + return this; + } + + /** + * Configure the Google auth token to use + * + * @param token The token + * @return The builder + */ + public CliBuilder withToken(String token) { + this.token = token; + return this; + } + + /** + * Configure the java tool options + * + * @param javaToolOptions The java tool options + * @return The builder + */ + public CliBuilder withJavaToolOptions(String javaToolOptions) { + this.javaToolOptions = javaToolOptions; + return this; + } + + /** + * Configure the location to import/export the emulator data + * + * @param emulatorData The emulator data + * @return The builder + */ + public CliBuilder withEmulatorData(Path emulatorData) { + this.emulatorData = emulatorData; + return this; + } + + /** + * Set the import/export behaviour for the specified emulator data. This setting is inactive unless + * {@link #withEmulatorData(Path)} is set. + * + * @param importExport The import/export setting + * @return The builder + */ + public CliBuilder withImportExport(ImportExport importExport) { + this.importExport = importExport; + return this; + } + + /** + * Run the firebase tools with a debug flag + * + * @param debug Whether to run with debug or not + * @return The builder + */ + public CliBuilder withDebug(boolean debug) { + this.debug = debug; + return this; + } + + /** + * Finish the builder + * + * @return The parent builder + */ + public Builder done() { + Builder.this.cliArguments = new CliArgumentsConfig( + Optional.ofNullable(this.projectId), + Optional.ofNullable(this.token), + Optional.ofNullable(this.javaToolOptions), + Optional.ofNullable(this.emulatorData), + this.importExport, + this.debug); + return Builder.this; + } + } + + /** + * Builder for the Firebase configuration + */ + public class FirebaseConfigBuilder { + + private HostingConfig hostingConfig = HostingConfig.DEFAULT; + private StorageConfig storageConfig = StorageConfig.DEFAULT; + private FirestoreConfig firestoreConfig = FirestoreConfig.DEFAULT; + private FunctionsConfig functionsConfig = FunctionsConfig.DEFAULT; + private final Map services = new HashMap<>(); + + /** + * Create a new builder + */ + public FirebaseConfigBuilder() { + } + + /** + * Configure the directory where to find the hosting files + * + * @param hostingContentDir The hosting directory + * @return The builder + */ + public FirebaseConfigBuilder withHostingPath(Path hostingContentDir) { + this.hostingConfig = new HostingConfig( + Optional.of(hostingContentDir)); + return this; + } + + /** + * Configure the Google Cloud storage rules file + * + * @param rulesFile The rules file. + * @return The builder + */ + public FirebaseConfigBuilder withStorageRules(Path rulesFile) { + this.storageConfig = new StorageConfig( + Optional.of(rulesFile)); + return this; + } + + /** + * Configure the Firestore rules file + * + * @param rulesFile The rules file + * @return The builder + */ + public FirebaseConfigBuilder withFirestoreRules(Path rulesFile) { + this.firestoreConfig = new FirestoreConfig( + Optional.of(rulesFile), + this.firestoreConfig.indexesFile); + return this; + } + + /** + * Configure the firestore indexes file + * + * @param indexes The indexes file + * @return The builder + */ + public FirebaseConfigBuilder withFirestoreIndexes(Path indexes) { + this.firestoreConfig = new FirestoreConfig( + this.firestoreConfig.rulesFile(), + Optional.of(indexes)); + return this; + } + + /** + * Configure the input directory for the functions + * + * @param functions The path to the functions + * @return The builder + */ + public FirebaseConfigBuilder withFunctionsFromPath(Path functions) { + this.functionsConfig = new FunctionsConfig( + Optional.of(functions), + this.functionsConfig.ignores()); + return this; + } + + /** + * Configure the ignores for the functions directory + * + * @param ignores The ignores + * @return The builder + */ + public FirebaseConfigBuilder withFunctionIgnores(String[] ignores) { + this.functionsConfig = new FunctionsConfig( + this.functionsConfig.functionsPath, + ignores); + return this; + } + + /** + * Include an emulator on a random port + * + * @param emulator The emulator + * @return The builder + */ + public FirebaseConfigBuilder withEmulator(Emulator emulator) { + this.services.put(emulator, ExposedPort.RANDOM_PORT); + return this; + } + + /** + * Include emulators on a random port + * + * @param emulators The emulators + * @return The builder + */ + public FirebaseConfigBuilder withEmulators(Emulator... emulators) { + for (Emulator emulator : emulators) { + withEmulator(emulator); + } + return this; + } + + /** + * Include an emulator on a fixed port + * + * @param emulator The emulator + * @param port The port to expose on + * @return The builder + */ + public FirebaseConfigBuilder withEmulatorOnFixedPort(Emulator emulator, int port) { + this.services.put(emulator, new ExposedPort(port)); + return this; + } + + /** + * Include emulators on fixed ports + * + * @param emulatorsAndPorts Alternating the {@link Emulator} and the {@link Integer} port. + * @return The builder + * @throws IllegalArgumentException In case the arguments don't alternate between Emulator and Port. + */ + public FirebaseConfigBuilder withEmulatorsOnPorts(Object... emulatorsAndPorts) { + if (emulatorsAndPorts.length % 2 != 0) { + throw new IllegalArgumentException("Emulators and ports must both be specified alternating"); + } + + try { + for (int i = 0; i < emulatorsAndPorts.length; i += 2) { + var emulator = (Emulator) emulatorsAndPorts[i]; + var port = (Integer) emulatorsAndPorts[i + 1]; + withEmulatorOnFixedPort(emulator, port); + } + } catch (ClassCastException e) { + throw new IllegalArgumentException("Emulators and ports must be specified alternating"); + } + + return this; + } + + /** + * Finish the firebase configuration + * + * @return The primary builder + */ + public Builder done() { + Builder.this.firebaseConfig = new FirebaseConfig( + hostingConfig, + storageConfig, + firestoreConfig, + functionsConfig, + services); + Builder.this.customFirebaseJson = null; + + return Builder.this; + } + } + } + + private final Map services; + private final boolean followStdOut; + private final boolean followStdErr; + private final Consumer afterStart; + + /** + * Create the builder for the emulator container + * + * @return The builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new Firebase Emulator container + * + * @param emulatorConfig The generic configuration of the firebase emulators + */ + public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) { + super(new FirebaseDockerBuilder(emulatorConfig).build()); + + this.services = emulatorConfig.firebaseConfig().services; + this.followStdOut = emulatorConfig.dockerConfig().followStdOut(); + this.followStdErr = emulatorConfig.dockerConfig().followStdErr(); + this.afterStart = emulatorConfig.dockerConfig().afterStart(); + + emulatorConfig.cliArguments().emulatorData().ifPresent(path -> { + // https://firebase.google.com/docs/emulator-suite/install_and_configure#export_and_import_emulator_data + // Mount the volume to the specified path + this.withFileSystemBind(path.toString(), EMULATOR_DATA_PATH, BindMode.READ_WRITE); + }); + + if (this.services.containsKey(Emulator.FIREBASE_HOSTING)) { + var hostingPath = emulatorConfig + .firebaseConfig() + .hostingConfig() + .hostingContentDir() + .map(Path::toString) + .orElse(new File(FirebaseJsonBuilder.FIREBASE_HOSTING_SUBPATH).getAbsolutePath()); + + // Mount volume for static hosting content + this.withFileSystemBind(hostingPath, containerHostingPath(emulatorConfig), BindMode.READ_ONLY); + } + + if (this.services.containsKey(Emulator.CLOUD_FUNCTIONS)) { + var functionsPath = emulatorConfig + .firebaseConfig() + .functionsConfig() + .functionsPath() + .map(Path::toString) + .orElse(new File(FirebaseJsonBuilder.FIREBASE_FUNCTIONS_SUBPATH).getAbsolutePath()); + + // Mount volume for functions + this.withFileSystemBind(functionsPath, containerFunctionsPath(emulatorConfig), BindMode.READ_ONLY); + } + } + + static String containerHostingPath(EmulatorConfig emulatorConfig) { + var hostingPath = emulatorConfig.firebaseConfig().hostingConfig().hostingContentDir(); + if (emulatorConfig.customFirebaseJson().isPresent()) { + var firebaseJsonDir = emulatorConfig.customFirebaseJson().get().getParent(); + hostingPath = hostingPath.map(path -> path.subpath(firebaseJsonDir.getNameCount(), path.getNameCount())); + } + + if (hostingPath.isPresent()) { + var path = hostingPath.get(); + if (path.isAbsolute()) { + return FIREBASE_HOSTING_PATH; + } else { + return FIREBASE_ROOT + "/" + hostingPath.get(); + } + } else { + return FIREBASE_HOSTING_PATH; + } + } + + static String containerFunctionsPath(EmulatorConfig emulatorConfig) { + var functionsPath = emulatorConfig.firebaseConfig().functionsConfig().functionsPath(); + if (emulatorConfig.customFirebaseJson().isPresent()) { + var firebaseJsonDir = emulatorConfig.customFirebaseJson().get().getParent(); + functionsPath = functionsPath.map(path -> path.subpath(firebaseJsonDir.getNameCount(), path.getNameCount())); + } + return FIREBASE_ROOT + "/" + functionsPath + .map(Path::toString) + .orElse(FirebaseJsonBuilder.FIREBASE_FUNCTIONS_SUBPATH); + } + + private static class FirebaseDockerBuilder { + + private static final Map DOWNLOADABLE_EMULATORS = Map.of( + Emulator.REALTIME_DATABASE, "database", + Emulator.CLOUD_FIRESTORE, "firestore", + Emulator.PUB_SUB, "pubsub", + Emulator.CLOUD_STORAGE, "storage", + Emulator.EMULATOR_SUITE_UI, "ui"); + + private final ImageFromDockerfile result; + + private final EmulatorConfig emulatorConfig; + private final Map devServices; + + private DockerfileBuilder dockerBuilder; + + public FirebaseDockerBuilder(EmulatorConfig emulatorConfig) { + this.devServices = emulatorConfig.firebaseConfig().services; + this.emulatorConfig = emulatorConfig; + + this.result = new ImageFromDockerfile("localhost/testcontainers/firebase", false) + .withDockerfileFromBuilder(builder -> this.dockerBuilder = builder); + } + + public ImageFromDockerfile build() { + this.validateConfiguration(); + this.configureBaseImage(); + this.initialSetup(); + this.authenticateToFirebase(); + this.setupJavaToolOptions(); + this.setupUserAndGroup(); + this.downloadEmulators(); + this.addFirebaseJson(); + this.includeFirestoreFiles(); + this.includeStorageFiles(); + this.setupDataImportExport(); + this.setupHosting(); + this.setupFunctions(); + this.runExecutable(); + + return result; + } + + private void validateConfiguration() { + if (isEmulatorEnabled(Emulator.AUTHENTICATION) && emulatorConfig.cliArguments().projectId().isEmpty()) { + throw new IllegalStateException("Can't create Firebase Auth emulator. Google Project id is required"); + } + + if (isEmulatorEnabled(Emulator.EMULATOR_SUITE_UI)) { + if (!isEmulatorEnabled(Emulator.EMULATOR_HUB)) { + LOGGER.info( + "Firebase Emulator UI is enabled, but no Hub port is specified. You will not be able to use the Hub API "); + } + + if (!isEmulatorEnabled(Emulator.LOGGING)) { + LOGGER.info( + "Firebase Emulator UI is enabled, but no Logging port is specified. You will not be able to see the logging "); + } + + if (isEmulatorEnabled(Emulator.CLOUD_FIRESTORE)) { + if (!isEmulatorEnabled(Emulator.CLOUD_FIRESTORE_WS)) { + LOGGER.warn("Firebase Firestore Emulator and Emulator UI are enabled but no Firestore Websocket " + + "port is specified. You will not be able to use the Firestore UI"); + } + } + } + + if (emulatorConfig.customFirebaseJson.isPresent()) { + var hostingDirIsAbsolute = emulatorConfig.firebaseConfig.hostingConfig.hostingContentDir + .map(Path::isAbsolute) + .orElse(false); + + if (hostingDirIsAbsolute) { + throw new IllegalStateException( + "When using a custom firebase.json, the hosting path must be relative to the firebase.json file"); + } + + var firebasePath = emulatorConfig.customFirebaseJson.get().toAbsolutePath().getParent(); + + var hostingDirIsChildOfFirebaseJsonParent = emulatorConfig.firebaseConfig.hostingConfig.hostingContentDir + .map(Path::toAbsolutePath) + .map(h -> h.startsWith(firebasePath)) + .orElse(true); + + if (!hostingDirIsChildOfFirebaseJsonParent) { + throw new IllegalStateException( + "When using a custom firebase.json, the hosting path must be in the same subtree as the firebase.json file"); + } + } + + if (emulatorConfig.firebaseConfig.functionsConfig.functionsPath.isPresent()) { + var functionsDirIsAbsolute = emulatorConfig.firebaseConfig.functionsConfig.functionsPath + .map(Path::isAbsolute) + .orElse(false); + + if (functionsDirIsAbsolute) { + throw new IllegalStateException("Functions path cannot be absolute"); + } + } + + // TODO: Validate if a custom firebase.json is defined, that the hosts are defined as 0.0.0.0 + } + + private void configureBaseImage() { + dockerBuilder.from(emulatorConfig.dockerConfig().imageName()); + } + + private void initialSetup() { + dockerBuilder + .run("apk --no-cache add openjdk17-jre bash curl openssl gettext nano nginx sudo && " + + "npm cache clean --force && " + + "npm i -g firebase-tools@" + emulatorConfig.firebaseVersion() + " && " + + "deluser nginx && delgroup abuild && delgroup ping && " + + "mkdir -p " + FIREBASE_ROOT + " && " + + + "mkdir -p " + EMULATOR_DATA_PATH + " && " + + "mkdir -p " + EMULATOR_EXPORT_PATH + " && " + + "chmod 777 -R /srv/*"); + } + + private void downloadEmulators() { + var cmd = DOWNLOADABLE_EMULATORS + .entrySet() + .stream() + .map(e -> downloadEmulatorCommand(e.getKey(), e.getValue())) + .filter(Objects::nonNull) + .collect(Collectors.joining(" && ")); + + dockerBuilder.run(cmd); + } + + private String downloadEmulatorCommand(Emulator emulator, String downloadId) { + if (isEmulatorEnabled(emulator)) { + return "firebase setup:emulators:" + downloadId; + } else { + return null; + } + } + + private void authenticateToFirebase() { + emulatorConfig.cliArguments().token().ifPresent( + token -> dockerBuilder.env("FIREBASE_TOKEN", token)); + } + + private void setupJavaToolOptions() { + emulatorConfig.cliArguments().javaToolOptions().ifPresent( + toolOptions -> dockerBuilder.env("JAVA_TOOL_OPTIONS", toolOptions)); + } + + private void addFirebaseJson() { + dockerBuilder.workDir(FIREBASE_ROOT); + + emulatorConfig.customFirebaseJson().ifPresentOrElse( + this::includeCustomFirebaseJson, + this::generateFirebaseJson); + + this.dockerBuilder.add("firebase.json", FIREBASE_ROOT + "/firebase.json"); + } + + private void includeCustomFirebaseJson(Path customFilePath) { + this.result.withFileFromPath( + "firebase.json", + customFilePath); + } + + private void includeFirestoreFiles() { + emulatorConfig.firebaseConfig().firestoreConfig.rulesFile.ifPresent(rulesFile -> { + this.dockerBuilder.add("firestore.rules", FIREBASE_ROOT + "/firestore.rules"); + this.result.withFileFromPath("firestore.rules", rulesFile); + }); + + emulatorConfig.firebaseConfig().firestoreConfig.indexesFile.ifPresent(indexesFile -> { + this.dockerBuilder.add("firestore.indexes.json", FIREBASE_ROOT + "/firestore.indexes.json"); + this.result.withFileFromPath("firestore.indexes.json", indexesFile); + }); + } + + private void includeStorageFiles() { + emulatorConfig.firebaseConfig().storageConfig.rulesFile.ifPresent(rulesFile -> { + this.dockerBuilder.add("storage.rules", FIREBASE_ROOT + "/storage.rules"); + this.result.withFileFromPath("storage.rules", rulesFile); + }); + } + + private void generateFirebaseJson() { + var firebaseJsonBuilder = new FirebaseJsonBuilder(this.emulatorConfig); + String firebaseJson; + try { + firebaseJson = firebaseJsonBuilder.buildFirebaseConfig(); + } catch (IOException e) { + throw new IllegalStateException("Failed to generate firebase.json file", e); + } + + this.result.withFileFromString("firebase.json", firebaseJson); + } + + private void setupDataImportExport() { + emulatorConfig.cliArguments().emulatorData().ifPresent( + emulator -> this.dockerBuilder.volume(EMULATOR_DATA_PATH)); + } + + private void setupHosting() { + // Specify public directory if hosting is enabled + if (emulatorConfig.firebaseConfig().hostingConfig().hostingContentDir().isPresent()) { + this.dockerBuilder.run("mkdir -p " + containerHostingPath(emulatorConfig)); + this.dockerBuilder.volume(containerHostingPath(emulatorConfig)); + } + } + + private void setupFunctions() { + if (emulatorConfig.firebaseConfig().functionsConfig().functionsPath.isPresent()) { + this.dockerBuilder.run("mkdir -p " + containerFunctionsPath(emulatorConfig)); + this.dockerBuilder.volume(containerFunctionsPath(emulatorConfig)); + } + } + + private void setupUserAndGroup() { + var commands = new ArrayList(); + + emulatorConfig.dockerConfig.groupId().ifPresent(group -> commands.add("addgroup -g " + group + " runner")); + + emulatorConfig.dockerConfig.userId().ifPresent(user -> { + var groupName = emulatorConfig.dockerConfig().groupId().map(i -> "runner").orElse("node"); + commands.add("adduser -u " + user + " -G " + groupName + " -D -h /srv/firebase runner"); + }); + + var group = dockerGroup(); + var user = dockerUser(); + + commands.add("chown " + user + ":" + group + " -R /srv/*"); + + var runCmd = String.join(" && ", commands); + + LOGGER.info("Running docker container as user/group: {}:{}", user, group); + + dockerBuilder + .run(runCmd) + .user(user + ":" + group); + } + + private int dockerUser() { + return emulatorConfig.dockerConfig().userId().orElse(1000); + } + + private int dockerGroup() { + return emulatorConfig.dockerConfig().groupId().orElse(1000); + } + + private void runExecutable() { + List arguments = new ArrayList<>(); + + arguments.add("emulators:start"); + + emulatorConfig.cliArguments().projectId() + .map(id -> "--project") + .ifPresent(arguments::add); + + emulatorConfig.cliArguments().projectId() + .ifPresent(arguments::add); + + if (emulatorConfig.cliArguments().debug) { + arguments.add("--debug"); + } + + if (emulatorConfig.cliArguments().importExport.isDoExport()) { + emulatorConfig + .cliArguments() + .emulatorData() + .map(path -> "--import") + .ifPresent(arguments::add); + + /* + * We write the data to a subdirectory of the mount point. The firebase emulator tries to remove and + * recreate the mount-point directory, which will obviously fail. By using a subdirectory, export succeeds. + */ + emulatorConfig + .cliArguments() + .emulatorData() + .map(path -> EMULATOR_EXPORT_PATH) + .ifPresent(arguments::add); + } + + if (emulatorConfig.cliArguments().importExport.isDoExport()) { + emulatorConfig + .cliArguments() + .emulatorData() + .map(path -> "--export-on-exit") + .ifPresent(arguments::add); + + /* + * We write the data to a subdirectory of the mount point. The firebase emulator tries to remove and + * recreate the mount-point directory, which will obviously fail. By using a subdirectory, export succeeds. + */ + emulatorConfig + .cliArguments() + .emulatorData() + .map(path -> EMULATOR_EXPORT_PATH) + .ifPresent(arguments::add); + } + + dockerBuilder.entryPoint(new String[] { "/usr/local/bin/firebase" }); + dockerBuilder.cmd(arguments.toArray(new String[0])); + } + + private boolean isEmulatorEnabled(Emulator emulator) { + return this.devServices.containsKey(emulator); + } + } + + /** + * Override start to handle logging redirection + */ + @Override + public void start() { + super.start(); + + if (followStdOut) { + followOutput(this::writeToStdOut, OutputFrame.OutputType.STDOUT); + } + + if (followStdErr) { + followOutput(this::writeToStdErr, OutputFrame.OutputType.STDERR); + } + + if (afterStart != null) { + afterStart.accept(this); + } + } + + @Override + public void stop() { + /* + * We override the way test containers stops the container. By default, test containers will send a + * kill (SIGKILL) command instead of a stop (SIGTERM) command. This will kill the container instantly + * and prevent firebase from writing the "--export-on-exit" data to the mounted directory. + */ + this.getDockerClient().stopContainerCmd(this.getContainerId()).exec(); + + super.stop(); + } + + /** + * Configures the Pub/Sub emulator container. + */ + @Override + public void configure() { + super.configure(); + + services.keySet() + .forEach(emulator -> { + var exposedPort = services.get(emulator); + // Expose emulatorPort + if (exposedPort.isFixed()) { + addFixedExposedPort(exposedPort.fixedPort(), exposedPort.fixedPort()); + } else { + addExposedPort(emulator.internalPort); + } + }); + + waitingFor(Wait.forLogMessage(".*Emulator Hub running at.*", 1)); + } + + /** + * Get the various endpoints for the emulators. The map values are in the form of a string "host:port". + * + * @return The emulator endpoints + */ + public Map emulatorEndpoints() { + return services.keySet() + .stream() + .collect(Collectors.toMap( + e -> e, + this::getEmulatorEndpoint)); + } + + /** + * Return the TCP port an emulator is listening on. + * + * @param emulator The emulator + * @return The TC Port + */ + public Integer emulatorPort(Emulator emulator) { + var exposedPort = services.get(emulator); + if (exposedPort.isFixed()) { + return exposedPort.fixedPort(); + } else { + return getMappedPort(emulator.internalPort); + } + } + + /** + * Get the ports on which the emulators are running. + * + * @return A map {@link Emulator} -> {@link Integer} indicating the TCP port the emulator is running on. + */ + public Map emulatorPorts() { + return services.keySet() + .stream() + .collect(Collectors.toMap( + e -> e, + this::emulatorPort)); + } + + private void writeToStdOut(OutputFrame frame) { + writeOutputFrame(frame, Level.INFO); + } + + private void writeToStdErr(OutputFrame frame) { + writeOutputFrame(frame, Level.ERROR); + } + + private void writeOutputFrame(OutputFrame frame, Level level) { + LOGGER.atLevel(level).log(frame.getUtf8StringWithoutLineEnding()); + } + + private String getEmulatorEndpoint(Emulator emulator) { + return this.getHost() + ":" + emulatorPort(emulator); + } +} diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseJsonBuilder.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseJsonBuilder.java new file mode 100644 index 00000000..70057563 --- /dev/null +++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseJsonBuilder.java @@ -0,0 +1,228 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Optional; +import java.util.function.Consumer; + +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.json.*; + +/** + * This class is responsible to generate the Firebase.json file which controls the emulators. + */ +class FirebaseJsonBuilder { + + private static final String ALL_IP = "0.0.0.0"; + public static final String FIREBASE_HOSTING_SUBPATH = "public"; + public static final String FIREBASE_FUNCTIONS_SUBPATH = "functions"; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final FirebaseEmulatorContainer.EmulatorConfig emulatorConfig; + private final FirebaseConfig root; + + public FirebaseJsonBuilder(FirebaseEmulatorContainer.EmulatorConfig emulatorConfig) { + this.emulatorConfig = emulatorConfig; + this.root = new FirebaseConfig(); + } + + public String buildFirebaseConfig() throws IOException { + generateFirebaseConfig(); + + StringWriter writer = new StringWriter(); + objectMapper.writeValue(writer, root); + return writer.toString(); + } + + private void generateFirebaseConfig() { + // private Object database; + // private Object dataconnect; + configureEmulator(); + // private ExtensionsConfig extensions; + configureFirestore(); + configureFunctions(); + configureHosting(); + // private Remoteconfig remoteconfig; + configureStorage(); + } + + private void configureEmulator() { + var emulators = new Emulators(); + root.setEmulators(emulators); + + withEmulator(FirebaseEmulatorContainer.Emulator.AUTHENTICATION, (port) -> { + var auth = new Auth(); + emulators.setAuth(auth); + auth.setHost(ALL_IP); + auth.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE, (port) -> { + var database = new Database(); + emulators.setDatabase(database); + database.setHost(ALL_IP); + database.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE, (port) -> { + var firestore = new Firestore(); + emulators.setFirestore(firestore); + firestore.setHost(ALL_IP); + firestore.setPort(port); + withEmulator(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE_WS, firestore::setWebsocketPort); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS, (port) -> { + var functions = new Functions(); + emulators.setFunctions(functions); + functions.setHost(ALL_IP); + functions.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.EVENT_ARC, (port) -> { + var eventarc = new Eventarc(); + emulators.setEventarc(eventarc); + eventarc.setHost(ALL_IP); + eventarc.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING, (port) -> { + var hosting = new Hosting(); + emulators.setHosting(hosting); + hosting.setHost(ALL_IP); + hosting.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.EMULATOR_HUB, (port) -> { + var hub = new Hub(); + emulators.setHub(hub); + hub.setHost(ALL_IP); + hub.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.LOGGING, (port) -> { + var logging = new Logging(); + emulators.setLogging(logging); + logging.setHost(ALL_IP); + logging.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.PUB_SUB, (port) -> { + var pubSub = new Pubsub(); + emulators.setPubsub(pubSub); + pubSub.setHost(ALL_IP); + pubSub.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE, (port) -> { + var storage = new Storage(); + emulators.setStorage(storage); + storage.setHost(ALL_IP); + storage.setPort(port); + }); + + withEmulator(FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI, (port) -> { + var ui = new Ui(); + emulators.setUi(ui); + ui.setHost(ALL_IP); + ui.setPort(port); + }); + + // Missing emulators + // private Apphosting apphosting; + // private Dataconnect dataconnect; + // private Extensions extensions; + // private Boolean singleProjectMode; + // private Tasks tasks; + } + + private void withEmulator(FirebaseEmulatorContainer.Emulator emulator, Consumer handler) { + if (isEmulatorEnabled(emulator)) { + var exposedPort = emulatorConfig.firebaseConfig().services().get(emulator); + var port = Optional.ofNullable(exposedPort.fixedPort()) + .orElse(emulator.internalPort); + + handler.accept(port); + } + } + + private void configureFirestore() { + if (isEmulatorEnabled(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE)) { + var firestore = new HashMap(); // Generated sources can't handle anyOf yet + root.setFirestore(firestore); + + emulatorConfig.firebaseConfig().firestoreConfig().rulesFile().ifPresent(rules -> { + var rulesFile = fileRelativeToCustomJsonOrDefault(rules, "firestore.rules"); + firestore.put("rules", rulesFile); + }); + + emulatorConfig.firebaseConfig().firestoreConfig().indexesFile().ifPresent(index -> { + var indexFile = fileRelativeToCustomJsonOrDefault(index, "firestore.indexes.json"); + firestore.put("indexes", indexFile); + }); + } + } + + private void configureFunctions() { + if (isEmulatorEnabled(FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS)) { + var functions = new HashMap(); + root.setFunctions(functions); + + var functionsPath = emulatorConfig + .firebaseConfig() + .functionsConfig() + .functionsPath() + .map(Path::toString) + .orElseThrow(); + + functions.put("source", functionsPath); + functions.put("ignores", new String[] { "node_modules" }); + } + } + + private void configureHosting() { + if (isEmulatorEnabled(FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING)) { + var hosting = new HashMap(); + root.setHosting(hosting); + + var hostingPath = emulatorConfig + .firebaseConfig() + .hostingConfig() + .hostingContentDir() + .map(path -> path.isAbsolute() ? FIREBASE_HOSTING_SUBPATH : path.toString()) + .orElseThrow(); + + hosting.put("public", hostingPath); + } + } + + private void configureStorage() { + if (isEmulatorEnabled(FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE)) { + emulatorConfig.firebaseConfig().storageConfig().rulesFile().ifPresent(rules -> { + var storage = new HashMap(); // Generated sources can't handle anyOf yet + root.setStorage(storage); + + var rulesFile = fileRelativeToCustomJsonOrDefault(rules, "storage.rules"); + storage.put("rules", rulesFile); + }); + } + } + + private String fileRelativeToCustomJsonOrDefault(Path otherFile, String defaultFile) { + return emulatorConfig.customFirebaseJson() + .map(path -> relativePath(path, otherFile)) + .orElse(defaultFile); + } + + private String relativePath(Path firebaseJson, Path otherFile) { + return firebaseJson.getParent().relativize(otherFile).toString(); + } + + private boolean isEmulatorEnabled(FirebaseEmulatorContainer.Emulator emulator) { + return this.emulatorConfig.firebaseConfig().services().containsKey(emulator); + } + +} diff --git a/firebase-devservices/deployment/src/main/resources/META-INF/schema/firebase-config.json b/firebase-devservices/deployment/src/main/resources/META-INF/schema/firebase-config.json new file mode 100644 index 00000000..02e77372 --- /dev/null +++ b/firebase-devservices/deployment/src/main/resources/META-INF/schema/firebase-config.json @@ -0,0 +1,2757 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "definitions": { + "ExtensionsConfig": { + "additionalProperties": false, + "type": "object" + }, + "FrameworksBackendOptions": { + "additionalProperties": false, + "properties": { + "concurrency": { + "description": "Number of requests a function can serve at once.", + "type": "number" + }, + "cors": { + "description": "If true, allows CORS on requests to this function.\nIf this is a `string` or `RegExp`, allows requests from domains that match the provided value.\nIf this is an `Array`, allows requests from domains matching at least one entry of the array.\nDefaults to true for {@link https.CallableFunction} and false otherwise.", + "type": [ + "string", + "boolean" + ] + }, + "cpu": { + "anyOf": [ + { + "enum": [ + "gcf_gen1" + ], + "type": "string" + }, + { + "type": "number" + } + ], + "description": "Fractional number of CPUs to allocate to a function." + }, + "enforceAppCheck": { + "description": "Determines whether Firebase AppCheck is enforced. Defaults to false.", + "type": "boolean" + }, + "ingressSettings": { + "description": "Ingress settings which control where this function can be called from.", + "enum": [ + "ALLOW_ALL", + "ALLOW_INTERNAL_AND_GCLB", + "ALLOW_INTERNAL_ONLY" + ], + "type": "string" + }, + "invoker": { + "description": "Invoker to set access control on https functions.", + "enum": [ + "public" + ], + "type": "string" + }, + "labels": { + "$ref": "#/definitions/Record", + "description": "User labels to set on the function." + }, + "maxInstances": { + "description": "Max number of instances to be running in parallel.", + "type": "number" + }, + "memory": { + "description": "Amount of memory to allocate to a function.", + "enum": [ + "128MiB", + "16GiB", + "1GiB", + "256MiB", + "2GiB", + "32GiB", + "4GiB", + "512MiB", + "8GiB" + ], + "type": "string" + }, + "minInstances": { + "description": "Min number of actual instances to be running at a given time.", + "type": "number" + }, + "omit": { + "description": "If true, do not deploy or emulate this function.", + "type": "boolean" + }, + "preserveExternalChanges": { + "description": "Controls whether function configuration modified outside of function source is preserved. Defaults to false.", + "type": "boolean" + }, + "region": { + "description": "HTTP functions can override global options and can specify multiple regions to deploy to.", + "type": "string" + }, + "secrets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "serviceAccount": { + "description": "Specific service account for the function to run as.", + "type": "string" + }, + "timeoutSeconds": { + "description": "Timeout for the function in seconds, possible values are 0 to 540.\nHTTPS functions can specify a higher timeout.", + "type": "number" + }, + "vpcConnector": { + "description": "Connect cloud function to specified VPC connector.", + "type": "string" + }, + "vpcConnectorEgressSettings": { + "description": "Egress settings for VPC connector.", + "enum": [ + "ALL_TRAFFIC", + "PRIVATE_RANGES_ONLY" + ], + "type": "string" + } + }, + "type": "object" + }, + "Record": { + "additionalProperties": false, + "type": "object" + } + }, + "properties": { + "$schema": { + "format": "uri", + "type": "string" + }, + "database": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + } + }, + "required": [ + "rules" + ], + "type": "object" + }, + { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "instance": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "instance", + "rules" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "instance": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "rules", + "target" + ], + "type": "object" + } + ] + }, + "type": "array" + } + ] + }, + "dataconnect": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "source": { + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object" + }, + { + "items": { + "additionalProperties": false, + "properties": { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "source": { + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object" + }, + "type": "array" + } + ] + }, + "emulators": { + "additionalProperties": false, + "properties": { + "apphosting": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "startCommandOverride": { + "type": "string" + } + }, + "type": "object" + }, + "auth": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "database": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "dataconnect": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "postgresHost": { + "type": "string" + }, + "postgresPort": { + "type": "integer" + } + }, + "type": "object" + }, + "eventarc": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "extensions": { + "properties": { + }, + "type": "object" + }, + "firestore": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "websocketPort": { + "type": "integer" + } + }, + "type": "object" + }, + "functions": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "hosting": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "hub": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "logging": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "pubsub": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "singleProjectMode": { + "type": "boolean" + }, + "storage": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "tasks": { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "ui": { + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "port": { + "type": [ + "integer" + ] + } + }, + "type": "object" + } + }, + "type": "object" + }, + "extensions": { + "$ref": "#/definitions/ExtensionsConfig" + }, + "firestore": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" + }, + "indexes": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + } + }, + "type": "object" + }, + { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" + }, + "indexes": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "target" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" + }, + "indexes": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + } + ] + }, + "type": "array" + } + ] + }, + "functions": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "codebase": { + "type": "string" + }, + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "runtime": { + "enum": [ + "nodejs10", + "nodejs12", + "nodejs14", + "nodejs16", + "nodejs18", + "nodejs20", + "nodejs22", + "nodejs6", + "nodejs8", + "python310", + "python311", + "python312" + ], + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object" + }, + { + "items": { + "additionalProperties": false, + "properties": { + "codebase": { + "type": "string" + }, + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "runtime": { + "enum": [ + "nodejs10", + "nodejs12", + "nodejs14", + "nodejs16", + "nodejs18", + "nodejs20", + "nodejs22", + "nodejs6", + "nodejs8", + "python310", + "python311", + "python312" + ], + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + ] + }, + "hosting": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "appAssociation": { + "enum": [ + "AUTO", + "NONE" + ], + "type": "string" + }, + "cleanUrls": { + "type": "boolean" + }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, + "headers": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "type": "string" + } + }, + "required": [ + "headers", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "headers", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "i18n": { + "additionalProperties": false, + "properties": { + "root": { + "type": "string" + } + }, + "required": [ + "root" + ], + "type": "object" + }, + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "public": { + "type": "string" + }, + "redirects": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "rewrites": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "region": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "run", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "source": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "regex": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "regex", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "site": { + "type": "string" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "trailingSlash": { + "type": "boolean" + } + }, + "type": "object" + }, + { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "appAssociation": { + "enum": [ + "AUTO", + "NONE" + ], + "type": "string" + }, + "cleanUrls": { + "type": "boolean" + }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, + "headers": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "type": "string" + } + }, + "required": [ + "headers", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "headers", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "i18n": { + "additionalProperties": false, + "properties": { + "root": { + "type": "string" + } + }, + "required": [ + "root" + ], + "type": "object" + }, + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "public": { + "type": "string" + }, + "redirects": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "rewrites": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "region": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "run", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "source": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "regex": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "regex", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "site": { + "type": "string" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "trailingSlash": { + "type": "boolean" + } + }, + "required": [ + "target" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "appAssociation": { + "enum": [ + "AUTO", + "NONE" + ], + "type": "string" + }, + "cleanUrls": { + "type": "boolean" + }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, + "headers": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "source": { + "type": "string" + } + }, + "required": [ + "headers", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "headers", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "i18n": { + "additionalProperties": false, + "properties": { + "root": { + "type": "string" + } + }, + "required": [ + "root" + ], + "type": "object" + }, + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "public": { + "type": "string" + }, + "redirects": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "rewrites": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "destination", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "region": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "function", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + }, + "source": { + "type": "string" + } + }, + "required": [ + "run", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "source": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "destination", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "additionalProperties": false, + "properties": { + "functionId": { + "type": "string" + }, + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + } + }, + "required": [ + "functionId" + ], + "type": "object" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "function", + "regex" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "regex": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "pinTag": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "regex", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "regex": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "regex" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "site": { + "type": "string" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "trailingSlash": { + "type": "boolean" + } + }, + "required": [ + "site" + ], + "type": "object" + } + ] + }, + "type": "array" + } + ] + }, + "remoteconfig": { + "additionalProperties": false, + "properties": { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "template": { + "type": "string" + } + }, + "required": [ + "template" + ], + "type": "object" + }, + "storage": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "rules" + ], + "type": "object" + }, + { + "items": { + "additionalProperties": false, + "properties": { + "bucket": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "bucket", + "rules" + ], + "type": "object" + }, + "type": "array" + } + ] + } + }, + "type": "object" +} diff --git a/firebase-devservices/deployment/src/test/firebase.json b/firebase-devservices/deployment/src/test/firebase.json new file mode 100644 index 00000000..dc71457c --- /dev/null +++ b/firebase-devservices/deployment/src/test/firebase.json @@ -0,0 +1,40 @@ +{ + "emulators": { + "firestore": { + "host": "0.0.0.0", + "port": 7002, + "websocketPort": 7003 + }, + "storage": { + "host": "0.0.0.0", + "port": 7005 + }, + "hosting": { + "host": "0.0.0.0", + "port": 7006 + }, + "functions": { + "host": "0.0.0.0", + "port": 7007 + }, + "ui": { + "host": "0.0.0.0", + "enabled": true, + "port": 7009 + }, + "singleProjectMode": true + }, + "hosting": { + "public": "hosting" + }, + "functions": { + "source": "functions" + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "storage": { + "rules": "storage.rules" + } +} diff --git a/firebase-devservices/deployment/src/test/firestore.indexes.json b/firebase-devservices/deployment/src/test/firestore.indexes.json new file mode 100644 index 00000000..28787432 --- /dev/null +++ b/firebase-devservices/deployment/src/test/firestore.indexes.json @@ -0,0 +1,39 @@ +{ + "indexes": [], + "fieldOverrides": [ + { + "collectionGroup": "Aanmelding", + "fieldPath": "datum", + "ttl": false, + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] + }, + { + "collectionGroup": "events", + "fieldPath": "transactionId", + "ttl": false, + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION" + }, + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] + } + ] +} diff --git a/firebase-devservices/deployment/src/test/firestore.rules b/firebase-devservices/deployment/src/test/firestore.rules new file mode 100644 index 00000000..5627b22e --- /dev/null +++ b/firebase-devservices/deployment/src/test/firestore.rules @@ -0,0 +1,9 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /data/{document} { + allow read: if request.auth != null && request.auth.uid == resource.data.ownerId; + allow write: if request.auth != null && request.auth.uid == resource.data.ownerId; + } + } +} \ No newline at end of file diff --git a/firebase-devservices/deployment/src/test/functions/.gitignore b/firebase-devservices/deployment/src/test/functions/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/firebase-devservices/deployment/src/test/functions/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/firebase-devservices/deployment/src/test/functions/index.js b/firebase-devservices/deployment/src/test/functions/index.js new file mode 100644 index 00000000..890feb44 --- /dev/null +++ b/firebase-devservices/deployment/src/test/functions/index.js @@ -0,0 +1,8 @@ +// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers. +const {logger} = require("firebase-functions"); +const {onRequest} = require("firebase-functions/v2/https"); + +exports.helloworld = onRequest(async (req, res) => { + logger.log("Received hello world request"); + res.send("Hello world"); +}); diff --git a/firebase-devservices/deployment/src/test/functions/package-lock.json b/firebase-devservices/deployment/src/test/functions/package-lock.json new file mode 100644 index 00000000..fc4671b0 --- /dev/null +++ b/firebase-devservices/deployment/src/test/functions/package-lock.json @@ -0,0 +1,2694 @@ +{ + "name": "functions", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "functions", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "firebase-admin": "^13.0.1", + "firebase-functions": "^6.1.2" + }, + "engines": { + "node": "20" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", + "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", + "license": "MIT" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", + "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", + "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.11", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", + "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.11", + "@firebase/database": "1.0.10", + "@firebase/database-types": "1.0.7", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", + "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.2" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", + "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", + "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", + "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", + "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "get-intrinsic": "^1.2.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "optional": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/firebase-admin": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.1.tgz", + "integrity": "sha512-sKQ/Yw8o/WdC9qTKvuLMBjTbdcBISIXW4+M9PXk0bNjxEbZf1Er7EVq47eRb5+bnKof10xlns6zAIbj4tmSexg==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.10.0", + "@google-cloud/storage": "^7.14.0" + } + }, + "node_modules/firebase-functions": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.1.2.tgz", + "integrity": "sha512-1ZKLLOs4YhpzfWOZo0wsqNBusy9113GUfRs89BF6yOlmkxlcJxdJzZEs/ygWeXVJKquZhW2K1Gm10Gir4RJxGQ==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.21", + "cors": "^2.8.5", + "express": "^4.21.0", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0" + } + }, + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/firebase-devservices/deployment/src/test/functions/package.json b/firebase-devservices/deployment/src/test/functions/package.json new file mode 100644 index 00000000..a43fa9cc --- /dev/null +++ b/firebase-devservices/deployment/src/test/functions/package.json @@ -0,0 +1,18 @@ +{ + "name": "functions", + "version": "1.0.0", + "description": "Test functions for TestContainers Firebase", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Jeroen Benckhuijsen (jeroen.benckhuijsen@group9.nl)", + "license": "Apache-2.0", + "engines": { + "node": "20" + }, + "dependencies": { + "firebase-admin": "^13.0.1", + "firebase-functions": "^6.1.2" + } +} diff --git a/firebase-devservices/deployment/src/test/hosting/test.me b/firebase-devservices/deployment/src/test/hosting/test.me new file mode 100644 index 00000000..003eae77 --- /dev/null +++ b/firebase-devservices/deployment/src/test/hosting/test.me @@ -0,0 +1 @@ +This is a test file for hosting \ No newline at end of file diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java new file mode 100644 index 00000000..49397aca --- /dev/null +++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java @@ -0,0 +1,213 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer; + +class FirebaseEmulatorConfigBuilderTest { + + private FirebaseEmulatorConfigBuilder configBuilder; + + @BeforeEach + void setUp() { + var projectConfig = new TestProjectConfig( + Optional.of("my-project-id")); + var config = new TestFirebaseDevServiceConfig( + new TestFirebase( + true, + new TestFirebaseEmulator( + "11.0.0", + new TestDocker( + "node:21-alpine", + Optional.of(1001), + Optional.of(1002), + Optional.empty(), + Optional.empty(), + Optional.of(false), + Optional.of(true)), + new TestCli( + Optional.of("MY_TOKEN"), + Optional.of("-Xmx"), + Optional.of("data"), + Optional.of(FirebaseEmulatorContainer.ImportExport.EXPORT_ONLY), + Optional.of(true)), + Optional.empty(), + new TestUI( + true, + Optional.of(6000), + Optional.of(6001), + Optional.of(6002))), + new TestGenericDevService(true, Optional.of(6003)), + new TestHosting( + true, + Optional.of(6004), + Optional.of("public")), + new TestGenericDevService( + false, + Optional.of(6005)), + new TestFirestoreDevService( + true, + Optional.of(6006), + Optional.of(6007), + Optional.of("firestore.rules"), + Optional.of("firestore.indexes.json"))), + new TestGenericDevService( + true, + Optional.of(6008)), + new TestGenericDevService( + true, + Optional.of(6009)), + new TestStorageDevService( + true, + Optional.empty(), + Optional.of("storage.rules"))); + configBuilder = new FirebaseEmulatorConfigBuilder(projectConfig, config); + } + + @Test + void testBuild() { + FirebaseEmulatorContainer.EmulatorConfig emulatorConfig = configBuilder.buildConfig(); + + assertNotNull(emulatorConfig); + + assertEquals("node:21-alpine", emulatorConfig.dockerConfig().imageName()); + assertEquals(1001, emulatorConfig.dockerConfig().userId().orElse(null)); + assertEquals(1002, emulatorConfig.dockerConfig().groupId().orElse(null)); + assertFalse(emulatorConfig.dockerConfig().followStdOut()); + assertTrue(emulatorConfig.dockerConfig().followStdErr()); + + assertEquals("11.0.0", emulatorConfig.firebaseVersion()); + + assertEquals("my-project-id", emulatorConfig.cliArguments().projectId().orElse(null)); + assertEquals("MY_TOKEN", emulatorConfig.cliArguments().token().orElse(null)); + assertEquals("-Xmx", emulatorConfig.cliArguments().javaToolOptions().orElse(null)); + assertPathEndsWith("data", emulatorConfig.cliArguments().emulatorData().orElse(null)); + assertEquals(FirebaseEmulatorContainer.ImportExport.EXPORT_ONLY, emulatorConfig.cliArguments().importExport()); + assertTrue(emulatorConfig.cliArguments().debug()); + + assertTrue(emulatorConfig.customFirebaseJson().isEmpty()); + + assertPathEndsWith("public", emulatorConfig.firebaseConfig().hostingConfig().hostingContentDir().orElse(null)); + assertPathEndsWith("storage.rules", emulatorConfig.firebaseConfig().storageConfig().rulesFile().orElse(null)); + assertPathEndsWith("firestore.rules", emulatorConfig.firebaseConfig().firestoreConfig().rulesFile().orElse(null)); + assertPathEndsWith("firestore.indexes.json", + emulatorConfig.firebaseConfig().firestoreConfig().indexesFile().orElse(null)); + + } + + private void assertPathEndsWith(String expected, Path path) { + assertNotNull(path); + assertTrue(path.toString().endsWith(expected)); + } + + @Test + void testExposedEmulators() { + FirebaseEmulatorContainer.EmulatorConfig emulatorConfig = configBuilder.buildConfig(); + + Map exposedPorts = emulatorConfig + .firebaseConfig().services(); + + assertEquals(10, exposedPorts.size()); + assertEquals(6000, exposedPorts.get(FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI).fixedPort()); + assertEquals(6001, exposedPorts.get(FirebaseEmulatorContainer.Emulator.LOGGING).fixedPort()); + assertEquals(6002, exposedPorts.get(FirebaseEmulatorContainer.Emulator.EMULATOR_HUB).fixedPort()); + assertEquals(6003, exposedPorts.get(FirebaseEmulatorContainer.Emulator.AUTHENTICATION).fixedPort()); + assertEquals(6004, exposedPorts.get(FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING).fixedPort()); + assertEquals(6006, exposedPorts.get(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE).fixedPort()); + assertEquals(6007, exposedPorts.get(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE_WS).fixedPort()); + assertEquals(6008, exposedPorts.get(FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS).fixedPort()); + assertEquals(6009, exposedPorts.get(FirebaseEmulatorContainer.Emulator.PUB_SUB).fixedPort()); + assertNull(exposedPorts.get(FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE).fixedPort()); + + assertNull(exposedPorts.get(FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE)); + } + + // Record implementations for interfaces + record TestProjectConfig( + Optional projectId) implements FirebaseDevServiceProjectConfig { + } + + record TestFirebaseDevServiceConfig( + FirebaseDevServiceConfig.Firebase firebase, + FirebaseDevServiceConfig.GenericDevService functions, + FirebaseDevServiceConfig.GenericDevService pubsub, + FirebaseDevServiceConfig.StorageDevService storage) implements FirebaseDevServiceConfig { + } + + record TestFirebase( + boolean preferFirebaseDevServices, + Emulator emulator, + FirebaseDevServiceConfig.GenericDevService auth, + FirebaseDevServiceConfig.Firebase.HostingDevService hosting, + FirebaseDevServiceConfig.GenericDevService database, + FirebaseDevServiceConfig.Firebase.FirestoreDevService firestore) implements FirebaseDevServiceConfig.Firebase { + + } + + record TestFirebaseEmulator( + String firebaseVersion, + FirebaseDevServiceConfig.Firebase.Emulator.Docker docker, + FirebaseDevServiceConfig.Firebase.Emulator.Cli cli, + Optional customFirebaseJson, + UI ui) implements FirebaseDevServiceConfig.Firebase.Emulator { + } + + record TestDocker( + String imageName, + Optional dockerUser, + Optional dockerGroup, + Optional dockerUserEnv, + Optional dockerGroupEnv, + Optional followStdOut, + Optional followStdErr) implements FirebaseDevServiceConfig.Firebase.Emulator.Docker { + } + + record TestCli( + Optional token, + Optional javaToolOptions, + Optional emulatorData, + Optional importExport, + Optional debug) implements FirebaseDevServiceConfig.Firebase.Emulator.Cli { + } + + record TestUI( + boolean enabled, + Optional emulatorPort, + Optional loggingPort, + Optional hubPort) implements FirebaseDevServiceConfig.Firebase.Emulator.UI { + } + + record TestFirestoreDevService( + boolean enabled, + Optional emulatorPort, + Optional websocketPort, + Optional rulesFile, + Optional indexesFile) implements FirebaseDevServiceConfig.Firebase.FirestoreDevService { + } + + record TestHosting( + boolean enabled, + Optional emulatorPort, + Optional hostingPath) implements FirebaseDevServiceConfig.Firebase.HostingDevService { + } + + record TestStorageDevService( + boolean enabled, + Optional emulatorPort, + Optional rulesFile) implements FirebaseDevServiceConfig.StorageDevService { + + } + + record TestGenericDevService( + boolean enabled, + Optional emulatorPort) implements FirebaseDevServiceConfig.GenericDevService { + } + +} diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java new file mode 100644 index 00000000..77312a87 --- /dev/null +++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java @@ -0,0 +1,88 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class FirebaseEmulatorContainerCustomConfigTest { + + private static final File tempEmulatorDataDir; + + static { + try { + // Create a temporary directory for emulator data + tempEmulatorDataDir = new File("target/firebase-emulator-container-data"); + tempEmulatorDataDir.mkdirs(); + var testContainer = new TestableFirebaseEmulatorContainer("FirebaseEmulatorContainerCustomConfigTest"); + firebaseContainer = testContainer.testBuilder() + .withCliArguments() + .withEmulatorData(tempEmulatorDataDir.toPath()) + .done() + .readFromFirebaseJson(new File("src/test/firebase.json").toPath()) + .build(); + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Container + private static final FirebaseEmulatorContainer firebaseContainer; + + @Test + public void testFirestoreRulesAndIndexes() throws InterruptedException, IOException { + // Verify the firebase.json file exists in the container + String firebaseJsonCheck = firebaseContainer.execInContainer("cat", "/srv/firebase/firebase.json").getStdout(); + assertTrue(firebaseJsonCheck.contains("\"emulators\""), "Expected firebase.json to be present in the container"); + + // Verify the firestore.rules file exists in the container + String firestoreRulesCheck = firebaseContainer.execInContainer("cat", "/srv/firebase/firestore.rules").getStdout(); + assertTrue(firestoreRulesCheck.contains("service cloud.firestore"), + "Expected firestore.rules to be present in the container"); + } + + @Test + public void testStorageRules() throws IOException, InterruptedException { + // Verify the storage.rules file exists in the container + String storageRulesCheck = firebaseContainer.execInContainer("cat", "/srv/firebase/storage.rules").getStdout(); + assertTrue(storageRulesCheck.contains("service firebase.storage"), + "Expected storage.rules to be present in the container"); + } + + @Test + public void testHosting() throws IOException, InterruptedException, URISyntaxException { + HttpClient httpClient = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .GET() + .uri(new URI("http://localhost:7006/test.me")) + .build(); + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + var body = response.body(); + assertEquals("This is a test file for hosting", body); + } + + @Test + public void testFunctions() throws IOException, InterruptedException, URISyntaxException { + HttpClient httpClient = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .GET() + .uri(new URI("http://localhost:7007/demo-test-project/us-central1/helloworld")) + .build(); + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + var body = response.body(); + assertEquals("Hello world", body); + } + +} diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java new file mode 100644 index 00000000..122cbba3 --- /dev/null +++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java @@ -0,0 +1,362 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.GrpcTransportChannel; +import com.google.api.gax.rpc.FixedTransportChannelProvider; +import com.google.cloud.NoCredentials; +import com.google.cloud.firestore.*; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.cloud.pubsub.v1.TopicAdminClient; +import com.google.cloud.pubsub.v1.TopicAdminSettings; +import com.google.cloud.storage.*; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseAuthException; +import com.google.firebase.auth.UserRecord; +import com.google.firebase.database.*; +import com.google.firebase.internal.EmulatorCredentials; +import com.google.firebase.internal.FirebaseProcessEnvironment; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +@Testcontainers +public class FirebaseEmulatorContainerIntegrationTest { + + private static final File tempDataParent; + private static final File tempEmulatorDataDir; + private static final File tempHostingContentDir; + + static { + try { + tempDataParent = new File("target/firebase-emulator-it"); + + // Create a temporary directory for emulator data + tempEmulatorDataDir = new File(tempDataParent, "firebase-emulator-data"); + tempEmulatorDataDir.mkdirs(); + tempHostingContentDir = new File(tempDataParent, "firebase-hosting-content"); + tempHostingContentDir.mkdirs(); + + // Create a static HTML file in the hosting directory + File indexFile = new File(tempHostingContentDir, "index.html"); + try (FileWriter writer = new FileWriter(indexFile, Charset.defaultCharset())) { + writer.write("

Hello, Firebase Hosting!

"); + } + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private static final TestableFirebaseEmulatorContainer testContainer = new TestableFirebaseEmulatorContainer( + "FirebaseEmulatorContainerIntegrationTest", + FirebaseEmulatorContainerIntegrationTest::customizeFirebaseOptions); + + private static final FirebaseEmulatorContainer firebaseContainer = testContainer.testBuilder() + .withCliArguments() + .withEmulatorData(tempEmulatorDataDir.toPath()) + .done() + .withFirebaseConfig() + .withHostingPath(tempHostingContentDir.toPath()) + .withFunctionsFromPath(new File("src/test/functions").toPath()) + .withEmulatorsOnPorts( + FirebaseEmulatorContainer.Emulator.AUTHENTICATION, 6000, + FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE, 6001, + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE, 6002, + FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE_WS, 6003, + FirebaseEmulatorContainer.Emulator.PUB_SUB, 6004, + FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE, 6005, + FirebaseEmulatorContainer.Emulator.FIREBASE_HOSTING, 6006, + FirebaseEmulatorContainer.Emulator.CLOUD_FUNCTIONS, 6007, + // Emulator.EVENT_ARC, 6008, + FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI, 6009, + FirebaseEmulatorContainer.Emulator.EMULATOR_HUB, 6010, + FirebaseEmulatorContainer.Emulator.LOGGING, 6011) + .done() + .build(); + + private static void customizeFirebaseOptions(FirebaseOptions.Builder builder) { + var emulatorHost = firebaseContainer.getHost(); + var dbPort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.REALTIME_DATABASE); + + builder.setDatabaseUrl("http://" + emulatorHost + ":" + dbPort + "?ns=demo-test-project"); + } + + @BeforeAll + public static void setup() { + firebaseContainer.start(); + } + + @AfterAll + public static void tearDown() { + firebaseContainer.stop(); + + validateEmulatorDataWritten(); + + // Recursively delete the contents of the directories and then delete the directories + deleteDirectoryRecursively(tempDataParent); + } + + // Helper method to recursively delete all files and directories + private static void deleteDirectoryRecursively(File directory) { + if (directory.exists()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirectoryRecursively(file); + } else { + assertTrue(file.delete()); + } + } + } + assertTrue(directory.delete()); + } + } + + private static void validateEmulatorDataWritten() { + var emulatorDataDir = new File(tempEmulatorDataDir, "emulator-data"); + assertTrue(emulatorDataDir.exists()); + assertTrue(emulatorDataDir.isDirectory()); + assertTrue(emulatorDataDir.canRead()); + assertTrue(emulatorDataDir.canWrite()); + assertTrue(emulatorDataDir.canExecute()); + + // Verify that files were written to the emulator data directory + File[] files = emulatorDataDir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 0, "Expected files to be present in the emulator data directory"); + } + + @Test + public void testFirebaseAuthenticationEmulatorConnection() throws FirebaseAuthException { + // Retrieve the host and port for the Authentication emulator + int authPort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.AUTHENTICATION); + + // Set the environment variable for the Firebase Authentication emulator + FirebaseProcessEnvironment.setenv("FIREBASE_AUTH_EMULATOR_HOST", firebaseContainer.getHost() + ":" + authPort); + + // Initialize FirebaseOptions without setting the auth emulator host directly + FirebaseAuth auth = FirebaseAuth.getInstance(testContainer.getApp()); + + // Create a test user and verify it + UserRecord.CreateRequest request = new UserRecord.CreateRequest() + .setEmail("user@example.com") + .setPassword("password"); + UserRecord userRecord = auth.createUser(request); + + assertNotNull(userRecord); + assertEquals("user@example.com", userRecord.getEmail()); + + // Clean up by deleting the test user + auth.deleteUser(userRecord.getUid()); + } + + @Test + public void testFirestoreEmulatorConnection() throws Exception { + int firestorePort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.CLOUD_FIRESTORE); + + FirestoreOptions options = FirestoreOptions.newBuilder() + .setProjectId("demo-test-project") + .setEmulatorHost(firebaseContainer.getHost() + ":" + firestorePort) + .setCredentials(new EmulatorCredentials()) + .build(); + + try (Firestore firestore = options.getService()) { + DocumentReference docRef = firestore.collection("testCollection").document("testDoc"); + ApiFuture result = docRef.set(Map.of("field", "value")); + + assertNotNull(result.get()); + DocumentSnapshot snapshot = docRef.get().get(); + assertEquals("value", snapshot.getString("field")); + } + } + + @Test + public void testRealtimeDatabaseEmulatorConnection() throws ExecutionException, InterruptedException { + DatabaseReference ref = FirebaseDatabase.getInstance(testContainer.getApp()).getReference("testData"); + + // Write data to the database + ref.setValueAsync("testValue").get(); + + // Set up a listener and latch for asynchronous reading + CountDownLatch latch = new CountDownLatch(1); + final String[] value = { null }; + + ref.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot snapshot) { + value[0] = snapshot.getValue(String.class); + latch.countDown(); + } + + @Override + public void onCancelled(DatabaseError error) { + latch.countDown(); + } + }); + + // Wait for the listener to retrieve data + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals("testValue", value[0], "Expected to retrieve 'testValue' from Realtime Database"); + } + + @Test + public void testPubSubEmulatorConnection() throws Exception { + // Retrieve the host and port for the Pub/Sub emulator + int pubSubPort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.PUB_SUB); + + // Set up a gRPC channel to the Pub/Sub emulator + ManagedChannel channel = ManagedChannelBuilder.forAddress(firebaseContainer.getHost(), pubSubPort) + .usePlaintext() + .build(); + + // Set the channel provider for Pub/Sub client + FixedTransportChannelProvider channelProvider = FixedTransportChannelProvider + .create(GrpcTransportChannel.create(channel)); + + TopicAdminSettings topicAdminSettings = TopicAdminSettings.newBuilder() + .setCredentialsProvider(new NoCredentialsProvider()) + .setTransportChannelProvider(channelProvider) + .build(); + + try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) { + topicAdminClient.createTopic("projects/demo-test-project/topics/testTopic"); + } + + // Create a publisher with the channel provider + Publisher publisher = Publisher.newBuilder("projects/demo-test-project/topics/testTopic") + .setChannelProvider(channelProvider) + .setCredentialsProvider(new NoCredentialsProvider()) + .build(); + + // Publish a message to the Pub/Sub emulator + PubsubMessage message = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8("Test message")).build(); + ApiFuture messageIdFuture = publisher.publish(message); + assertNotNull(messageIdFuture.get(), "Expected message to be published successfully"); + + // Shutdown the channel + channel.shutdownNow(); + } + + @Test + public void testStorageEmulatorConnection() throws IOException { + int storagePort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.CLOUD_STORAGE); + + Storage storage = StorageOptions.newBuilder() + .setHost("http://" + firebaseContainer.getHost() + ":" + storagePort) + .setProjectId("demo-test-project") + .setCredentials(NoCredentials.getInstance()) + .build().getService(); + + var bucketName = "demo-test-project.appspot.com"; + + BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, "test-upload") + .setContentType("application/json") + .setContentDisposition("attachment; filename=\"test-upload\"") + .build(); + + try (var writer = storage.writer(blobInfo)) { + writer.write(ByteBuffer.wrap("{\"test\": 1}".getBytes(StandardCharsets.UTF_8))); + } + + try (var reader = storage.reader(blobInfo.getBlobId())) { + try (var bufReader = new BufferedReader(Channels.newReader(reader, StandardCharsets.UTF_8))) { + var contents = bufReader.readLine(); + assertEquals("{\"test\": 1}", contents, "Expected blob content to match"); + } + } + } + + @Test + public void testEmulatorUIReachable() throws Exception { + // Get the host and port for the Emulator UI + int uiPort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.EMULATOR_SUITE_UI); + + // Construct the URL for the Emulator UI root (where index.html would be served) + URL url = new URI("http://" + firebaseContainer.getHost() + ":" + uiPort + "/").toURL(); + + // Open a connection and send an HTTP GET request + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + // Get the response code to confirm the UI is reachable + int responseCode = connection.getResponseCode(); + assertEquals(200, responseCode, "Expected HTTP status 200 for Emulator UI index.html"); + + // Close the connection + connection.disconnect(); + } + + @Test + public void testEmulatorHub() throws Exception { + // Get the host and port for the Emulator UI + int uiPort = firebaseContainer.emulatorPort(FirebaseEmulatorContainer.Emulator.EMULATOR_HUB); + + // Construct the URL for the Emulator UI root (where index.html would be served) + URL url = new URI("http://" + firebaseContainer.getHost() + ":" + uiPort + "/emulators").toURL(); + + // Open a connection and send an HTTP GET request + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + // Get the response code to confirm the UI is reachable + int responseCode = connection.getResponseCode(); + assertEquals(200, responseCode, "Expected HTTP status 200 for Emulator Hub API"); + + // Close the connection + connection.disconnect(); + } + + @Test + public void testHosting() throws IOException, InterruptedException, URISyntaxException { + HttpClient httpClient = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .GET() + .uri(new URI("http://localhost:6006/index.html")) + .build(); + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + var body = response.body(); + assertEquals("

Hello, Firebase Hosting!

", body); + } + + @Test + public void testFunctions() throws IOException, InterruptedException, URISyntaxException { + HttpClient httpClient = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .GET() + .uri(new URI("http://localhost:6007/demo-test-project/us-central1/helloworld")) + .build(); + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + var body = response.body(); + assertEquals("Hello world", body); + } + +} diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/TestableFirebaseEmulatorContainer.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/TestableFirebaseEmulatorContainer.java new file mode 100644 index 00000000..f43be657 --- /dev/null +++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/TestableFirebaseEmulatorContainer.java @@ -0,0 +1,78 @@ +package io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers; + +import java.util.function.Consumer; + +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.internal.EmulatorCredentials; + +/** + * Subclass of {@link FirebaseEmulatorContainer} which has some extra facilities to ease testing. Functionally + * this class is equivalent of its superclass with respect to the testing we need to perform. + */ +public class TestableFirebaseEmulatorContainer { + + private final String name; + private final Consumer options; + private FirebaseApp app; + + /** + * Creates a new Firebase Emulator container + * + * @param name The name of the firebase app (must be unique across the JVM). + * @param options Consumer to handle additional changes to the FirebaseOptions.Builder. + */ + public TestableFirebaseEmulatorContainer(String name, Consumer options) { + this.name = name; + this.options = options; + } + + /** + * Creates a new Firebase Emulator container + * + * @param name The name of the firebase app (must be unique across the JVM). + */ + public TestableFirebaseEmulatorContainer(String name) { + this.name = name; + this.options = null; + } + + public FirebaseEmulatorContainer.Builder testBuilder() { + var builder = FirebaseEmulatorContainer.builder(); + + /* + * We determine the current group and user using an env variable. This is set by the GitHub Actions runner. + * The user and group are used to set the user/group for the user in the docker container run by + * TestContainers for the Firebase Emulators. This way, the data exported by the Firebase Emulators + * can be read from the build. + */ + builder.withDockerConfig() + .withUserIdFromEnv("CURRENT_USER") + .withGroupIdFromEnv("CURRENT_GROUP") + .afterStart(this::afterStart) + .done() + .withFirebaseVersion("latest") + .withCliArguments() + .withProjectId("demo-test-project") + .done(); + + return builder; + } + + private void afterStart(FirebaseEmulatorContainer container) { + var firebaseBuilder = FirebaseOptions.builder() + .setProjectId("demo-test-project") + .setCredentials(new EmulatorCredentials()); + + if (options != null) { + options.accept(firebaseBuilder); + } + + FirebaseOptions options = firebaseBuilder.build(); + app = FirebaseApp.initializeApp(options, name); + } + + public FirebaseApp getApp() { + return app; + } +} diff --git a/firebase-devservices/deployment/src/test/storage.rules b/firebase-devservices/deployment/src/test/storage.rules new file mode 100644 index 00000000..17f5f58e --- /dev/null +++ b/firebase-devservices/deployment/src/test/storage.rules @@ -0,0 +1,10 @@ +service firebase.storage { + match /b/{bucket}/o { + match /company/{allPaths=**} { + allow read: if true + } + match /building/{allPaths=**} { + allow read: if true + } + } +} diff --git a/firebase-devservices/pom.xml b/firebase-devservices/pom.xml new file mode 100644 index 00000000..0ad36293 --- /dev/null +++ b/firebase-devservices/pom.xml @@ -0,0 +1,20 @@ + + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-services-parent + 2.14.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-google-cloud-firebase-devservices-parent + Quarkus - Google Cloud Services - Firebas Dev Services + pom + + + runtime + deployment + + + \ No newline at end of file diff --git a/firebase-devservices/runtime/pom.xml b/firebase-devservices/runtime/pom.xml new file mode 100644 index 00000000..c9e69e71 --- /dev/null +++ b/firebase-devservices/runtime/pom.xml @@ -0,0 +1,55 @@ + + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices-parent + 2.14.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-google-cloud-firebase-devservices + Quarkus - Google Cloud Services - Firebase Dev Services - Runtime + Use Google Cloud Firebase + + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-common + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + \ No newline at end of file diff --git a/firebase-devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/firebase-devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..c04a5b47 --- /dev/null +++ b/firebase-devservices/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,16 @@ +--- +name: "Google Cloud Firebase Devservices" +artifact: ${project.groupId}:${project.artifactId}:${project.version} +metadata: + keywords: + - "firebase" + - "google cloud" + - "gcloud" + - "gcp" + categories: + - "cloud" + - "data" + guide: "https://quarkiverse.github.io/quarkiverse-docs/quarkus-google-cloud-services/main/firebase-devservices.html" + status: "experimental" + config: + - "quarkus.google.cloud.devservices." diff --git a/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirebaseDevServiceConfig.java b/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirebaseDevServiceConfig.java new file mode 100644 index 00000000..418a634d --- /dev/null +++ b/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirebaseDevServiceConfig.java @@ -0,0 +1,23 @@ +package io.quarkiverse.googlecloudservices.firestore.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +/** + * Config mapping to detect if the Firebase Dev Services are running, in which case the PubSub dev service + * will be disabled by default as these two Devservice are in conflict with each other. + */ +@ConfigMapping(prefix = "quarkus.google.cloud.firebase.devservice") +@ConfigRoot +public interface FirebaseDevServiceConfig { + + /** + * Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module + * is included. In that case, the Firebase devservices will by default be preferred and the DevService for + * PubSub will be disabled. + */ + Optional preferFirebaseDevServices(); + +} diff --git a/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirestoreDevServiceProcessor.java b/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirestoreDevServiceProcessor.java index aebcab1f..86cd3f11 100644 --- a/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirestoreDevServiceProcessor.java +++ b/firestore/deployment/src/main/java/io/quarkiverse/googlecloudservices/firestore/deployment/FirestoreDevServiceProcessor.java @@ -35,6 +35,7 @@ public class FirestoreDevServiceProcessor { @BuildStep public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildItem, FirestoreBuildTimeConfig buildTimeConfig, + FirebaseDevServiceConfig firebaseConfig, List devServicesSharedNetworkBuildItem, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, @@ -56,7 +57,7 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI // Try starting the container if conditions are met try { - devService = startContainerIfAvailable(dockerStatusBuildItem, buildTimeConfig.devservice(), + devService = startContainerIfAvailable(dockerStatusBuildItem, buildTimeConfig.devservice(), firebaseConfig, globalDevServicesConfig.timeout); } catch (Throwable t) { LOGGER.warn("Unable to start Firestore dev service", t); @@ -80,6 +81,7 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI */ private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(DockerStatusBuildItem dockerStatusBuildItem, FirestoreDevServiceConfig config, + FirebaseDevServiceConfig firebaseConfig, Optional timeout) { if (!config.enabled()) { // Firestore service explicitly disabled @@ -87,6 +89,12 @@ private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(D return null; } + if (firebaseConfig.preferFirebaseDevServices().orElse(false)) { + // Firebase DevServices are included, use them instead + LOGGER.debug("Not starting Dev Services for Firestore as the Firebase DevServices are preferred"); + return null; + } + if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) { LOGGER.warn("Not starting devservice because docker is not available"); return null; diff --git a/integration-tests/firebase-admin/firebase.json b/integration-tests/firebase-admin/firebase.json index 2fb2a16b..94fd7894 100644 --- a/integration-tests/firebase-admin/firebase.json +++ b/integration-tests/firebase-admin/firebase.json @@ -1,10 +1,21 @@ { "emulators": { "auth": { - "port": 9099 + "port": 9099, + "host": "0.0.0.0" }, "ui": { - "enabled": true + "port": 4000, + "enabled": true, + "host": "0.0.0.0" + }, + "hub": { + "port": 4400, + "host": "0.0.0.0" + }, + "logging": { + "port": 4500, + "host": "0.0.0.0" }, "singleProjectMode": true } diff --git a/integration-tests/firebase-admin/pom.xml b/integration-tests/firebase-admin/pom.xml index 15eb82b5..dac3740a 100644 --- a/integration-tests/firebase-admin/pom.xml +++ b/integration-tests/firebase-admin/pom.xml @@ -37,6 +37,10 @@ rest-assured test
+ + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices + @@ -62,14 +66,10 @@ org.jboss.logmanager.LogManager - true maven-failsafe-plugin - - true - diff --git a/integration-tests/firebase-admin/src/main/resources/application.properties b/integration-tests/firebase-admin/src/main/resources/application.properties index f9b81cc0..bac883f6 100644 --- a/integration-tests/firebase-admin/src/main/resources/application.properties +++ b/integration-tests/firebase-admin/src/main/resources/application.properties @@ -1,4 +1,5 @@ # Set the project ID with the demo- prefix to use an emulated service. # When the emulator is started with this project ID, non-emulated services access will fail. quarkus.google.cloud.project-id=demo-test-project-id -quarkus.google.cloud.firebase.auth.enabled=true \ No newline at end of file +quarkus.google.cloud.firebase.auth.enabled=true +quarkus.google.cloud.devservices.project-id=demo-test-project-id \ No newline at end of file diff --git a/integration-tests/firebase-admin/src/test/java/io/quarkiverse/googlecloudservices/it/firebaseadmin/FirebaseAuthTest.java b/integration-tests/firebase-admin/src/test/java/io/quarkiverse/googlecloudservices/it/firebaseadmin/FirebaseAuthTest.java index 431f3d9b..72d300ca 100644 --- a/integration-tests/firebase-admin/src/test/java/io/quarkiverse/googlecloudservices/it/firebaseadmin/FirebaseAuthTest.java +++ b/integration-tests/firebase-admin/src/test/java/io/quarkiverse/googlecloudservices/it/firebaseadmin/FirebaseAuthTest.java @@ -10,13 +10,19 @@ public abstract class FirebaseAuthTest { @ConfigProperty(name = "quarkus.google.cloud.project-id") String projectId; + @ConfigProperty(name = "quarkus.google.cloud.firebase.auth.emulator-host") + String emulatorHost; + @BeforeEach public void deleteAllAccounts() { + var emulatorHostParts = emulatorHost.split(":"); + var port = emulatorHostParts.length == 2 ? Integer.parseInt(emulatorHostParts[1]) : 9099; + given() - .port(9099) + .port(port) .auth() .oauth2("owner") - .delete("http://localhost:9099/emulator/v1/projects/{projectId}/accounts", projectId) + .delete("/emulator/v1/projects/{projectId}/accounts", projectId) .then() .statusCode(200); } diff --git a/integration-tests/firebase/.firebaserc b/integration-tests/firebase/.firebaserc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/integration-tests/firebase/.firebaserc @@ -0,0 +1 @@ +{} diff --git a/integration-tests/firebase/.gitignore b/integration-tests/firebase/.gitignore new file mode 100644 index 00000000..dbb58ffb --- /dev/null +++ b/integration-tests/firebase/.gitignore @@ -0,0 +1,66 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/integration-tests/firebase/README.md b/integration-tests/firebase/README.md new file mode 100755 index 00000000..0d44d417 --- /dev/null +++ b/integration-tests/firebase/README.md @@ -0,0 +1,28 @@ +# Quarkus - Google Cloud Services - Integration Tests - Firebase Admin + +This integration test show how to integrate with Firebase Admin SDK. + +**WARNING**: It is disabled by default as it needs the emulator authenticated with a valid account to run. + +## Installing gcloud CLI and Emulator Suite + +1. Install the gcloud CLI following [this](https://cloud.google.com/sdk/docs/install) guide +2. Authenticate with your Google Account +```shell +$ gcloud auth login +``` +3. Install the Local Emulator Suite using [this](https://firebase.google.com/docs/emulator-suite/install_and_configure#install_the_local_emulator_suite) instructions + +## Running + +1. Before running the integration tests, start the Local Emulator Suite using the following command: +```shell +$ firebase emulators:start --project demo-test-project-id +``` + +_After that, the emulator will start in the default port, which the application is configured to connect to. Since it is configured using the `demo-` prefix, all operations will be performed in the emulator environment only, without affecting any real projects. If needed, you will be able to use the Emulator UI at http://localhost:4000/auth._ + +2. In order to have the Admin SDK connecting to the emulator, set the following environment variable: +```shell +$ export FIREBASE_AUTH_EMULATOR_HOST="localhost:9099" +``` \ No newline at end of file diff --git a/integration-tests/firebase/firebase.json b/integration-tests/firebase/firebase.json new file mode 100644 index 00000000..2fb2a16b --- /dev/null +++ b/integration-tests/firebase/firebase.json @@ -0,0 +1,11 @@ +{ + "emulators": { + "auth": { + "port": 9099 + }, + "ui": { + "enabled": true + }, + "singleProjectMode": true + } +} diff --git a/integration-tests/firebase/pom.xml b/integration-tests/firebase/pom.xml new file mode 100644 index 00000000..0833face --- /dev/null +++ b/integration-tests/firebase/pom.xml @@ -0,0 +1,81 @@ + + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-services-integration-tests-parent + 2.14.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-google-cloud-services-firebase-it + Quarkus - Google Cloud Services - Integration Tests - Firebase + + + true + true + ${skipTests} + + + + + io.quarkus + quarkus-rest-jackson + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firebase-devservices + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-firestore + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-pubsub + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + ${graalvmHome} + + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + maven-failsafe-plugin + + + + + diff --git a/integration-tests/firebase/src/main/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResource.java b/integration-tests/firebase/src/main/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResource.java new file mode 100644 index 00000000..3d084f66 --- /dev/null +++ b/integration-tests/firebase/src/main/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResource.java @@ -0,0 +1,89 @@ +package io.quarkiverse.googlecloudservices.it.firebase; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import com.google.cloud.firestore.Firestore; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; + +import io.quarkiverse.googlecloudservices.pubsub.QuarkusPubSub; +import io.quarkus.runtime.StartupEvent; + +@Path("/app") +public class FirebaseResource { + + @Inject + Firestore firestore; + + @Inject + QuarkusPubSub quarkusPubSub; + + Publisher publisher; + + public void init(@Observes StartupEvent event) throws IOException { + quarkusPubSub.createTopic("test"); + quarkusPubSub.createSubscription("test", "test"); + publisher = quarkusPubSub.publisher("test"); + } + + @POST + public void createData(String data) throws InterruptedException, ExecutionException { + Object monitor = new Object(); + + var subscriber = quarkusPubSub.subscriber("test", (message, consumer) -> { + try { + var col = firestore.collection("test"); + var msgData = message.getData().toString(StandardCharsets.UTF_8); + var fields = Map.of("test", msgData); + + col.document("test").create(fields).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + consumer.ack(); + + synchronized (monitor) { + monitor.notify(); + } + }); + subscriber.startAsync().awaitRunning(); + + publisher.publish(PubsubMessage + .newBuilder() + .setData(ByteString.copyFrom(data, StandardCharsets.UTF_8)) + .build()).get(); + synchronized (monitor) { + monitor.wait(5000); + } + + subscriber.stopAsync().awaitTerminated(); + } + + @GET + public Response getData() throws ExecutionException, InterruptedException { + var col = firestore.collection("test"); + var docs = col.listDocuments(); + var iter = docs.iterator(); + + if (iter.hasNext()) { + var docRef = iter.next(); + var snapshot = docRef.get().get(); + return Response.ok(snapshot.getString("test")).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + } +} diff --git a/integration-tests/firebase/src/main/resources/application.properties b/integration-tests/firebase/src/main/resources/application.properties new file mode 100644 index 00000000..1e24c613 --- /dev/null +++ b/integration-tests/firebase/src/main/resources/application.properties @@ -0,0 +1,8 @@ +# Set the project ID with the demo- prefix to use an emulated service. +# When the emulator is started with this project ID, non-emulated services access will fail. +quarkus.google.cloud.project-id=demo-test-project-id +quarkus.google.cloud.devservices.project-id=demo-test-project-id +quarkus.google.cloud.access-token-enabled=false +quarkus.google.cloud.devservices.firebase.auth.enabled=true +quarkus.google.cloud.devservices.firebase.firestore.enabled=true +quarkus.google.cloud.devservices.pubsub.enabled=true diff --git a/integration-tests/firebase/src/test/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResourceTest.java b/integration-tests/firebase/src/test/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResourceTest.java new file mode 100644 index 00000000..0ef5470f --- /dev/null +++ b/integration-tests/firebase/src/test/java/io/quarkiverse/googlecloudservices/it/firebase/FirebaseResourceTest.java @@ -0,0 +1,31 @@ +package io.quarkiverse.googlecloudservices.it.firebase; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class FirebaseResourceTest { + + @Test + void shouldCreateDataUsingPubsubInFirestore() { + given() + .body("some test string") + .post("/app") + .then() + .log().ifValidationFails() + .statusCode(204); + + given() + .get("/app") + .then() + .log().ifValidationFails() + .statusCode(200) + .assertThat() + .body(is("some test string")); + } + +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5313efd9..c6687353 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -34,6 +34,7 @@ google-cloud-functions app-engine firebase-admin + firebase diff --git a/pom.xml b/pom.xml index 5b82435d..95df9702 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ pubsub spanner storage + firebase-devservices firebase-admin firestore bigtable diff --git a/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/FirebaseDevServiceConfig.java b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/FirebaseDevServiceConfig.java new file mode 100644 index 00000000..ecd17733 --- /dev/null +++ b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/FirebaseDevServiceConfig.java @@ -0,0 +1,23 @@ +package io.quarkiverse.googlecloudservices.pubsub.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +/** + * Config mapping to detect if the Firebase Dev Services are running, in which case the PubSub dev service + * will be disabled by default as these two Devservice are in conflict with each other. + */ +@ConfigMapping(prefix = "quarkus.google.cloud.firebase.devservice") +@ConfigRoot +public interface FirebaseDevServiceConfig { + + /** + * Indicates to use the dev service for Firebase. The default value is not setup unless the firebase module + * is included. In that case, the Firebase devservices will by default be preferred and the DevService for + * PubSub will be disabled. + */ + Optional preferFirebaseDevServices(); + +} diff --git a/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubDevServiceProcessor.java b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubDevServiceProcessor.java index 096914f4..3e2d0460 100644 --- a/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubDevServiceProcessor.java +++ b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubDevServiceProcessor.java @@ -35,6 +35,7 @@ public class PubSubDevServiceProcessor { @BuildStep public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildItem, PubSubBuildTimeConfig pubSubBuildTimeConfig, + FirebaseDevServiceConfig firebaseConfig, List devServicesSharedNetworkBuildItem, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, @@ -57,7 +58,7 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI // Try starting the container if conditions are met try { devService = startContainerIfAvailable(dockerStatusBuildItem, pubSubBuildTimeConfig.devservice(), - globalDevServicesConfig.timeout); + firebaseConfig, globalDevServicesConfig.timeout); } catch (Throwable t) { LOGGER.warn("Unable to start PubSub dev service", t); // Dump captured logs in case of an error @@ -80,6 +81,7 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI */ private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(DockerStatusBuildItem dockerStatusBuildItem, PubSubDevServiceConfig config, + FirebaseDevServiceConfig firebaseConfig, Optional timeout) { if (!config.enabled()) { // PubSub service explicitly disabled @@ -87,6 +89,12 @@ private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(D return null; } + if (firebaseConfig.preferFirebaseDevServices().orElse(false)) { + // Firebase DevServices are included, use them instead + LOGGER.debug("Not starting Dev Services for Firestore as the Firebase DevServices are preferred"); + return null; + } + if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) { LOGGER.warn("Not starting devservice because docker is not available"); return null;